开发指引

1. 接口规则

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

2. 开发准备

2.1. 搭建和配置开发环境

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

测试步骤

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

    • wechatpay-apache-httpclient,适用于使用Apache HttpClient处理HTTP的Java开发者

    • wechatpay-guzzle-middleware,适用于PHP开发者

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]);

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

说明:

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

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

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

    1.签名生成

    2.签名验证

    3.敏感信息加解密

    4.merchantPrivateKey(私钥)

    5.wechatpayCertificates(平台证书)

    6.APIV3Key(V3 key)

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

3. 快速接入

3.1. 业务流程图

重点步骤说明:

步骤2用户确认支付后,商户调用微信支付统一下单API生成预支付交易以获取支付二维码链接code_url;

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

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

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

步骤4: 商户根据返回的code_url生成二维码供用户扫描,有关二维码的规则请参考2.2.2部分的说明

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

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

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


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

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

注意:

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

步骤说明:用户在商户PC网站内完成商品选择后进入支付页面,商户需要通过后端请求该统一下单API来获取支付二维码链接code_url。

示例代码

public void CreateOrder() throws Exception{
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
  // 请求body参数
  String reqdata = "{"
          + "\"time_expire\":\"2018-06-08T10:34:56+08:00\","
          + "\"amount\": {"
          + "\"total\":100,"
          + "\"currency\":\"CNY\""
          + "},"
          + "\"mchid\":\"1230000109\","
          + "\"description\":\"Image形象店-深圳腾大-QQ公仔\","
          + "\"notify_url\":\" https://www.weixin.qq.com/wxpay/pay.php\","
          + "\"out_trade_no\":\"1217752501201407033233368018\","
          + "\"goods_tag\":\"WXG\","
          + "\"appid\":\"wxd678efh567hg6787\","
          + "\"attach\":\"自定义数据说明\","
          + "\"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);
  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/transactions/native', //请求URL
    [
        // JSON请求体
        'json' => [
            "time_expire" => "2018-06-08T10:34:56+08:00", 
            "amount" => [
                "total" => 100, 
                "currency" => "CNY", 
            ],
            "mchid" => "1230000109", 
            "description" => "Image形象店-深圳腾大-QQ公仔", 
            "notify_url" => " https://www.weixin.qq.com/wxpay/pay.php", 
            "out_trade_no" => "1217752501201407033233368018", 
            "goods_tag" => "WXG", 
            "appid" => "wxd678efh567hg6787", 
            "attach" => "自定义数据说明", 
            "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->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;

}

重要参数说明

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

• description:商品描述

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

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

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

3.2.2. 【客户端】生成支付二维码

步骤说明:通过Native统一下单API成功获取支付二维码链接(code_url)后,需要在前端(PC网页或POS机具)生成二维码供用户扫描支付。

注意
  • code_url对应链接格式:weixin://weixin://pay.weixin.qq.com/bizpayurl/up?pr=NwY5Mz9&groupid=00。请商户调用第三方库将code_url生成二维码图片。该模式链接较短,生成的二维码打印到结账小票上的识别率较高。

    例如,将weixin://weixin://pay.weixin.qq.com/bizpayurl/up?pr=NwY5Mz9&groupid=00 生成二维码建下图

  • 更多二维码的相关背景知识可参考

    https://www.qrcode.com/zh/index.html

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
  URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/id/4200000745202011093730578574");
  uriBuilder.setParameter("mchid", mchId);
 
  //完成签名并执行请求
  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/pay/transactions/id/1217752501201407033233368018?mchid=1230000109', //请求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;
}

注意:

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

更多参数、响应详情及错误码请参见 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/transactions/out-trade-no/sdkphp12345678920201028112429/close");
    //请求body参数
    String reqdata ="{\"mchid\": \""+mchId+"\"}";
     
    StringEntity entity = new StringEntity(reqdata);
    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) {
            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/transactions/out-trade-no/{out_trade_no}/close', //请求URL
        [
            // JSON请求体
            'json' => [
                "mchid " => "1230000109",
            ],
            '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;
  }
  

注意

  • 订单生成后不能马上调用关单接口,最短调用时间间隔为5分钟
  • 已支付成功的订单不能关闭

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

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

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

示例代码


public void TradeBill() throws Exception {
   
  //请求URL
  URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/bill/tradebill");
  uriBuilder.setParameter("bill_date", "2020-11-09");
  uriBuilder.setParameter("bill_type", "ALL");
 
  //完成签名并执行请求
  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/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->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;
}
      

注意

更多参数、响应详情及错误码请参见 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;
}

注意

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

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

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

4. 常见问题

Q:微信native支付生成的二维码有有效期吗?

一个二维码的有效期是根据统一下单接口返回的code_url决定,code_url的有效期是2小时。

Q:native支付扫码后提示:支付失败,该商户暂不支持通过长按识别二维码完成支付

A:微信支付已经不支持通过长按识别二维码的方式或通过相册识别二维码的方式完成支付。



技术咨询

反馈有奖