商户进件
特约商户进件
基础支付
JSAPI支付
APP支付
H5支付
Native支付
小程序支付
合单支付
付款码支付
经营能力
支付即服务
点金计划
行业方案
平台收付通
智慧商圈
微信支付分停车服务
电子发票
营销工具
代金券
商家券
委托营销
支付有礼
小程序发券插件
H5发券
图片上传(营销专用)
现金红包
资金应用
分账
连锁品牌分账
风险合规
商户开户意愿确认
消费者投诉2.0
商户平台处置通知
其他能力
图片上传
视频上传
微信支付平台证书

APP支付开发指引

1. 接口规则

为了在保证支付安全的前提下,带给商户简单、一致且易用的开发体验,我们推出了全新的微信支付APIv3接口。该版本API的具体规则请参考“APIv3接口规则

2. 开发准备

2.1. 搭建和配置开发环境

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

测试步骤

1、根据自身开发语言,选择对应的开发库并构建项目,具体配置请参考下面链接的详细说明:

    • wechatpay-java(推荐)wechatpay-apache-httpclient,适用于Java开发者。

    • wechatpay-php(推荐)、wechatpay-guzzle-middleware,适用于PHP开发者

    注:当前开发指引接口PHP示例代码采用wechatpay-guzzle-middleware版本

    • wechatpay-go,适用于Go开发者

更多资源可前往微信支付开发者社区搜索查看

2、创建加载商户私钥、加载平台证书、初始化httpClient的通用方法


@Before
public void setup() throws IOException {
    // 加载商户私钥(privateKey:私钥字符串)
    PrivateKey merchantPrivateKey = PemUtil
            .loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
 
    // 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
    AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
            new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),apiV3Key.getBytes("utf-8"));
 
    // 初始化httpClient
    httpClient = WechatPayHttpClientBuilder.create()
            .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
            .withValidator(new WechatPay2Validator(verifier)).build();
}
 
@After
public void after() throws IOException {
    httpClient.close();
}

use GuzzleHttp\Exception\RequestException;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
use GuzzleHttp\HandlerStack;
 
// 商户相关配置,
$merchantId = '1000100'; // 商户号
$merchantSerialNumber = 'XXXXXXXXXX'; // 商户API证书序列号
$merchantPrivateKey = PemUtil::loadPrivateKey('./path/to/mch/private/key.pem'); // 商户私钥文件路径
 
// 微信支付平台配置
$wechatpayCertificate = PemUtil::loadCertificate('./path/to/wechatpay/cert.pem'); // 微信支付平台证书文件路径
 
// 构造一个WechatPayMiddleware
$wechatpayMiddleware = WechatPayMiddleware::builder()
    ->withMerchant($merchantId, $merchantSerialNumber, $merchantPrivateKey) // 传入商户相关配置
    ->withWechatPay([ $wechatpayCertificate ]) // 可传入多个微信支付平台证书,参数类型为array
    ->build();
 
// 将WechatPayMiddleware添加到Guzzle的HandlerStack中
$stack = GuzzleHttp\HandlerStack::create();
$stack->push($wechatpayMiddleware, 'wechatpay');
 
// 创建Guzzle HTTP Client时,将HandlerStack传入,接下来,正常使用Guzzle发起API请求,WechatPayMiddleware会自动地处理签名和验签
$client = new GuzzleHttp\Client(['handler' => $stack]);

/*
    Package core 微信支付api v3 go http-client 基础库,你可以使用它来创建一个client,并向微信支付发送http请求
    只需要你在初始化客户端的时候,传递credential以及validator
    credential用来生成http header中的authorization信息
    validator则用来校验回包是否被篡改
    如果http请求返回的err为nil,一般response.Body 都不为空,你可以尝试对其进行序列化
    请注意及时关闭response.Body
    注意:使用微信支付apiv3 go库需要引入相关的包,该示例代码必须引入的包名有以下信息

    "context"
    "crypto/x509"
    "fmt"
    "io/ioutil"
    "log"
    "github.com/wechatpay-apiv3/wechatpay-go/core"
    "github.com/wechatpay-apiv3/wechatpay-go/core/option"
    "github.com/wechatpay-apiv3/wechatpay-go/utils"

    */
