微信支付小微商户V3版本接口使用libsodium扩展以及代码示例

文章目录

微信支付小微商户接口地址

其接口操作中需要下载证书针对返回的 AEAD_AES_256_GCM算法解密,其中用到了sodium_crypto_aead_aes256gcm_decrypt ( string ciphertext , stringad , string nonce , stringkey )这个函数。使用这个函数需要开启 libsodium 扩展。

官方文档对该扩展的说明如下: As of PHP 7.2.0 this extension is bundled with PHP. For older PHP versions this extension is available via PECL. 从php 7.2.0开始,这个扩展与php捆绑在一起。对于旧的PHP版本,此扩展可通过pecl获得。

<?php

namespace WechatBundleServices;

use CarbonCarbon;
use DingoApiExceptionResourceException;

/**
 * Class WechatCouponService
 * @package WechatBundleServices
 */
class WechatCouponService
{
    /**
     * @var string
     */
    public baseUrl;
    /**
     * @var string
     */
    publicmch_id;
    /**
     * @var string
     */
    public sub_mch_id;
    /**
     * @var string
     */
    publicapp_id;
    /**
     * @var string
     */
    public private_key;
    /**
     * @var string
     */
    publicserial_no;
    /**
     * @var string
     */
    public mch_key;

    const REDIS_NAME_WECHAT_PAY_CERT = 'wechat_pay_v3_cert_no';

    const KEY_LENGTH_BYTE = 32;
    const AUTH_TAG_LENGTH_BYTE = 16;

    const GET_CERTIFICATES = '/v3/certificates';//获取商户平台证书
    const CREATE_COUPON_STOCKS = '/v3/marketing/favor/coupon-stocks';//创建代金券批次API
    const START_COUPON_STOCKS = '/v3/marketing/favor/stocks/%d/start';//激活代金券批次API
    const COUPON_SEND = '/v3/marketing/favor/users/%s/coupons';//发放代金券API
    const PAUSE_COUPON_STOCKS = '/v3/marketing/favor/stocks/%d/pause';//暂停代金券批次API
    const RESTART_COUPON_STOCKS = '/v3/marketing/favor/stocks/%d/pause';//重启代金券批次API
    const QUERY_COUPON_STOCKS = '/v3/marketing/favor/stocks';//条件查询批次列表API
    const QUERY_COUPON_STOCKS_INFO = '/v3/marketing/favor/stocks/%s';//查询批次详情API
    const QUERY_COUPON_INFO = '/v3/marketing/favor/users/%s/coupons/%s';//查询代金券详情API
    const QUERY_COUPON_MERCHANTS = '/v3/marketing/favor/stocks/%s/merchants';//查询代金券可用商户API
    const QUERY_COUPON_ITEMS = '/v3/marketing/favor/stocks/%s/items';//查询代金券可用单品API
    const QUERY_USER_COUPON = '/v3/marketing/favor/users/%s/coupons';//根据商户号查用户的券
    const COUPON_STOCKS_USER_FLOW_DOWNLOAD = '/v3/marketing/favor/stocks/%s/use-flow';//下载批次核销明细API
    const COUPON_STOCKS_REFUND_FLOW_DOWNLOAD = '/v3/marketing/favor/stocks/%s/refund-flow';//下载批次退款明细API
    const SETTING_COUPON_CALLBACKS = '/v3/marketing/favor/callbacks';//设置消息通知地址API
    /**
     * @var string
     */
    privatewechat_app_id;

    /**
     * WechatCouponService constructor.
     */
    public function __construct()
    {
        this->baseUrl = 'https://api.mch.weixin.qq.com';
        // 微信支付 商户号this->mch_id = '';
        // 二级商户号,需要走进件系统生成
        this->sub_mch_id = '';
        // 微信支付 商户号绑定的appidthis->app_id = '';
        // 商户私钥
        this->private_key = wordwrap(file_get_contents(storage_path('apiclient_key.pem')), 64, "n", true);
        // 商户证书序列号
        // 如何查看证书序列号:https://wechatpay-api.gitbook.io/wechatpay-api-v3/chang-jian-wen-ti/zheng-shu-xiang-guan#ru-he-cha-kan-zheng-shu-xu-lie-haothis->serial_no = '';
        // apiv3秘钥:https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/api-v3-mi-yao
        this->mch_key = '';
    }

