添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

2. 开发环境搭建

为了帮助开发者调用开放接口,我们提供了 Java PHP GO 语言版本的开发库,封装了 签名生成、签名验证、敏感信息加/解密、媒体文件上传 等基础功能

2.1.1. 使用条件

• WechatPay merchant account

• Java 1.8 or later

• Maven or Gradle;

• 0.4.6 or later version of the Java httpClient SDK

2.1.2. 安装

Gradle: 在你的 build.gradle 文件中加入如下的依赖

implementation 'com.github.wechatpay-apiv3:wechatpay-apache-httpclient:0.4.7'
        
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.7</version>
</dependency>

2.1.3. 初始化

用WechatPayHttpClientBuilder初始化创建一个httpClient,httpClient将被用于发送请求调用微信支付接口。 用CertificatesManager初始化一个verifier实例,用于验证签名。

private CloseableHttpClient httpClient; private CertificatesManager certificatesManager; private Verifier verifier; @Before //initialize verifier and build httpClient public void setup() throws GeneralSecurityException, IOException, HttpCodeException, NotFoundException { PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey); certificatesManager = CertificatesManager.getInstance(); certificatesManager.putMerchant(merchantId, new WechatPay2Credentials(merchantId, new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8)); verifier = certificatesManager.getVerifier(merchantId); WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create() .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey) .withValidator(new WechatPay2Validator(verifier)); httpClient = builder.build(); @Test public void encryptedTest() throws IllegalBlockSizeException { String text = "helloWorld"; String transformation = "RSA/ECB/PKCS1Padding"; String encryptedText = RsaCryptoUtil.encrypt(text, verifier.getValidCertificate(), transformation); System.out.println(encryptedText); @Test public void decryptedTest() throws BadPaddingException { String encryptedText = OBTgun4jiN13n3nmSg8v0MNDMy3mbs380yq4GrgO8vwCqXnvrWxwo3jUCNY2UY7MIOtv/SD8ke64MxcSB0hn5EzKv1LOLprI3NvvmNLS4C3SBulxpZG62RYp1+8FgwAI4M//icXvjtZWVH4KVDg=="; String transformation = "RSA/ECB/PKCS1Padding"; String decryptedText = RsaCryptoUtil.decrypt(encryptedText, PemUtil.loadPrivateKey(privateKey), transformation); assert("helloWorld".equals(decryptedText));

go mod init

2. 无需 clone 仓库中的代码,直接在项目目录中执行:

go get -u github.com/wechatpay-apiv3/wechatpay-go

来添加依赖,完成 go.mod 修改与 SDK 下载

2.2.3. 初始化

初始化Client并向Client中加载商户号,私钥,API V3 key,商户证书和序列号, 用于之后发送接口请求。