func SetUp() (opt []option.ClientOption, err error) {
    //商户号
    mchID := ""
    //商户证书序列号
    mchCertSerialNumber := ""
    //商户私钥文件路径
    privateKeyPath := ""
    //平台证书文件路径
    wechatCertificatePath := ""

    // 加载商户私钥
    privateKey, err := utils.LoadPrivateKeyWithPath(privateKeyPath)
    if err != nil {
        log.Printf("load private err:%s", err.Error())
        return nil, err
    }
    // 加载微信支付平台证书
    wechatPayCertificate, err := utils.LoadCertificateWithPath(wechatCertificatePath)
    if err != nil {
        log.Printf("load certificate err:%s",err)
        return nil, err
    }
    //设置header头中authorization信息
    opts := []option.ClientOption{
        option.WithMerchant(mchID, mchCertSerialNumber, privateKey), // 设置商户相关配置
        option.WithWechatPay([]*x509.Certificate{wechatPayCertificate}), // 设置微信支付平台证书,用于校验回包信息用
    }
    return opts, nil
}

3、基于接口的示例代码,替换请求参数后可发起测试

说明:

• 上面的开发库为微信支付官方开发库,其它没有审核或者控制下的第三方工具和库,微信支付不保证它们的安全性和可靠性

通过包管理工具引入SDK后,可根据下面每个接口的示例代码替换相关参数后进行快速测试

开发者如果想详细了解签名生成、签名验证、敏感信息加/解密、媒体文件上传等常用方法的具体代码实现,可阅读下面的详细说明:

    1.签名生成

    2.签名验证

    3.敏感信息加解密

    4.merchantPrivateKey(私钥)

    5.wechatpayCertificates(平台证书)

    6.APIV3Key(V3 key)

如想更详细的了解我们的接口规则,可查看我们的接口规则指引文档

2.2. 业务开发配置

2.2.1. 注册APP

APP接入微信支付,需要先将商户APP在微信开放平台进行注册,登记APP开发参数以生成APPID。具体操作步骤如下:

一、登录微信开放平台,进入“管理中心”→“移动应用”→"创建移动应用";

二、完成基本信息的录入,商户需要在本步骤提交APP对应的下载地址,应用官网,应用水印,icon等业务信息;

三、完成平台信息的录入,商户需要在本步骤提交APP在Android及iOS端对应的开发参数,包括Android端应用的包名,应用签名,iOS端应用的bundle ID, universal link等;

注意:

四、以上信息全部提交完成后,即完成APP的注册,商户可在“管理中心”→“移动应用”中,选择具体的应用查看其APPID及已获得的接口能力;

五、获取到APP的APPID后,需要将该APPID与商户的收款mch_id进行绑定,商户可登录商户平台后前往“产品中心”->“AppID账号管理”界面中进行AppID的绑定及管理,界面如图所示:

2.2.2. iOS开发要点说明
• iOS系统OpenSDK升级指引

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

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

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

• 开发配置:

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

  • 一、项目设置APPID

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

  • 二、注册APPID

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

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

•注意:

OpenSDK前端拉起支付及SDK回调的相关说明,请参考OpenSDK调起支付说明

  • 一、后台设置

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

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

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

    点击下载签名生成工具

  • 二、注册APPID

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

    final IWXAPI msgApi = WXAPIFactory.createWXAPI(context, null);

    // 将该app注册到微信

    msgApi.registerApp("wxd930ea5d5a258f4f");


•注意:

OpenSDK前端拉起支付及SDK回调的相关说明,OpenSDK调起支付说明

3. 快速接入

3.1. 业务流程图

重点步骤说明:

步骤3:用户下单发起支付,商户可通过微信支付APP下单API创建支付订单。

商户调用APP下单API后,分正常返回和异常返回情况:

  • 正常返回:返回prepay_id,商户可根据返回的prepay_id来生成调用OpenSDK的签名以执行下一步。
  • 异常返回:返回http code或错误码,商户可根据http code列表错误码说明来排查原因并执行下一步操作

步骤8: 商户通过APP调起支付OpenSDK调起微信支付,发起支付请求,有关OpenSDK调起支付的详细说明,请参考2.2.2部分的说明

步骤15-19 : 用户支付成功后,商户可通过以下两种方式获取订单状态

我们通过以下接口将用户确认订单信息回调通知给商户系统:

方法一:支付结果通知。用户支付成功后,微信支付会将支付成功的结果以回调通知的形式同步给商户,商户的回调地址需要在调用APP下单API时传入notify_url参数。

方法二:当因网络抖动或本身notify_url存在问题等原因,导致无法接收到回调通知时,商户也可主动调用查询订单API 查询订单API来获取订单状态


3.2. API接入(含示例代码)

本文档展示了如何使用微信支付服务端 SDK 快速接入APP支付产品,完成与微信支付对接的部分。

注意:

  • 文档中的代码示例是用来阐述 API 基本使用方法,代码中的示例参数需替换成商户自己账号及参数才能跑通。
  • 以下接入步骤仅提供参考,请商户结合自身业务需求进行评估、修改。
3.2.1. 【服务端】APP下单

步骤说明:用户在商户APP内完成商户选择后进入支付页面,商户需要通过后端请求该APP下单API来获取预支付ID。

示例代码

public void CreateOrder() throws Exception{
  //请求URL
  HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/partner/transactions/app");
  // 请求body参数
  String reqdata = "{"
          + "\"sp_appid\":\"wx8888888888888888\","
          + "\"sp_mchid\":\"1230000109\","
		  + "\"sub_appid\":\"wxd678efh567hg6999\","
          + "\"sub_mchid\":\"1900000109\","
          + "\"description\":\"Image形象店-深圳腾大-QQ公仔\","
          + "\"out_trade_no\":\"1217752501201407033233368018\","
          + "\"time_expire\":\"2018-06-08T10:34:56+08:00\","
          + "\"goods_tag\":\"WXG\","
          + "\"attach\":\"自定义数据说明\","
          + "\"notify_url\":\"https://www.weixin.qq.com/wxpay/pay.php\","
          + "\"amount\": {"
          + "\"total\":100,"
          + "\"currency\":\"CNY\""
          + "},"
          + "\"settle_info\": {"
          + "\"profit_sharing\":false,"
          + "},"
          + "\"detail\": {"
          + "\"invoice_id\":\"wx123\","
          + "\"goods_detail\": ["
          + "{"
          + "\"goods_name\":\"iPhoneX 256G\","
          + "\"wechatpay_goods_id\":\"1001\","
          + "\"quantity\":1,"
          + "\"merchant_goods_id\":\"商品编码\","
          + "\"unit_price\":828800"
          + "},"
          + "{"
          + "\"goods_name\":\"iPhoneX 256G\","
          + "\"wechatpay_goods_id\":\"1001\","
          + "\"quantity\":1,"
          + "\"merchant_goods_id\":\"商品编码\","
          + "\"unit_price\":828800"
          + "}"
          + "],"
          + "\"cost_price\":608800"
          + "},"
          + "\"scene_info\": {"
          + "\"store_info\": {"
          + "\"address\":\"广东省深圳市南山区科技中一道10000号\","
          + "\"area_code\":\"440305\","
          + "\"name\":\"腾讯大厦分店\","
          + "\"id\":\"0001\""
          + "},"
          + "\"device_id\":\"013467007045764\","
          + "\"payer_client_ip\":\"14.23.150.211\""
          + "}"
          + "}";
  StringEntity entity = new StringEntity(reqdata,"utf-8");
  entity.setContentType("application/json");
  httpPost.setEntity(entity);
  httpPost.setHeader("Accept", "application/json");

  //完成签名并执行请求
  CloseableHttpResponse response = httpClient.execute(httpPost);

  try {
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) { //处理成功
          System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
      } else if (statusCode == 204) { //处理成功,无返回Body
          System.out.println("success");
      } else {
          System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
          throw new IOException("request failed");
      }
  } finally {
      response.close();
  }
}
try {
    $resp = $client->request(
        'POST',
        'https://api.mch.weixin.qq.com/v3/pay/partner/transactions/app', //请求URL
        [
            // JSON请求体
            'json' => [ 
                "sp_appid" => "wx8888888888888888", 
                "sp_mchid" => "1230000109", 
				"sub_appid" => "wxd678efh567hg6999",
                "sub_mchid" => "1900000109", 
                "description" => "Image形象店-深圳腾大-QQ公仔", 
                "out_trade_no" => "1217752501201407033233368018", 
                "time_expire" => "2018-06-08T10:34:56+08:00", 
                "goods_tag" => "WXG", 
                "attach" => "自定义数据说明", 
                "notify_url" => "https://www.weixin.qq.com/wxpay/pay.php", 
                "amount" => [
                    "total" => 100, 
                    "currency" => "CNY", 
                ],
                "settle_info" => [
                    "profit_sharing" => false, 
                ],
                "detail" => [
                    "invoice_id" => "wx123", 
                    "goods_detail" => [
                        [
                            "goods_name" => "iPhoneX 256G", 
                            "wechatpay_goods_id" => "1001", 
                            "quantity" => 1, 
                            "merchant_goods_id" => "商品编码", 
                            "unit_price" => 828800, 
                        ],
                        [
                            "goods_name" => "iPhoneX 256G", 
                            "wechatpay_goods_id" => "1001", 
                            "quantity" => 1, 
                            "merchant_goods_id" => "商品编码", 
                            "unit_price" => 828800, 
                        ],
                    ],
                    "cost_price" => 608800, 
                ],
                "scene_info" => [
                    "store_info" => [
                        "address" => "广东省深圳市南山区科技中一道10000号", 
                        "area_code" => "440305", 
                        "name" => "腾讯大厦分店", 
                        "id" => "0001", 
                    ],
                    "device_id" => "013467007045764", 
                    "payer_client_ip" => "14.23.150.211", 
                ]
            ],
            'headers' => [ 'Accept' => 'application/json' ]
        ]
    );
    $statusCode = $resp->getStatusCode();
    if ($statusCode == 200) { //处理成功
        echo "success,return body = " . $resp->getBody()->getContents()."\n";
    } else if ($statusCode == 204) { //处理成功,无返回Body
        echo "success";
    }
} catch (RequestException $e) {
    // 进行错误处理
    echo $e->getMessage()."\n";
    if ($e->hasResponse()) {
        echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
    }
    return;
}