    /**
     * 获取API v3证书
     * @return mixed
     */
    public function getCert()
    {wechatPayV3CertNo = app('redis')->get(self::REDIS_NAME_WECHAT_PAY_CERT);
        if (empty(wechatPayV3CertNo)) {
            try {url = this->baseUrl . self::GET_CERTIFICATES;timestamp = time();
                nonce =this->nonce_str();
                body = '';sign = this->sign(url, 'GET', timestamp,nonce, body,this->getPrivateKey(this->private_key),this->mch_id,
                    this->serial_no);header = [
                    'Authorization: WECHATPAY2-SHA256-RSA2048 ' . sign,
                    'Accept:application/json',
                    'User-Agent:' .this->mch_id,
                ];
                result =this->curl(url, '',header, 'GET');
                result = json_decode(result, true);
                if (!isset(result['data'])) {
                    throw new Exception('微信支付商户平台小微企业请求证书请求失败' . json_encode(result, 256));
                }
            } catch (Exception e) {
                throw new ResourceException(e->getMessage());
                app('api.exception')->report(e->getMessage());
            }wechatPayV3CertNo = result['data']['0']['serial_no'];
            app('redis')->set(self::REDIS_NAME_WECHAT_PAY_CERT,wechatPayV3CertNo, 'EX', 600);
        }
        return wechatPayV3CertNo;
    }

    /**
     * 获取随机字符串
     * @return string
     */
    protected function nonce_str()
    {
        staticcharacters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        charactersLength = strlen(characters);
        randomString = '';
        for (i = 0; i<32;i++) {
            randomString .=characters[rand(0, charactersLength - 1)];
        }
        returnrandomString;
    }

    /**
     * 获取签名
     * @param url
     * @paramhttp_method
     * @param timestamp
     * @paramnonce
     * @param body
     * @parammch_private_key
     * @param merchant_id
     * @paramserial_no
     * @return string
     */
    protected function sign(url,http_method, timestamp,nonce, body,mch_private_key, merchant_id,serial_no)
    {
        url_parts = parse_url(url);
        canonical_url = (url_parts['path'] . (!empty(url_parts['query']) ? "?{url_parts['query']}" : ""));
        message =http_method . "n" .
            canonical_url . "n" .timestamp . "n" .
            nonce . "n" .body . "n";

        openssl_sign(message,raw_sign, mch_private_key, 'sha256WithRSAEncryption');sign = base64_encode(raw_sign);schema = 'WECHATPAY2-SHA256-RSA2048';
        token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',merchant_id, nonce,timestamp, serial_no,sign);
        return token;
    }

    /**
     * 验签
     * @parammessage
     * @param signature
     * @parammerchantPublicKey
     * @return bool|int
     */
    private function verify(message,signature, merchantPublicKey)
    {
        if (empty(merchantPublicKey)) {
            return false;
        }

        if (!in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) {
            throw new RuntimeException("当前PHP环境不支持SHA256withRSA");
        }
        signature = base64_decode(signature);
        return openssl_verify(message,signature, this->getPublicKey(merchantPublicKey), 'sha256WithRSAEncryption');
    }