"fmt" "github.com/wechatpay-apiv3/wechatpay-go/core" "github.com/wechatpay-apiv3/wechatpay-go/core/option" "github.com/wechatpay-apiv3/wechatpay-go/utils" "log" "net/http" var ( mchID = "" // merchant id mchCertificateSerialNumber = "" // merchant certificate serial number mchAPIv3Key = "" // merchant api v3 key header = make(http.Header) ctx = context.Background() cert *x509.Certificate mchPrivateKey *rsa.PrivateKey client *core.Client err error func setup() { //Load platform certificate cert, err = utils.LoadCertificateWithPath("Path/To/Platform/Cert") if err != nil { log.Fatal(err) // Load private key mchPrivateKey, err = utils.LoadPrivateKeyWithPath("Path/To/Private/Key") if err != nil { log.Fatal("load merchant private key error") // Initialize client which is capable to update platform certificate periodically opts := []core.ClientOption{ option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key), client, err = core.NewClient(ctx, opts...) if err != nil { log.Fatalf("new wechat pay client err:%s", err) func Encryption(t *testing.T) { var testRSACryptoUtilMchCertificateStr = `-----BEGIN CERTIFICATE----- -----END CERTIFICATE-----` testRSACryptoUtilCertificate, _ = LoadCertificate(testRSACryptoUtilMchCertificateStr) const message = "hello world" // 使用OAEP padding方式对证书加密 ciphertext, _ := EncryptOAEPWithCertificate(message, testRSACryptoUtilCertificate) // 使用PKCS1 padding对证书加密 ciphertext, _ := EncryptPKCS1v15WithCertificate(message, testRSACryptoUtilCertificate) func TestDecryption(t *testing.T) { var testRSACryptoUtilPrivateKeyStr = `-----BEGIN PRIVATE KEY----- -----END PRIVATE KEY-----` testRSACryptoUtilPrivateKey, _ = LoadPrivateKey(testingKey(testRSACryptoUtilPrivateKeyStr)) const ciphertext = "" // 使用PKCS1 padding进行私钥解密 decryptMessage, _ := DecryptPKCS1v15(ciphertext, testRSACryptoUtilPrivateKey) // 使用OAEP padding方式私钥解密 decryptMessage, _ := DecryptOAEP(ciphertext, testRSACryptoUtilPrivateKey) $merchantId = '190000****'; $merchantPrivateKeyFilePath = 'file:///path/to/merchant/apiclient_key.pem'; $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE); $merchantCertificateSerial = '3775B6A45ACD588826D15E583A95F5DD********'; $platformCertificateFilePath = 'file:///path/to/wechatpay/cert.pem'; $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC); $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath); // init a API V3 instance $instance = Builder::factory([ 'mchid' => $merchantId, 'serial' => $merchantCertificateSerial, 'privateKey' => $merchantPrivateKeyInstance, 'certs' => [ $platformCertificateSerial => $platformPublicKeyInstance, $encryptor = static function(string $msg) use ($platformPublicKeyInstance): string { return Rsa::encrypt($msg, $platformPublicKeyInstance, OPENSSL_PKCS1_PADDING); $encryptedMsg = $encryptor('HelloWorld'); $decryptor = static function(string $msg) use ($merchantPrivateKeyInstance): string { return Rsa::decrypt($msg, $merchantPrivateKeyInstance, OPENSSL_PKCS1_PADDING); $decryptedMsg = $decryptor('ciphertext');

1. iOS系统OpenSDK升级指引

由于苹果公司在iOS13系统回收了查询 App bundleID 的能力,导致微信无法保证授权凭证能正确返回给AppID对应的应用。为此,微信支付强烈要求所有商户尽快升级到OpenSDK1.8.6,并让用户及时更新APP,否则安全风险将一直存在。谢谢配合!

详细OpenSDK升级指引请参见:OpenSDK升级指引

OpenSDK升级后请一定按照文档要求完成验证工作,确保OpenSDK升级成功。

以下项目开发环境以Xcode6.0,运行环境为IOS7.0为例,说明其开发中需要的操作

I. 项目设置APPID

商户在微信开放平台申请开发APP应用后,微信开放平台会生成APP的唯一标识APPID。在Xcode中打开项目,设置项目属性中的URL Schemes为您的APPID。如图标红位置所示

II. 注册APPID

商户APP工程中引入微信lib库和头文件,调用API前,需要先向微信注册您的APPID,代码如下:

   [WXApi registerApp:@"wxd930ea5d5a258f4f" withDescription:@"demo 2.0"];
                

1. 后台设置

商户在微信开放平台申请开发应用后,微信开放平台会生成APP的唯一标识APPID。由于需要保证支付安全,需要在开放平台绑定商户应用包名和应用签名,设置好后才能正常发起支付。设置界面在【开放平台】中的栏目【管理中心 / 修改应用 / 修改开发信息】里面,如图红框内所示。

应用包名:是在APP项目配置文件AndroidManifest.xml中声明的package值,例如上图中的package="demo.wxpay.tenpay.com"。

应用签名:根据项目的应用包名和编译使用的keystore,可由签名工具生成一个32位的md5串,在调试的手机上安装签名工具后,运行可生成应用签名串,如图8.9所示,绿色串即应用签名。

点击下载签名生成工具

2. 注册APPID:

商户APP工程中引入微信JAR包,调用API前,需要先向微信注册您的APPID,代码如下:

final IWXAPI msgApi = WXAPIFactory.createWXAPI(context, null);
// 将该app注册到微信
msgApi.registerApp("wxd930ea5d5a258f4f");
    //Call Unified Order API
    public void unifiedOrderTest() throws IOException {
      String unifiedOrderBody = String.join("\n" ,
                		"{" ,
                        "'sp_appid': 'wx2421b1c4370ec43b'," ,
                        "'sp_mchid': '10000100'," ,
                        "'sub_mchid': '20000100'," ,
                        "'sub_appid': 'wx352671b037b437ec'," ,
                        "'out_trade_no': '20150806125346'," ,
                        "'merchant_category_code': '1011'," ,
                        "'payer': {" ,
                        "'sp_openid': 'oUpF8uMuAJO_M2pxb1Q9zNjWeS6o'" ,
                        "}," ,
                        "'notify_url': 'https://wxpay.wxutil.com/pub_v2/pay/notify.v2.php'," ,
                        "'trade_type': 'APP'," ,
                        "'amount': {" ,
                        "'total': 10000," ,
                        "'currency': 'HKD'" ,
                        "}," ,
                        "'attach': 'Payment test'," ,
                        "'description': 'In-APP Pay test'," ,
                        "'goods_tag': '," ,
                        "'detail': {" ,
                        "'cost_price': 10000," ,
                        "'receipt_id': '1234'," ,
                        "'goods_detail': [{" ,
                        "'goods_id': 'iphone6s_16G'," ,
                        "'wxpay_goods_id': '1001'," ,
                        "'goods_name': 'iPhone6s 16G'," ,
                        "'quantity': 1," ,
                        "'price': 528800" ,
                        "}]" ,
                        "}," ,
                        "'scene_info': {" ,
                        "'payer_client_ip': '14.23.150.211'," ,
                        "'device_ip': '59.37.125.32'," ,
                        "'device_id': '013467007045764'," ,
                        "'operator_id': 'P001'," ,
                        "'store_info': {" ,
                        "'id': 'SZTX001'" ,
                        "}" ,
                        "}" ,
                        "}").replace("'","\"");
        HttpPost httpPost = new HttpPost("https://apihk.mch.weixin.qq.com/v3/global/transactions/app");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
        httpPost.setEntity(new StringEntity(unifiedOrderBody));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        //Process the response
//Call unified order API
func unifiedOrder() {
   orderRequestBody := `{
	"sp_appid": "wx2421b1c4370ec43b",
	"sp_mchid": "10000100",
	"sub_mchid": "20000100",
	"sub_appid": "wx352671b037b437ec",
	"out_trade_no": "20150806125346",
	"merchant_category_code": "1011",
	"payer": {
		"sp_openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"
	"notify_url": "https://wxpay.wxutil.com/pub_v2/pay/notify.v2.php",
	"trade_type": "APP",
	"amount": {
		"total": 10000,
		"currency": "HKD"
	"attach": "Payment test",
	"description": "In-APP Pay test",
	"goods_tag": "",
	"detail": {
		"cost_price": 10000,
		"receipt_id": "1234",
		"goods_detail": [{
			"goods_id": "iphone6s_16G",
			"wxpay_goods_id": "1001",
			"goods_name": "iPhone6s 16G",
			"quantity": 1,
			"price": 528800
	"scene_info": {
		"payer_client_ip": "14.23.150.211",
		"device_ip": "59.37.125.32",
		"device_id": "013467007045764",
		"operator_id": "P001",
		"store_info": {
			"id": "SZTX001"
   result, err := client.Post(ctx, "https://apihk.mch.weixin.qq.com/v3/global/transactions/app", orderRequestBody)
   if err != nil {
      // Process error
   log.Printf("status=%d resp=%s", result.Response.StatusCode, result.Response.Body)
// Call Unified Order API
public function unifiedOrder($instance){
    try {
        $resp = $instance
            ->chain('v3/global/transactions/app')
            ->post(['json' => [
                'sp_appid'     => 'wxdace645e0bc2c424',
                'sp_mchid'     => '10000100',
                'sub_mchid'    => '20000100',
				'sub_appid'		=> 'wx352671b037b437ec',
                'out_trade_no' => 'YX201710140020Z',
                'merchant_category_code' => '4111',
                'notify_url'   => 'https://weixin.qq.com/',
                'trade_type'   => 'APP',
                'amount'       => [
                    'total'    => 1,
                    'currency' => 'USD'
				'payer'			=> [
					'sub_openid' => 'oUpF8uMuAJO_M2pxb1Q9zNjWeS6o'
                'attach'  => 'mini-program payment test',
                'description'  => 'Image形象店-深圳腾大-QQ公仔',
                'goods_tag'  => '001'
        echo $resp->getStatusCode(), PHP_EOL;
        echo $resp->getBody(), PHP_EOL;
    } catch (Exception $e) {
        // EXception handling
        echo $e->getMessage(), PHP_EOL;
        if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
            $r = $e->getResponse();
            echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
            echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
        echo $e->getTraceAsString(), PHP_EOL;
										"stock_id": "Python",
										"stock_creator_mchid": "123456",
										"limit": 10,
          

重要参数:

notify_url: 接受异步支付结果回调通知的地址,通知url必须为外网可访问的url,不能携带参数。请使用https协议链接

out_trade_no: 商户系统内部订单号

其他重要参数请前往APP支付下单API文档页面参考。

3.2.2 APP调起支付

步骤说明:通过APP支付下单接口获取到发起支付的必要参数prepay_id,可以按照接口定义中的规则,使用微信支付提供的SDK调起APP支付。

APP调起支付的参数需要按照签名规则进行签名计算

构造签名串

   签名串一共有四行,每一行为一个参数。行尾以\n(换行符,ASCII编码值为0x0A)结束,包括最后一行。
   如果参数本身以\n结束,也需要附加一个\n
                
$ echo -n -e \
"wx8888888888888888\n1414561699\n5K8264ILTKCH16CQ2502SI8ZNMTM67VS\nWX1217752501201407033233368018\n" \
  | openssl dgst -sha256 -sign apiclient_key.pem \
  | openssl base64 -A
  uOVRnA4qG/MNnYzdQxJanN+zU+lTgIcnU9Bx7cob8H/4nLBiCwIUFluw==
                
PayReq *request = [[[PayReq alloc] init] autorelease];
request.partnerId = @"10000100";
request.prepayId= @"1101000000140415649af9fc314aa427";
request.package = @"Sign=WXPay";
request.nonceStr= @"a462b76e7436e98e0ed6e13c64b4fd1c";
request.timeStamp= @"1397527777";
request.sign= @"oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==";
[WXApi sendReq:request];
                
IWXAPI api;
PayReq request = new PayReq();
request.appId = "wxd930ea5d5a258f4f";
request.partnerId = "1900000109";
request.prepayId= "1101000000140415649af9fc314aa427",;
request.packageValue = "Sign=WXPay";
request.nonceStr= "1101000000140429eb40476f8896f4c9";
request.timeStamp= "1398746574";
request.sign= "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYDSs2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==";
api.sendReq(request);
    //Call query order API
    public void queryOrderTest() throws IOException {
        HttpGet httpGet = new HttpGet("https://apihk.mch.weixin.qq.com/v3/global/transactions/id/" +
                "4200123456789000?sub_mchid=100012321&sp_mchid=1900000000");
        httpGet.addHeader("Accept", "application/json");
        httpGet.addHeader("Content-type", "application/json; charset=utf-8");
        CloseableHttpResponse response = httpClient.execute(httpGet);
        //Process the response 
//Call query order API
func queryOrder() {
   result, err := client.Get(ctx, "https://apihk.mch.weixin.qq.com/v3/global/transactions/id/4200000000000000?sub_mchid=100012321&sp_mchid=1900000000")
   if err != nil {
      // Process error
   log.Printf("status=%d resp=%s", result.Response.StatusCode, result.Response.Body)
// Call query order API
public function queryOrder($instance)
    try {
        $resp = $instance
            ->v3->global->transactions->outTradeNo->_out_trade_no_
            ->get([
                    'query' => [
                'sp_mchid'     => '1900000000',
                'sub_mchid'    => '100012321',
                    'out_trade_no' => 'YX0001'
        echo $resp->getStatusCode(), PHP_EOL;
        echo $resp->getBody(), PHP_EOL;
    } catch (Exception $e) {
        // Exception handling
										"stock_id": "Python",
										"stock_creator_mchid": "123456",
										"limit": 10,
          

重要参数:

id: 微信支付订单号

out_trade_no:商户内部订单号

可以用上述任一订单号进行查询。其他重要参数请前往查询订单API文档页面参考。

3.2.4 申请退款

步骤说明:支付完成支付状态为SUCCESS后,可以调用该接口提交退款请求。

@Test //Call Refund API public void refundTest() throws IOException { String refundBody = String.join("\n" , "{" , "'sp_appid': 'wx2421b1c4370ec43b', " , "'sp_mchid': '10000100'," , "'sub_mchid': '20000100'," , "'transaction_id': '1008450740201411110005820873'," , "'out_refund_no': 'R20150806125346'," , " 'amount' : {" , " 'refund': 5," , " 'total':10," , " 'currency':'HKD'" , " }," , " 'reason': 'The item has been sold out.'," , " 'source': 'REFUND_SOURCE_UNSETTLED_FUNDS'" , "}").replace("'","\""); HttpPost httpPost = new HttpPost("https://apihk.mch.weixin.qq.com/v3/global/refunds"); httpPost.addHeader("Accept", "application/json"); httpPost.addHeader("Content-type", "application/json; charset=utf-8"); httpPost.setEntity(new StringEntity(refundBody)); CloseableHttpResponse response = httpClient.execute(httpPost); //Process the response func refund() { refundRequestBody := `{ "sp_appid": "wx2421b1c4370ec43b", "sp_mchid": "10000100", "sub_mchid": "20000100", "transaction_id": "1008450740201411110005820873", "out_refund_no": "R20150806125346", "amount" : { "refund": 50, "total":100, "currency":"HKD" "reason": "The item has been sold out", "source": "REFUND_SOURCE_UNSETTLED_FUNDS" result, err := client.Post(ctx, "https://apihk.mch.weixin.qq.com/v3/global/refunds", refundRequestBody) if err != nil { // Process error log.Printf("status=%d resp=%s", result.Response.StatusCode, result.Response.Body) // Call Refund API public function refundOrder($instance){ try { $resp = $instance ->chain('v3/global/refunds') ->post(['json' => [ 'sp_appid' => 'wxdace12300000000', 'sp_mchid' => '1900000000', 'sub_mchid' => '100012321', 'out_trade_no' => 'YX0001', "out_refund_no"=> "REFUNDYX0001", 'amount' => [ 'refund' => 1, 'total' => 1, 'currency' => 'USD' echo $resp->getStatusCode(), PHP_EOL; echo $resp->getBody(), PHP_EOL; } catch (Exception $e) { // Exception handling "stock_id": "Python", "stock_creator_mchid": "123456", "limit": 10,

重要参数:

out_trade_no: 商户内部订单号

transaction_id: 微信订单号

out_refund_no: 商户内部退款单号,应与订单号一一对应

notify_url: 退款结果接收地址

其他重要参数请前往申请退款API文档页面参考。

3.2.5 查询单笔退款

步骤说明:提交退款申请后,通过调用该接口查询退款状态。

@Test //Call Query Refund API public void querySingleRefundTest() throws IOException { HttpGet httpGet = new HttpGet("https://apihk.mch.weixin.qq.com/v3/global/refunds/out-refund-no/RYX001?sub_mchid=100012321&sp_mchid=1900000000"); httpGet.addHeader("Accept", "application/json"); httpGet.addHeader("Content-type", "application/json; charset=utf-8"); CloseableHttpResponse response = httpClient.execute(httpGet); //Process the response //Query single refund API func querySingleRefund() { result, err := client.Get(ctx, "https://apihk.mch.weixin.qq.com/v3/global/refunds/out-refund-no/REFUNDYX001?sub_mchid=100012321&sp_mchid=1900000000") if err != nil { // Process error log.Printf("status=%d resp=%s", result.Response.StatusCode, result.Response.Body) // Call Query Refund API public function queryRefund($instance){ try { $resp = $instance ->v3->global->refunds->outRefundNo->_out_refund_no_ ->get([ 'query' => [ 'sp_mchid' => '1900000000', 'sub_mchid'=> '100012321' 'out_refund_no' => 'REFUNDYX0001' echo $resp->getStatusCode(), PHP_EOL; echo $resp->getBody(), PHP_EOL; } catch (Exception $e) { // Exception handling "stock_id": "Python", "stock_creator_mchid": "123456", "limit": 10,

重要参数:

out_trade_no: 商户内部订单号

refund_id:微信支付退款单号

其他重要参数请前往查询单笔退款API文档页面参考。

3.2.6 查询所有退款

步骤说明:提交退款申请后,通过调用该接口查询一笔订单下对应的全部退款信息。

@Test //Call Query All Refund API public void queryAllRefundTest() throws IOException { HttpGet httpGet = new HttpGet("https://apihk.mch.weixin.qq.com/v3/global/refunds" + "?out_trade_no=YX202111100020&count=10&offset=0&sp_mchid=1900000000&sub_mchid=100012321"); httpGet.addHeader("Accept", "application/json"); httpGet.addHeader("Content-type", "application/json; charset=utf-8"); CloseableHttpResponse response = httpClient.execute(httpGet); //Process the response //Query all refund API func queryAllRefund() { result, err := client.Get(ctx, "https://apihk.mch.weixin.qq.com/v3/global/refunds/out-refund-no/out_trade_no=YX001&count=10&offset=0&sp_mchid=1900000000&sub_mchid=100012321") if err != nil { // Process error log.Printf("status=%d resp=%s", result.Response.StatusCode, result.Response.Body) // Call Query All Refund API public function queryAllRefund($instance){ try { $resp = $instance ->v3->global->refunds ->get([ 'query' => [ 'out_trade_no' => 'YX001', 'count' => 10, 'offset' => 0, 'sp_mchid' => '1900000000', 'sub_mchid'=> '100012321' echo $resp->getStatusCode(), PHP_EOL; echo $resp->getBody(), PHP_EOL; } catch (Exception $e) { // Exception handling "stock_id": "Python", "stock_creator_mchid": "123456", "limit": 10,

重要参数:

out_trade_no: 商户内部订单号

transaction_id: 微信订单号

offset: 分页起始位置

count: 单页返回的记录数,最大20条记录.

其他重要参数请前往查询所有退款API文档页面参考。

3.2.7 下载对账单

步骤说明:商户每天可以通过调用该接口可以下载历史交易账单,通过对帐单核对后可以校对更正支付信息。

@Test //Downloading Reconciliation API public void downloadReconTest() throws IOException { HttpGet httpGet = new HttpGet("https://apihk.mch.weixin.qq.com/v3/global/statements?date=20220401&mchid=1900000000"); httpGet.addHeader("Accept", "application/json"); httpGet.addHeader("Content-type", "application/json; charset=utf-8"); CloseableHttpResponse response = httpClient.execute(httpGet); //Process the response //Download recon file API func downloadReconFile() { result, err := client.Get(ctx, "https://apihk.mch.weixin.qq.com/v3/global/statements?date=20220401&mchid=1900000000") if err != nil { // Process error log.Printf("status=%d resp=%s", result.Response.StatusCode, result.Response.Body) //Downloading Reconciliation API public function downloadReconFile($instance){ try { $resp = $instance ->v3->global->statements ->get([ 'query' => [ 'date' => '20220401', 'mchid' => '1900000000' echo $resp->getStatusCode(), PHP_EOL; echo $resp->getBody(), PHP_EOL; } catch (Exception $e) { // Exception handling "stock_id": "Python", "stock_creator_mchid": "123456", "limit": 10,

重要参数:

date: 账单日期, 格式:20180103

其他重要参数请前往下载对账单API文档页面参考。

3.2.8 关闭订单

步骤说明:下单超过5分钟后,可以调用该接口对下单未支付的订单进行关单操作,以便商户更换订单号重新下单发起支付或商户系统退出不再受理后避免用户继续支付。

@Test //Call close order API public void closeOrderTest() throws IOException { String closeBody = String.join("\n" , "{" , " 'sp_mchid': '10000100'," , " 'sub_mchid': '20000100'" , "}").replace("'","\""); HttpPost httpPost = new HttpPost("https://apihk.mch.weixin.qq.com/v3/global/transactions/out-trade-no/YX001/close"); httpPost.addHeader("Accept", "application/json"); httpPost.addHeader("Content-type", "application/json; charset=utf-8"); httpPost.setEntity(new StringEntity(closeBody)); CloseableHttpResponse response = httpClient.execute(httpPost); System.out.println(response.getStatusLine().getStatusCode()); //Call close order API func closeOrder() { closeOrderBody := `{ "sp_mchid": "10000100", "sub_mchid": "20000100" result, err := client.Post(ctx, "https://apihk.mch.weixin.qq.com/v3/global/transactions/out-trade-no/YX001/close", closeOrderBody) if err != nil { // Process error log.Printf("status=%d resp=%s", result.Response.StatusCode, result.Response.Body) // Call close order API public function closeOrder($instance){ try { $resp = $instance ->v3->global->transactions->outTradeNo->_out_trade_no_->close ->post([ 'json' => [ 'sp_mchid' => '10000100', 'sub_mchid' => '20000100' 'out_trade_no' => 'YX0001' echo $resp->getStatusCode(), PHP_EOL; echo $resp->getBody(), PHP_EOL; } catch (Exception $e) { // Exception handling "stock_id": "Python", "stock_creator_mchid": "123456", "limit": 10,

重要参数:

out_trade_no: 商户内部订单号

transaction_id: 微信支付订单号

可以用上述任一订单号进行查询。其他重要参数请前往关闭订单API文档页面参考。

3.2.9 下载平台证书

步骤说明:在调用其他接口之前,需要调用该接口下载平台证书用于对返回消息进行验签和返回消息中的加密字段进行解密。

@Test //Call certificate downloading API public void certDownloadingTest() throws IOException { HttpGet httpGet = new HttpGet("https://apihk.mch.weixin.qq.com/v3/global/certificates"); httpGet.addHeader("Accept", "application/json"); httpGet.addHeader("Content-type", "application/json; charset=utf-8"); CloseableHttpResponse response = httpClient.execute(httpGet); //Process the response //Get the response body: EntityUtils.toString(response.getEntity()); //Get the response status code: response.getStatusLine().getStatusCode(); //Instead of calling the API to download platform certificate, //We also recommend use the certificateMenager to get the valid certificate verifier.getValidCertificate(); //Calling download certificate API func downloadCert() { result, err := client.Get((ctx, "https://apihk.mch.weixin.qq.com/v3/global/certificates")) if err != nil { if core.IsAPIError(err, "INVALID_REQUEST") { //Process invalid request // Process other error log.Printf("status=%d resp=%s", result.Response.StatusCode, result.Response.Body) // Call download certificate API public function downloadCert($instance){ $resp = $instance->chain('v3/global/certificates')->get( ['debug' => true] // debug mode,https://docs.guzzlephp.org/en/stable/request-options.html#debug echo $resp->getBody(), PHP_EOL; "stock_id": "Python", "stock_creator_mchid": "123456", "limit": 10,

重要参数:无

其他重要参数请前往下载平台证书API文档页面参考。

3.2.10 支付结果通知

步骤说明:用户完成支付后,微信支付会向下单时传入的notify_url推送支付结果通知,商户需要在接收到通知后返回对应的信息。

• 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。

• 如果在所有通知频率(4小时)后没有收到微信侧回调,商户应调用查询订单接口确认订单状态。

特别提醒:机构系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄露导致出现“假通知”,造成资金损失。

3.2.10.1 通知规则

用户支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理后返回应答成功。只有支付成功才会通知。

对后台通知交互时,如果微信收到应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 (通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m)

3.2.10.2 通知报文

支付结果通知是以“POST”方法访问商户设置的通知url,通知的数据以“JSON”格式通过请求主体(BODY)传输。通知的数据包括了加密的支付结果详情。

下面详细描述证书解密的流程

1、从商户平台上获取商户的密钥,记为“key”。

2、针对“algorithm”中描述的算法(目前为AEAD_AES_256_GCM),取得对应的参数“nonce”和“associated_data”。

3、使用“key”、“nonce”和“associated_data”对数据密文“ciphertext”进行解密(需要先对ciphertext做base64解码,然后再解密),得到证书内容

AEAD_AES_256_GCM算法的接口细节,请参考rfc5116。微信支付使用的密钥key长度为32个字节,随机串nonce长度12个字节,associated_data长度小于16个字节并可能为空。
3.2.10.3 通知签名

加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature。商户应当验证签名,以确认请求来自微信,而不是其他的第三方。签名验证的算法请参考《微信支付API V3签名验证》。

3.2.10.4 回调示例

支付成功结果通知

"id":"EV-2018022511223320873", "create_time":"20180225112233", "resource_type":"encrypt-resource", "event_type":"TRANSACTION.SUCCESS", "resource" : { "algorithm":"AEAD_AES_256_GCM", "ciphertext": "...", "nonce": "...", "associated_data": "" "id": "1008450740201411110005820873", "sp_appid": "wx2421b1c4370ec43b", "sp_mchid": "10000100", "sub_mchid": "20000100", "out_trade_no": "20150806125346", "payer": { "sp_openid": "oUpF8uN95-Ptaags6E_roPHg7AG0" "amount" : { "total": 528800, "currency": "HKD", "payer_total": 518799, "payer_currency": "CNY", "exchange_rate" : { "type": "SETTLEMENT_RATE", "rate": 8000000 "trade_type": "MICROPAY", "trade_state": "SUCCESS", "trade_state_desc": "支付成功", "bank_type": "CCB_DEBIT", "attach": "支付测试", "success_time": "2018-06-08T10:34:56+08:00", "promotion_detail":[ "promotion_id":"109519", "name":"单品惠-6", "scope":"SINGLE", "type":"DISCOUNT", "amount":1, "currency":"HKD", "activity_id":"931386", "wechatpay_contribute_amount":1, "merchant_contribute_amount":0, "other_contribute_amount":0, "goods_detail":[ "goods_id":"iphone6s_16G", "goods_remark":"商品备注", "quantity":1, "price":528800

• 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。

• 如果在所有通知频率(4小时)后没有收到微信侧回调,商户应调用查询订单接口确认订单状态。

特别提醒:机构系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄露导致出现“假通知”,造成资金损失。

3.2.11.1 通知规则

退款状态改变后,微信会把相关退款结果发送给商户。

对后台通知交互时,如果微信收到应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 (通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m)

3.2.11.2 通知报文

退款结果通知是以“POST”方法访问商户设置的通知url,通知的数据以“JSON”格式通过请求主体(BODY)传输。通知的数据包括了加密的退款结果详情。

下面详细描述证书解密的流程

1、从商户平台上获取商户的密钥,记为“key”。

2、针对“algorithm”中描述的算法(目前为AEAD_AES_256_GCM),取得对应的参数“nonce”和“associated_data”。

3、使用“key”、“nonce”和“associated_data”对数据密文“ciphertext”进行解密(需要先对ciphertext做base64解码,然后再解密),得到证书内容

AEAD_AES_256_GCM算法的接口细节,请参考rfc5116。微信支付使用的密钥key长度为32个字节,随机串nonce长度12个字节,associated_data长度小于16个字节并可能为空。
3.2.11.3 通知签名

加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature。商户应当验证签名,以确认请求来自微信,而不是其他的第三方。签名验证的算法请参考《微信支付API V3签名验证》。

3.2.11.4 回调示例
"id": "EV-2018022511223320873", "create_time": "2018-06-08T10:34:56+08:00", "resource_type": "encrypt-resource", "event_type": "REFUND.SUCCESS", "summary": "退款成功", "resource": { "original_type": "refund", "algorithm": "AEAD_AES_256_GCM", "ciphertext": "...", "associated_data": "", "nonce": "..." "sp_mchid": "1900000100", "sub_mchid": "1900000109", "transaction_id": "1008450740201411110005820873", "out_trade_no": "20150806125346", "refund_id": "50200207182018070300011301001", "out_refund_no": "7752501201407033233368018", "refund_status": "SUCCESS", "success_time": "2018-06-08T10:34:56+08:00", "recv_account": "招商银行信用卡0403", "fund_source": "REFUND_SOURCE_UNSETTLED_FUNDS", "amount" : { "total": 528800, "currency": "HKD", "refund": 528800, "payer_total": 528800, "payer_refund": 528800, "payer_currency": "HKD", "exchange_rate" : { "type": "SETTLEMENT_RATE", "rate": 100000000

其他重要参数请前往退款通知API文档页面参考。

3.2.12 查询资金结算明细

步骤说明:商户在交易完结之后,可按结算日期查询已结算资金明细(sette_state为SETTLED),也可以查询未结算资金明细(sette_state为UNSETTLE)。

该接口供跨境收单机构/直连商户使用,特别是,日本/澳门机构商户若开通香港钱包业务,需要对接该接口。
@Test //Query Settlement API public void querySettlementTest() throws IOException { HttpGet httpGet = new HttpGet("https://apihk.mch.weixin.qq.com/v3/global/settle/settlements" + "?sub_mchid=100012321&settle_state=SETTLED&settle_start_date=20221225" + "&settle_end_date=20221226&offset=10&limit=5"); httpGet.addHeader("Accept", "application/json"); httpGet.addHeader("Content-type", "application/json; charset=utf-8"); CloseableHttpResponse response = httpClient.execute(httpGet); //Process the response //Query settlement API func querySettlement() { result, err := client.Get(ctx, "https://apihk.mch.weixin.qq.com/v3/global/settle/settlements?sub_mchid=100012321&settle_state=SETTLED&settle_start_date=20221225&settle_end_date=20221226&offset=10&limit=5") if err != nil { // Process error log.Printf("status=%d resp=%s", result.Response.StatusCode, result.Response.Body) //Querying Settlement Info API public function querySettlementInfo($instance){ try { $resp = $instance ->v3->global->settle->settlements ->get(['query' => [ 'sub_mchid' => '100012321', 'settle_state' => 'SETTLED', 'settle_start_date' => '20221225', 'settle_end_date' => '20221226', 'offset' => '10', 'limit' => '5' echo $resp->getStatusCode(), PHP_EOL; echo $resp->getBody(), PHP_EOL; } catch (\Exception $e) { // Exception handling "stock_id": "Python", "stock_creator_mchid": "123456", "limit": 10,

重要参数:

settle_state: 资金结算状态,枚举值:

SETTLED:已结算

UNSETTLE:未结算

其他重要参数请前往查询资金结算明细API文档页面参考。