func CreateOrder() {
       // 初始化客户端
    ctx := context.TODO()
    opts, err := SetUp()
    if err != nil {
        return
    }
    client, err := core.NewClient(ctx, opts...,)
    if err != nil{
        log.Printf("init client err:%s",err)
        return
    }
    //设置请求地址
  URL := "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/app"
  //设置请求信息,此处也可以使用结构体来进行请求
  mapInfo := map[string]interface{}{
    "sp_mchid": "1900007XXX",
    "sub_mchid": "1900008XXX",
    "out_trade_no": "APP1217752501201407033233368018",
    "sp_appid": "wxdace645e0bc2cXXX",
    "sub_appid": "wxb4ba3c02aa476XXX",
    "description": "Image形象店-深圳腾大-QQ公仔",
    "notify_url": "https://weixin.qq.com/",
    "amount": map[string]interface{}{
      "total": 1,
      "currency": "CNY",
    },
  }

  // 发起请求
  response, err := client.Post(ctx, URL, mapInfo)
  if err != nil{
    log.Printf("client post err:%s",err)
    return
  }
  // 校验回包内容是否有逻辑错误
  err = core.CheckResponse(response)
  if err != nil{
    log.Printf("check response err:%s",err)
    return
  }
  // 读取回包信息
  body, err := ioutil.ReadAll(response.Body)
  if err != nil{
    log.Printf("read response body err:%s",err)
    return
  }
  fmt.Println(string(body))
}
    

重要参数说明

• out_trade_no:商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一

• description:商品描述

• notify_url:支付回调通知URL,该地址必须为直接可访问的URL,不允许携带查询串

• total:订单总金额,单位为分

更多参数、响应详情及错误码请参见APP下单API接口文档

步骤说明:通过APP下单API成功获取预支付交易会话标识(prepay_id)后,需要通过OpenSDK来调起微信支付收银台

  • 该步骤请使用开放平台的官方OpenSDK:
    IOS OpenSDK下载地址
    Android OpenSDK下载地址
  • SDK的调用需要携带签名(参与签名的参数为:appid、partnerid、prepayid、package、noncestr、timestamp,参数区分大小写)
  • 重要入参说明:
    • package:取固定值Sign=WXPay
    • signType:该接口V3版本仅支持RSA
    • paySign:签名

    paySign生成规则、响应详情及错误码请参见APP调起支付接口文档