    /**
     * @param associatedData
     * @paramnonceStr
     * @param ciphertext
     * @paramaesKey
     * @return bool|string
     */
    private function decryptToString(associatedData,nonceStr, ciphertext,aesKey = '')
    {
        if (empty(aesKey)) {aesKey = this->mch_key;
        }ciphertext = base64_decode(ciphertext);
        if (strlen(ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
            return false;
        }

        // ext-sodium (default installed on >= PHP 7.2)
        if (function_exists('sodium_crypto_aead_aes256gcm_is_available') &&
            sodium_crypto_aead_aes256gcm_is_available()) {
            return sodium_crypto_aead_aes256gcm_decrypt(ciphertext,associatedData, nonceStr,aesKey);
        }

        // ext-libsodium (need install libsodium-php 1.x via pecl)
        if (function_exists('Sodiumcrypto_aead_aes256gcm_is_available') &&
            Sodiumcrypto_aead_aes256gcm_is_available()) {
            return Sodiumcrypto_aead_aes256gcm_decrypt(ciphertext,associatedData, nonceStr,aesKey);
        }

        // openssl (PHP >= 7.1 support AEAD)
        if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', openssl_get_cipher_methods())) {
            ctext = substr(ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
            authTag = substr(ciphertext, -self::AUTH_TAG_LENGTH_BYTE);

            return openssl_decrypt(ctext, 'aes-256-gcm',aesKey, OPENSSL_RAW_DATA, nonceStr,authTag, associatedData);
        }

        throw new RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
    }

    /**
     * 请求
     * @paramurl
     * @param array data
     * @paramheader
     * @param string method
     * @param inttime_out
     * @return mixed
     */
    private function curl(url,data = [], header,method = 'POST', time_out = 3)
    {curl = curl_init();
        // 设置curl允许执行的最长秒数

        curl_setopt(curl, CURLOPT_URL,url);
        curl_setopt(curl, CURLOPT_HTTPHEADER,header);
        curl_setopt(curl, CURLOPT_HEADER, false);
        curl_setopt(curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt(curl, CURLOPT_TIMEOUT,time_out);
        curl_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);

        if (method == 'POST') {
            curl_setopt(curl, CURLOPT_POST, true);
            curl_setopt(curl, CURLOPT_POSTFIELDS, data);
        }

        // 执行操作result = curl_exec(curl);

        curl_close(curl);
        return result;
    }

    /**
     * 获取请求头
     * @paramurl
     * @param body
     * @parammethod
     * @return array
     */
    protected function getCurlHeader(url,body, method)
    {timestamp = time();
        nonce =this->nonce_str();
        sign =this->sign(url,method, timestamp,nonce, body,this->getPrivateKey(this->private_key),this->mch_id,
            this->serial_no);

        return [
            'Authorization: WECHATPAY2-SHA256-RSA2048 ' .sign,
            'Accept:application/json',
            'User-Agent:' . this->mch_id,
            'Content-Type:application/json',
            'Wechatpay-Serial:' .this->getCert(),
        ];
    }

    /**
     * 获取私钥
     * @param key
     * @return bool|resource
     */
    protected function getPrivateKey(key)
    {
        return openssl_get_privatekey(key);
    }

    /**
     * @paramkey
     * @return resource
     */
    protected function getPublicKey(key)
    {
        return openssl_get_publickey(key);
    }

    /**
     * 获取请求头
     * @return array
     */
    private function getHeaders()
    {
        headers = array();
        foreach (_SERVER as key =>value) {
            if ('HTTP_' == substr(key, 0, 5)) {headers[str_replace('_', '-', substr(key, 5))] =value;
            }
            if (isset(_SERVER['PHP_AUTH_DIGEST'])) {header['AUTHORIZATION'] = _SERVER['PHP_AUTH_DIGEST'];
            } elseif (isset(_SERVER['PHP_AUTH_USER']) && isset(_SERVER['PHP_AUTH_PW'])) {header['AUTHORIZATION'] = base64_encode(_SERVER['PHP_AUTH_USER'] . ':' ._SERVER['PHP_AUTH_PW']);
            }
            if (isset(_SERVER['CONTENT_LENGTH'])) {header['CONTENT-LENGTH'] = _SERVER['CONTENT_LENGTH'];
            }
            if (isset(_SERVER['CONTENT_TYPE'])) {
                header['CONTENT-TYPE'] =_SERVER['CONTENT_TYPE'];
            }
        }
        return headers;
    }

    /**
     * 发放代金券API
     * @param stringstockId 批次号
     * @param string openId 用户openid
     * @param stringoutRequestNo 商户单据号
     * @param int|null couponValue 指定面额发券,面额
     * @param int|nullcouponMinimum 指定面额发券,券门槛
     * @return array
     * @throws Exception
     */
    public function couponSend(string stockId, stringopenId, string outRequestNo, ?intcouponValue, ?int couponMinimum): array
    {
        try {requestData = [
                'stock_id' => stockId,
                'out_request_no' =>outRequestNo,
                'appid' => this->app_id,
                'stock_creator_mchid' =>this->mch_id,
            ];
            if (!empty(couponValue)) {requestData['coupon_value'] = couponValue;
            }
            if (!empty(couponMinimum)) {
                requestData['coupon_minimum'] =couponMinimum;
            }
            header =this->getCurlHeader(this->baseUrl . sprintf(self::COUPON_SEND,openId), json_encode(requestData), 'POST');result = this->curl(this->baseUrl . sprintf(self::COUPON_SEND, openId), json_encode(requestData), header, 'POST');result = json_decode(result, true);
            if (!result || isset(result['code'])) {
                if (!result) {
                    throw new ResourceException('操作失败,请刷新页面后重试');
                }
                throw new ResourceException(result['code'] . '---' .result['message']);
            }
            return result;
        } catch (Exceptione) {
            throw new ResourceException(e->getMessage());
        }
    }

    /**
     * 条件查询批次列表API
     * @param string|nullcreateStartTime 起始时间
     * @param string|null createEndTime 终止时间
     * @param string|nullstatus 批次状态unactivated:未激活 audit:审核中 running:运行中 stoped:已停止 paused:暂停发放
     * @param int offset 分页页码
     * @param intlimit 分页大小
     * @return array
     * @throws Exception
     */
    public function queryCouponStocks(?string createStartTime, ?stringcreateEndTime, ?string status,offset = 0, limit = 10): ?array
    {
        try {
            if (!empty(status) && !in_array(status, ['unactivated', 'audit', 'running', 'stoped', 'paused'])) {
                throw new Exception('状态错误');
            }requestData = [
                'stock_creator_mchid' => this->mch_id,
                'offset' =>offset,
                'limit' => limit
            ];
            if (!empty(status)) {
                requestData['status'] =status;
            }
            if (!empty(createStartTime)) {requestData['create_start_time'] = Carbon::createFromTimestamp(strtotime(createStartTime))->toRfc3339String();
            }
            if (!empty(createEndTime)) {
                requestData['create_end_time'] = Carbon::createFromTimestamp(strtotime(createEndTime))->toRfc3339String();
            }
            url =this->baseUrl . self::QUERY_COUPON_STOCKS . '?' . getSignContent(requestData);header = this->getCurlHeader(url, '', 'GET');
            result =this->curl(url, '',header, 'GET');
            result = json_decode(result, true);
            if (!result || isset(result['code'])) {
                if (!result) {
                    throw new ResourceException('操作失败,请刷新页面后重试');
                }
                throw new ResourceException(result['code'] . '---' . result['message']);
            }
            returnresult;
        } catch (Exception e) {
            throw new ResourceException(e->getMessage());
        }
    }

    /**
     * 查询批次详情API
     * @param string stockId 批次号
     * @return mixed
     */
    public function queryCouponStocksInfo(stringstockId)
    {
        try {
            if (empty(stockId)) {
                throw new Exception('批次号不能为空');
            }requestData = [
                'stock_creator_mchid' => this->mch_id
            ];url = this->baseUrl . sprintf(self::QUERY_COUPON_STOCKS_INFO,stockId) . '?' . getSignContent(requestData);header = this->getCurlHeader(url, '', 'GET');
            result =this->curl(url, '',header, 'GET');
            result = json_decode(result, true);
            if (!result || isset(result['code'])) {
                if (!result) {
                    throw new ResourceException('操作失败,请刷新页面后重试');
                }
                throw new ResourceException(result['code'] . '---' . result['message']);
            }
            returnresult;
        } catch (Exception e) {
            throw new ResourceException(e->getMessage());
        }
    }

    /**
     * 查询代金券详情API
     * @param string openId openid
     * @param stringcouponId 代金券id
     * @return mixed
     */
    public function queryCouponInfo(string openId, stringcouponId)
    {
        try {
            if (empty(openId)) {
                throw new Exception('openId不能为空');
            }
            if (empty(couponId)) {
                throw new Exception('优惠券id不能为空');
            }
            requestData = [
                'appid' =>this->app_id
            ];
            url =this->baseUrl . sprintf(self::QUERY_COUPON_INFO, openId,couponId) . '?' . getSignContent(requestData);header = this->getCurlHeader(url, '', 'GET');
            result =this->curl(url, '',header, 'GET');
            result = json_decode(result, true);
            if (!result || isset(result['code'])) {
                if (!result) {
                    throw new ResourceException('操作失败,请刷新页面后重试');
                }
                throw new ResourceException(result['code'] . '---' . result['message']);
            }
            returnresult;
        } catch (Exception e) {
            throw new ResourceException(e->getMessage());
        }
    }

    /**
     * 查询代金券可用商户API
     * @param string stockId 批次号
     * @param intoffset 分页页码,最大1000。
     * @param int limit 分页大小,最大50。
     * @return mixed
     */
    public function queryCouponMerchants(stringstockId, offset = 1,limit = 10)
    {
        try {
            if (empty(stockId)) {
                throw new Exception('批次号不能为空');
            }requestData = [
                'stock_creator_mchid' => this->mch_id,
                'offset' =>offset,
                'limit' => limit
            ];url = this->baseUrl . sprintf(self::QUERY_COUPON_MERCHANTS,stockId) . '?' . getSignContent(requestData);header = this->getCurlHeader(url, '', 'GET');
            result =this->curl(url, '',header, 'GET');
            result = json_decode(result, true);
            if (!result || isset(result['code'])) {
                if (!result) {
                    throw new ResourceException('操作失败,请刷新页面后重试');
                }
                throw new ResourceException(result['code'] . '---' . result['message']);
            }
            returnresult;
        } catch (Exception e) {
            throw new ResourceException(e->getMessage());
        }
    }

    /**
     * 查询代金券可用单品API
     * @param string stockId 批次号
     * @param intoffset 分页页码,最大1000。
     * @param int limit 分页大小,最大50。
     * @return mixed
     */
    public function queryCouponItems(stringstockId, offset = 1,limit = 10)
    {
        try {
            if (empty(stockId)) {
                throw new Exception('批次号不能为空');
            }requestData = [
                'stock_creator_mchid' => this->mch_id,
                'offset' =>offset,
                'limit' => limit
            ];url = this->baseUrl . sprintf(self::QUERY_COUPON_ITEMS,stockId) . '?' . getSignContent(requestData);header = this->getCurlHeader(url, '', 'GET');
            result =this->curl(url, '',header, 'GET');
            result = json_decode(result, true);
            if (!result || isset(result['code'])) {
                if (!result) {
                    throw new ResourceException('操作失败,请刷新页面后重试');
                }
                throw new ResourceException(result['code'] . '---' . result['message']);
            }
            returnresult;
        } catch (Exception e) {
            throw new ResourceException(e->getMessage());
        }
    }

    /**
     * 根据商户号查用户的券
     * @param string openId 用户标识
     * @param stringstockId 批次号
     * @param string status 状态SENDED:可用 USED:已实扣
     * @param stringcreatorMchid 创建批次的商户号
     * @param string senderMchid 批次发放商户号
     * @param stringavailableMchid 可用商户号
     * @param int offset 分页页码
     * @param intlimit 分页大小
     * @return mixed
     */
    public function queryUserCoupon(string openId,stockId = '', status = '',creatorMchid = '', senderMchid = '',availableMchid = '', offset = 0,limit = 20)
    {
        try {
            if (!empty(status) && !in_array(status, ['SENDED', 'USED'])) {
                throw new Exception('状态错误');
            }
            requestData = [
                'appid' =>this->app_id,
                'offset' => offset,
                'limit' =>limit,
                'creator_mchid' => this->mch_id,
            ];
            if (!empty(stockId)) {
                requestData['stock_id'] =stockId;
            }
            if (!empty(status)) {requestData['status'] = status;
            }
            if (!empty(senderMchid)) {
                requestData['available_mchid'] =senderMchid;
            }
            if (!empty(availableMchid)) {requestData['available_mchid'] = availableMchid;
            }url = this->baseUrl . sprintf(self::QUERY_USER_COUPON,openId) . '?' . getSignContent(requestData);header = this->getCurlHeader(url, '', 'GET');
            result =this->curl(url, '',header, 'GET');
            result = json_decode(result, true);
            if (!result || isset(result['code'])) {
                if (!result) {
                    throw new ResourceException('操作失败,请刷新页面后重试');
                }
                throw new ResourceException(result['code'] . '---' . result['message']);
            }
            returnresult;
        } catch (Exception e) {
            throw new ResourceException(e->getMessage());
        }
    }

    /**
     * 下载批次核销明细API
     * @param string stockId 批次号
     * @return mixed
     */
    public function couponStocksUserFlowDownload(stringstockId)
    {
        try {
            if (empty(stockId)) {
                throw new Exception('批次号不能为空');
            }url = this->baseUrl . sprintf(self::COUPON_STOCKS_USER_FLOW_DOWNLOAD,stockId);
            header =this->getCurlHeader(url, '', 'GET');result = this->curl(url, '', header, 'GET');result = json_decode(result, true);
            if (!result || isset(result['code'])) {
                if (!result) {
                    throw new ResourceException('操作失败,请刷新页面后重试');
                }
                throw new ResourceException(result['code'] . '---' .result['message']);
            }
            return result;
        } catch (Exceptione) {
            throw new ResourceException(e->getMessage());
        }
    }

    /**
     * 下载批次退款明细API
     * @param stringstockId 批次号
     * @return mixed
     */
    public function couponStocksRefundFlowDownload(string stockId)
    {
        try {
            if (empty(stockId)) {
                throw new Exception('批次号不能为空');
            }
            url =this->baseUrl . sprintf(self::COUPON_STOCKS_REFUND_FLOW_DOWNLOAD, stockId);header = this->getCurlHeader(url, '', 'GET');
            result =this->curl(url, '',header, 'GET');
            result = json_decode(result, true);
            if (!result || isset(result['code'])) {
                if (!result) {
                    throw new ResourceException('操作失败,请刷新页面后重试');
                }
                throw new ResourceException(result['code'] . '---' . result['message']);
            }
            returnresult;
        } catch (Exception e) {
            throw new ResourceException(e->getMessage());
        }
    }

    /**
     * 设置消息通知地址API
     * @param string notifyUrl 支付通知商户url地址。
     * @param boolswitch 如果商户不需要再接收营销事件通知,可通过该开关关闭。枚举值:true:开启推送 false:停止推送
     * @return mixed
     */
    public function settingCouponCallbacks(string notifyUrl, boolswitch)
    {
        try {
            if (empty(notifyUrl)) {
                throw new ResourceException('回调地址不能为空,且必须是完整的https链接');
            }requestData = [
                'mchid' => this->mch_id,
                'notify_url' =>notifyUrl,
                'switch' => switch
            ];url = this->baseUrl . self::SETTING_COUPON_CALLBACKS;header = this->getCurlHeader(url, json_encode(requestData), 'POST');result = this->curl(url, json_encode(requestData),header, 'POST');
            result = json_decode(result, true);
            if (!result || isset(result['code'])) {
                if (!result) {
                    throw new ResourceException('操作失败,请刷新页面后重试');
                }
                throw new ResourceException(result['code'] . '---' . result['message']);
            }
            returnresult;
        } catch (Exception e) {
            throw new ResourceException(e->getMessage());
        }
    }
}
?>
<?php
if (!function_exists('getSignContent')) {
    /**
     * 拼接uri 用于验签等功能
     */
    function getSignContent(params) {
        ksort(params);
        i                = 0;stringToBeSigned = "";
        foreach (params ask => v) {
            if (i == 0) {
                stringToBeSigned .= "k" . "=" . "v";
            } else {stringToBeSigned .= "&" . "k" . "=" . "v";
            }
            i++;
        }
        unset (k, v);
        returnstringToBeSigned;
    }

}
?>php

zhaohao

大家好,欢迎来到赵豪博客!赵豪,94年生人,PHP程序员一枚,因为对PHP开发有着相对比较浓厚的兴趣,所以现在从事着PHP程序员的工作。 今天再次开通这个博客,这里将记录我的职业生涯的点点滴滴,感谢来访与关注!如果我的博客能给您带来一些帮助那真是一件非常荣幸的事情~

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

微信扫一扫

微信扫一扫

微信扫一扫,分享到朋友圈

微信支付小微商户V3版本接口使用libsodium扩展以及代码示例
返回顶部

显示

忘记密码?

显示

显示

获取验证码

Close