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

JSAPI支付开发指引

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. 设置支付目录

支付授权目录说明

a、商户最后请求拉起微信支付收银台的页面地址我们称之为“支付目录”,例如:https://www.weixin.com/pay.php。

b、商户实际的支付目录必须和在微信支付商户平台设置的一致,否则会报错“当前页面的URL未注册:”

支付授权目录设置说明

登录【微信支付商户平台-->产品中心-->开发配置】,设置后一般5分钟内生效。


图1支付目录配置

支付授权目录校验规则说明

a、如果支付授权目录设置为顶级域名(例如:https://www.weixin.com/ ),那么只校验顶级域名,不校验后缀;

b、如果支付授权目录设置为多级目录,就会进行全匹配,例如设置支付授权目录为https://www.weixin.com/abc/123/,则实际请求页面目录不能为https://www.weixin.com/abc/,也不能为https://www.weixin.com/abc/123/pay/,必须为https://www.weixin.com/abc/123/


图1 支付目录配置


2.2.2. 设置授权域名

授权域名说明:开发JSAPI支付时,在JSAPI下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。具体配置页如图2所示

授权域名设置说明:登录【微信公众平台-->公众号设置-->功能设置】


图2 微信网页授权域名设置

3. 快速接入

3.1. 业务流程图

重点步骤说明:

步骤3 用户下单发起支付,商户可通过JSAPI下单创建支付订单。

步骤8 商户可在微信浏览器内通过JSAPI调起支付API调起微信支付,发起支付请求。

步骤15 用户支付成功后,商户可接收到微信支付支付结果通知支付结果通知API

步骤20 商户在没有接收到微信支付结果通知的情况下需要主动调用查询订单API查询支付结果。

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

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

注意

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

步骤说明:用户通过商户下发的模板消息或扫描二维码在微信内进入商户网页,当用户选择相关商户购买时,商户系统先调用该接口在微信支付服务后台生成预支付交易单。

示例代码


public void CreateOrder() throws Exception{

  //请求URL
  HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi");
  // 请求body参数
  String reqdata = "{"
          + "\"sp_appid\":\"wx8888888888888888\","
          + "\"sp_mchid\":\"1230000109\","
          + "\"sub_mchid\":\"1900000109\","
          + "\"sub_appid\":\"wxd678efh567hg6999\","
          + "\"description\":\"Image形象店-深圳腾大-QQ公仔\","
          + "\"out_trade_no\":\"1217752501201407033233368018\","
          + "\"time_expire\":\"2018-06-08T10:34:56+08:00\","
          + "\"attach\":\"自定义数据说明\","
          + "\"notify_url\":\"https://www.weixin.qq.com/wxpay/pay.php\","
          + "\"goods_tag\":\"WXG\","
          + "\"settle_info\": {"
          + "\"profit_sharing\":false,"
          + "},"
          + "\"amount\": {"
          + "\"total\":100,"
          + "\"currency\":\"CNY\""
          + "},"
          + "\"payer\": {"
          + "\"sp_openid\":\"oUpF8uMuAJO_M2pxb1Q9zNjWeS6o\","
          + "\"sub_openid\":\"oUpF8uMuAJO_M2pxb1Q9zNjWeS6o\""
          + "},"
          + "\"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/jsapi', //请求URL
        [
            // JSON请求体
            'json' => [
                "sp_appid" => "wx8888888888888888", 
                "sp_mchid" => "1230000109", 
                "sub_mchid" => "1900000109", 
                "sub_appid" => "wxd678efh567hg6999", 
                "description" => "Image形象店-深圳腾大-QQ公仔", 
                "out_trade_no" => "1217752501201407033233368018", 
                "time_expire" => "2018-06-08T10:34:56+08:00", 
                "attach" => "自定义数据说明", 
                "notify_url" => "https://www.weixin.qq.com/wxpay/pay.php", 
                "goods_tag" => "WXG", 
                "settle_info" => [
                    "profit_sharing" => false, 
                ],
                "amount" => [
                    "total" => 100, 
                    "currency" => "CNY", 
                ],
                "payer" => [
                    "sp_openid" => "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o", 
                    "sub_openid" => "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o", 
                ],
                "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/jsapi"
  //设置请求信息,此处也可以使用结构体来进行请求
  mapInfo := map[string]interface{}{
    "sp_mchid": "1900007XXX",
    "sub_mchid": "1900008XXX",
    "out_trade_no": "1217752501201407033233368318",
    "sp_appid": "wxdace645e0bc2cXXX",
    "sub_appid": "wxdace645e0bc2cXXX",
    "description": "Image形象店-深圳腾大-QQ公仔",
    "notify_url": "https://weixin.qq.com/",
    "amount": map[string]interface{}{
      "total": 1,
      "currency": "CNY",
    },
    "payer": map[string]interface{}{
      "sp_openid": "o4GgauInH_RCEdvrrNGrntXDuXXX",
    },
  }

  // 发起请求
  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:订单总金额,单位为分

openid:openid是微信用户在appid下的唯一用户标识(appid不同,则获取到的openid就不同),可用于永久标记一个用户。openid获取方式请参考以下文档小程序获取openid公众号获取openidAPP获取openid

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

3.2.2.【客户端】JSAPI调起支付

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

注意

• WeixinJSBridge内置对象在其他浏览器中无效

• 此API需要将请求参数进行签名(参与签名的参数为:appId、timeStamp、nonceStr、package,参数区分大小写)

示例代码


  function onBridgeReady() {
      WeixinJSBridge.invoke('getBrandWCPayRequest', {
          "appId": "wx2421b1c4370ecxxx",       //公众号ID,由商户传入    
          "timeStamp": "1395712654",     //时间戳,自1970年以来的秒数    
          "nonceStr": "e61463f8efa94090b1f366cccfbbb444",     //随机串    
          "package": "prepay_id=wx21201855730335ac86f8c43d1889123400",
          "signType": "RSA",     //微信签名方式:    
          "paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==" //微信签名
      },
      function(res) {
          if (res.err_msg == "get_brand_wcpay_request:ok") {
              // 使用以上方式判断前端返回,微信团队郑重提示:
              //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
          }
      });
  }
  if (typeof WeixinJSBridge == "undefined") {
      if (document.addEventListener) {
          document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
      } else if (document.attachEvent) {
          document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
          document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
      }
  } else {
      onBridgeReady();
  }	  
  

重要入参说明

package:JSAPI下单接口返回的prepay_id参数值,提交格式如:prepay_id=***

signType:签名类型,该接口V3版本仅支持RSA

paySign:签名

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

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:获取OPENID接口报“此公众号并没有这些scope的权限,错误码10005”,如下图所示

A:请按以下步骤进行排查:

1. 建议检查一下公众号的功能。比如是不是在订阅号/未认证的公众号里面尝试调用认证服务号的功能

2. 确认APPID是否认证过期或者APPID填写错误

3. 请尝试使用snsapi_userinfo的授权登录方式

Q:JSAPI调起支付接口报“商家暂时没有此类交易权限,请联系商家客服”

A:请按以下步骤进行排查:

1. 请检查你的下单接口是否指定了支付用户的身份,该功能需单独开通指定身份支付权限方可使用

2. 请确认你使用的商户号是否有jsapi支付的权限,可登录商户平台-产品中心查看

Q:JSAPI调起支付接口报“当前页面的URL未注册”

A:请检查下单接口中使用的商户号是否在商户平台配置了对应的支付目录,可参考“1.2.1设置支付目录”章节说明

Q:获取OPENID接口报“redirect_url域名与后台配置不一致,错误码:10003”

A:请按以下步骤进行排查:

1. 检查下单接口传的appid与获取openid接口的appid是否同一个(需一致)

2. 检查appid对应的公众号后台,是否配置的授权域名和获取openid的域名一致。授权域名配置路径:【公众平台->设置->公众号设置->功能设置–>网页授权域名】

Q:JSAPI调起支付接口报“该商户暂不支持通过外部拉起微信完成支付”

A:JSAPI支付只能从微信浏览器内发起支付请求



技术咨询

文档反馈