iOS SDK调用说明

  • 一、拉起支付

    商户服务器生成支付订单,先调用APP下单API生成预付单,获取到prepay_id后将参数再次签名传输给APP发起支付。以下是调起微信支付的关键代码:

    PayReq *request = [[[PayReq alloc] init] autorelease];
    
    request.partnerId = @"10000100";
    
    request.prepayId= @"1101000000140415649af9fc314aa427";
    
    request.package = @"Sign=WXPay";
    
    request.nonceStr= @"a462b76e7436e98e0ed6e13c64b4fd1c";
    
    request.timeStamp= @"1397527777";
    
    request.sign= @"582282D72DD2B03AD892830965F428CB16E7A256";
    
    [WXApi sendReq:request]
                                

    注意:该sign生成字段名列表见调起支付API

  • 二、SDK结果回调

    按照微信SDK Sample,在类实现onResp函数,支付完成后,微信APP会返回到商户APP并回调onResp函数,开发者需要在该函数中接收通知,判断返回错误码, 如果支付成功则去后台查询支付结果再展示用户实际支付结果。注意 一定不能以客户端返回作为用户支付的结果,应以服务器端接收的支付通知或查询API返回的结果为准。代码示例如下:

    -(void)onResp:(BaseResp*)resp{
    
    if ([respisKindOfClass:[PayRespclass]]){
    
        PayResp*response=(PayResp*)resp;
    
        switch(response.errCode){
    
                    caseWXSuccess: //服务器端查询支付通知或查询API返回的结果再提示成功
    
                              NSlog(@"支付成功");
    
                    break;
    
                    default:
    
                              NSlog(@"支付失败,retcode=%d",resp.errCode);
    
                    break;
    
          }
    
      }
    
    }

    回调中errCode值列表:

    名称 描述 解决方案
    -2 用户取消 无需处理。发生场景:用户不支付了,点击取消,返回APP。
    0 成功 展示成功页面
    -1 错误 可能的原因:签名错误、未注册APPID、项目设置APPID不正确、注册的APPID与设置的不匹配、其他异常等。

Android SDK调用说明

  • 一、SDK拉起支付

    商户服务器生成支付订单,先调用APP下单API生成预付单,获取到prepay_id后将参数再次签名传输给APP发起支付。以下是调起微信支付的关键代码:

    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= "7FFECB600D7157C5AA49810D2D8F28BC2811827B";
      
      api.sendReq(request);

    注意:该sign生成字段名列表见调起支付API

  • 二、支付结果回调

    参照微信SDK Sample,在net.sourceforge.simcpux.wxapi包路径中实现WXPayEntryActivity类(包名或类名不一致会造成无法回调),在WXPayEntryActivity类中实现onResp函数,支付完成后,微信APP会返回到商户APP并回调onResp函数,开发者需要在该函数中接收通知,判断返回错误码, 如果支付成功则去后台查询支付结果再展示用户实际支付结果。注意一定不能以客户端返回作为用户支付的结果,应以服务器端的接收的支付通知或查询API返回的结果为准。代码示例如下:

    publicvoidonResp(BaseRespresp){
    
        if(resp.getType()==ConstantsAPI.COMMAND_PAY_BY_WX){
    
        Log.d(TAG,"onPayFinish,errCode="+resp.errCode);
    
        AlertDialog.Builderbuilder=newAlertDialog.Builder(this);
    
        builder.setTitle(R.string.app_tip);
    
        }
    
    }

    回调中errCode值列表:

    名称 描述 解决方案
    0 成功 展示成功页面
    -1 错误 可能的原因:签名错误、未注册APPID、项目设置APPID不正确、注册的APPID与设置的不匹配、其他异常等。
    -2 用户取消 无需处理。发生场景:用户不支付了,点击取消,返回APP。
3.2.3.【服务端】接收支付结果通知

步骤说明:当用户完成支付,微信会把相关支付结果将通过异步回调的方式通知商户,商户需要接收处理,并按文档规范返回应答

注意

  • 支付结果通知是以POST 方法访问商户设置的通知url,通知的数据以JSON 格式通过请求主体(BODY)传输。通知的数据包括了加密的支付结果详情
  • 加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature。商户应当验证签名,以确认请求来自微信,而不是其他的第三方。签名验证的算法请参考 《微信支付API v3签名验证》
  • 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx
  • 商户成功接收到回调通知后应返回成功的http应答码为200或204
  • 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
  • 对后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。(通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m)

更多参数、响应详情及错误码请参见 JSAPI / APP / H5 / Native / 小程序支付通知API接口文档

3.2.4. 【服务端】查询订单

步骤说明:当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知时,商户可通过查询订单接口核实订单支付状态

示例代码(通过微信订单号查询):


public void QueryOrder() throws Exception {
   
  //请求URL
  HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/pay/partner/transactions/id/1217752501201407033233368018?sp_mchid=1230000109&sub_mchid=1900000109");
  httpGet.setHeader("Accept", "application/json");

  //完成签名并执行请求
  CloseableHttpResponse response = httpClient.execute(httpGet);

  try {
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) { //处理成功
          System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
      } else if (statusCode == 204) { //处理成功,无返回Body
          System.out.println("success");
      } else {
          System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
          throw new IOException("request failed");
      }
  } finally {
      response.close();
  }
}
      

try {
    $resp = $client->request(
        'GET',
        'https://api.mch.weixin.qq.com/v3/pay/partner/transactions/id/1217752501201407033233368018?sp_mchid=1230000109&sub_mchid=1900000109', //请求URL
        [
            'headers' => [ 'Accept' => 'application/json']
        ]
    );
    $statusCode = $resp->getStatusCode();
    if ($statusCode == 200) { //处理成功
        echo "success,return body = " . $resp->getBody()->getContents()."\n";
    } else if ($statusCode == 204) { //处理成功,无返回Body
        echo "success";
    }
} catch (RequestException $e) {
    // 进行错误处理
    echo $e->getMessage()."\n";
    if ($e->hasResponse()) {
        echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
    }
    return;
}

func QueryOrder() {
       // 初始化客户端
    ctx := context.TODO()
    opts, err := SetUp()
    if err != nil {
        return
    }
    client, err := core.NewClient(ctx, opts...,)
    if err != nil{
        log.Printf("init client err:%s",err)
        return
    }
    //设置请求地址
  URL := "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/id/4200000985202103031441826014?sp_mchid=1900007XXX&sub_mchid=1900008XXX"
  // 发起请求
  response, err := client.Get(ctx, URL)
  if err != nil{
    log.Printf("client get err:%s",err)
    return
  }
  // 校验回包内容是否有逻辑错误
  err = core.CheckResponse(response)
  if err != nil{
    log.Printf("check response err:%s",err)
    return
  }
  // 读取回包信息
  body, err := ioutil.ReadAll(response.Body)
  if err != nil{
    log.Printf("read response body err:%s",err)
    return
  }
  fmt.Println(string(body))
}
    

注意:

  • 查询订单可通过微信支付订单号商户订单号两种方式查询,两种查询方式返回结果相同

更多参数、响应详情及错误码请参见 JSAPI / APP / H5 / Native / 小程序查询订单API接口文档

3.2.5. 【服务端】关闭订单

步骤说明:当商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口

示例代码


  public void CloseOrder() throws Exception {
     
    //请求URL
  HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/partner/transactions/out-trade-no/{out_trade_no}/close");
  // 请求body参数
  String reqdata = "{"
          + "\"sp_mchid\":\"1230000109\","
          + "\"sub_mchid\":\"1900000109\""
          + "}";
  StringEntity entity = new StringEntity(reqdata,"utf-8");
  entity.setContentType("application/json");
  httpPost.setEntity(entity);
  httpPost.setHeader("Accept", "application/json");

  //完成签名并执行请求
  CloseableHttpResponse response = httpClient.execute(httpPost);

  try {
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) { //处理成功
          System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
      } else if (statusCode == 204) { //处理成功,无返回Body
          System.out.println("success");
      } else {
          System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
          throw new IOException("request failed");
      }
  } finally {
      response.close();
  }
}
  
try {
    $resp = $client->request(
        'POST',
        'https://api.mch.weixin.qq.com/v3/pay/partner/transactions/out-trade-no/{out_trade_no}/close', //请求URL
        [
            // JSON请求体
            'json' => [
                "sp_mchid" => "1230000109", 
                "sub_mchid" => "1900000109",
            ],
            'headers' => [ 'Accept' => 'application/json' ]
        ]
    );
    $statusCode = $resp->getStatusCode();
    if ($statusCode == 200) { //处理成功
        echo "success,return body = " . $resp->getBody()->getContents()."\n";
    } else if ($statusCode == 204) { //处理成功,无返回Body
        echo "success";
    }
} catch (RequestException $e) {
    // 进行错误处理
    echo $e->getMessage()."\n";
    if ($e->hasResponse()) {
        echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
    }
    return;
}
  

func CloseOrder() {
       // 初始化客户端
    ctx := context.TODO()
    opts, err := SetUp()
    if err != nil {
        return
    }
    client, err := core.NewClient(ctx, opts...,)
    if err != nil{
        log.Printf("init client err:%s",err)
        return
    }
    //设置请求地址
  URL := "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/out-trade-no/1217752501201407033233368018/close"
  //设置请求信息,此处也可以使用结构体来进行请求
  mapInfo := map[string]interface{}{
    "sp_mchid": "1900007XXX",
    "sub_mchid": "1900008XXX",
  }

  // 发起请求
  response, err := client.Post(ctx, URL, mapInfo)
  if err != nil{
    log.Printf("client post err:%s",err)
    return
  }
  // 校验回包内容是否有逻辑错误
  err = core.CheckResponse(response)
  if err != nil{
    log.Printf("check response err:%s",err)
    return
  }
  // 读取回包信息
  body, err := ioutil.ReadAll(response.Body)
  if err != nil{
    log.Printf("read response body err:%s",err)
    return
  }
  fmt.Println(string(body))
}
    

注意

  • 关单没有时间限制,建议在订单生成后间隔几分钟(最短5分钟)再调用关单接口,避免出现订单状态同步不及时导致关单失败。
  • 已支付成功的订单不能关闭

更多参数、响应详情及错误码请参见 JSAPI / APP / H5 / Native / 小程序接口文档

3.2.6. 【服务端】申请交易账单

步骤说明:微信支付按天提供交易账单文件,商户可以通过该接口获取账单文件的下载地址

示例代码


public void TradeBill() throws Exception {
   
  //请求URL
  HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/bill/tradebill?bill_date=2019-06-11&sub_mchid=1900000001&bill_type=ALL");
  httpGet.setHeader("Accept", "application/json");

  //完成签名并执行请求
  CloseableHttpResponse response = httpClient.execute(httpGet);

  try {
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) { //处理成功
          System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
      } else if (statusCode == 204) { //处理成功,无返回Body
          System.out.println("success");
      } else {
          System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
          throw new IOException("request failed");
      }
  } finally {
      response.close();
  }
}
try {
    $resp = $client->request(
        'GET',
        'https://api.mch.weixin.qq.com/v3/bill/tradebill?bill_date=2019-06-11&sub_mchid=1900000001&bill_type=ALL', //请求URL
        [
            'headers' => [ 'Accept' => 'application/json']
        ]
    );
    $statusCode = $resp->getStatusCode();
    if ($statusCode == 200) { //处理成功
        echo "success,return body = " . $resp->getBody()->getContents()."\n";
    } else if ($statusCode == 204) { //处理成功,无返回Body
        echo "success";
    }
} catch (RequestException $e) {
    // 进行错误处理
    echo $e->getMessage()."\n";
    if ($e->hasResponse()) {
        echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
    }
    return;
}
      

func TradeBill() {
       // 初始化客户端
    ctx := context.TODO()
    opts, err := SetUp()
    if err != nil {
        return
    }
    client, err := core.NewClient(ctx, opts...,)
    if err != nil{
        log.Printf("init client err:%s",err)
        return
    }
    //设置请求地址
  URL := "https://api.mch.weixin.qq.com/v3/bill/tradebill?bill_date=2019-06-11&sub_mchid=1900000001&bill_type=ALL"
  // 发起请求
  response, err := client.Get(ctx, URL)
  if err != nil{
    log.Printf("client get err:%s",err)
    return
  }
  // 校验回包内容是否有逻辑错误
  err = core.CheckResponse(response)
  if err != nil{
    log.Printf("check response err:%s",err)
    return
  }
  // 读取回包信息
  body, err := ioutil.ReadAll(response.Body)
  if err != nil{
    log.Printf("read response body err:%s",err)
    return
  }
  fmt.Println(string(body))
}
    

注意

更多参数、响应详情及错误码请参见 JSAPI / APP / H5 / Native / 小程序接口文档

3.2.7. 【服务端】下载账单

步骤说明:通过申请交易账单接口获取到账单下载地址(download_url)后,再通过该接口获取到对应的账单文件,文件内包含交易相关的金额、时间、营销等信息,供商户核对订单、退款、银行到账等情况

示例代码


public void DownloadUrl(String download_url) throws Exception{
  PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
   
  //初始化httpClient
  //该接口无需进行签名验证、通过withValidator((response) -> true)实现
  httpClient =  WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, merchantPrivateKey).withValidator((response) -> true).build();
   
  //请求URL
  //账单文件的下载地址的有效时间为30s
  URIBuilder uriBuilder = new URIBuilder(download_url);
  HttpGet httpGet = new HttpGet(uriBuilder.build());
  httpGet.addHeader("Accept", "application/json");
   
  //执行请求
  CloseableHttpResponse response = httpClient.execute(httpGet);
  try {
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) {
          System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
      } else if (statusCode == 204) {
          System.out.println("success");
      } else {
          System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
          throw new IOException("request failed");
      }
  } finally {
      response.close();
  }
}
try {
    $resp = $client->request(
        'GET',
        'https://api.mch.weixin.qq.com/v3/billdownload/file?token=xx', //请求URL
        [
            'headers' => [ 'Accept' => 'application/json']
        ]
    );
    $statusCode = $resp->getStatusCode();
    if ($statusCode == 200) { //处理成功
        echo "success,return body = " . $resp->getReasonPhrase()."\n";
    } else if ($statusCode == 204) { //处理成功,无返回Body
        echo "success";
    }
} catch (RequestException $e) {
    // 进行错误处理
    echo $e->getMessage()."\n";
    if ($e->hasResponse()) {
        echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
    }
    return;
}

func CreateOrder() {
       // 初始化客户端
    ctx := context.TODO()
    opts, err := SetUp()
    if err != nil {
        return
    }
    client, err := core.NewClient(ctx, opts...,)
    if err != nil{
        log.Printf("init client err:%s",err)
        return
    }
    //设置请求地址
    URL := "https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx"  //申请账单接口获取到的download_url
  // 发起请求
  response, err := client.Get(ctx, URL)
  if err != nil{
    log.Printf("client get err:%s",err)
    return
  }
  // 校验回包内容是否有逻辑错误
  err = core.CheckResponse(response)
  if err != nil{
    log.Printf("check response err:%s",err)
    return
  }
  // 读取回包信息
  body, err := ioutil.ReadAll(response.Body)
  if err != nil{
    log.Printf("read response body err:%s",err)
    return
  }
  fmt.Println(string(body))
}
    

注意

• 账单文件的下载地址的有效时间为30s

• 强烈建议商户将实际账单文件的哈希值和之前从接口获取到的哈希值进行比对,以确认数据的完整性

更多参数、响应详情及错误码请参见 JSAPI / APP / H5 / Native / 小程序下载账单API接口文档

4. 常见问题

Q:微信APP支付,前端调起的时候返回errcode = -1该如何排查?

A:1. 查看APP下单参数返回是否正常,是否有正确的在调用SDK前获取了正确的prepay_id;

  •  2.查看调用SDK签名是否正确,请注意以下几点:
  • a) 参与签名的参数名大小写一定要与文档中保持一致;

    b) APP下单返回的签名和调用SDK使用的签名不是同一个,调用SDK需要单独根据SDK参数生成签名;

  • 3. 检查客户端调用sendReq(PayReq)对象赋值的正确性(必要时让商户提供数据),若通过异步获取到后台数据,比如data对象是通过异步请求得到的对象:request.appId = data.appid; 实际appid属性值为空;
  • 4. 检查对应的开发配置,包括iOS的appid配置,Android的包名及包签名设置。
Q:app调用“唤起支付api”返回:商户支付下单id非法

A:请确认唤起支付参数字段名是否与文档的一致。

Q:服务商模式下,调用APP下单接口,返回“特殊子商户未授权的产品权限”

A:APP支付需要进行单独的授权开通才可使用,请前往服务商平台子商户管理中找到对应的子商户授权服务商APP支付权限。

Q:调用APP下单接口,返回“sub_appid与sub_mch_id不匹配”

A:在调用APP下单接口前,需保证子商户号与子商户APP的appid存在绑定关系,请服务商前往服务商平台的子商户管理页面中操作绑定。



技术咨询

文档反馈