开发指引

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)

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

2.2. 业务开发配置

商户需要按照要求填写excel表格中服务id申请和先享卡id申请,并提交给微信支付对应负责人。商户待提交的具体内容参见商户填写字段(服务入驻+先享卡)。请确认表格已提交并已配置成功。

3. 快速接入

3.1. 业务流程图

重点步骤说明:

步骤1 用户进入商户页面领取先享卡,商户先请求后台接口获取领卡关键参数“token”。

获取领卡关键参数的后台接口为:预受理领卡请求API

步骤2 获得token之后,商户系统引导用户跳转到微信小程序进行领卡

根据场景不同,选择以下几种方式中的一种实现跳转微信小程序:

步骤3 用户领卡成功后,商户系统马上会收到一条用户领卡通知消息用户领卡通知是通过用户领卡通知API接口通知到你的服务器的

如果你长时间没有收到通知,也可以通过查询先享卡订单API接口进行主动查询

步骤4 用户领卡后就会进行消费,每次消费,商户都需要将消费信息同步给微信

商户可以通过增加用户记录API接口将用户消费信息通过给微信

步骤5 先享卡到期核算、用户使用完服务或用户提前退出约定,商户都会收到相关的通知消息

服务过程中,如果商户有需要,都可以通过后台接口查询先享卡订单API主动查询订单状态

说明: 扣费状态变化通知只有在最终状态时才会回调,比如完成约定,提前退出约定,到期结算

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

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

注意:

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

步骤说明:商户在引导用户跳转先享卡领卡前,需要先请求预受理领卡请求接口,再根据返回数据引导用户跳转领卡。

示例代码

public void PrepareCard() throws Exception{

//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/discount-card/cards");
// 请求body参数
String reqdata = "{"
        + "\"out_card_code\":\"6e8369071cd942c0476613f9d1ce9ca3\","
        + "\"card_template_id\":\"87789b2f25177433bcbf407e8e471f95\","
        + "\"appid\":\"wxd678efh567hg6787\","
        + "\"notify_url\":\"https://api.test.com\""
        + "}";
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/discount-card/cards', //请求URL
    [
        // JSON请求体
        'json' => [
            "out_card_code" => "6e8369071cd942c0476613f9d1ce9ca3",
            "card_template_id" => "87789b2f25177433bcbf407e8e471f95",
            "appid" => "wxd678efh567hg6787",
            "notify_url" => "https://api.test.com",
        ],
        '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;
}

更多参数、响应详情及错误码请参见预受理领卡请求API接口文档

3.2.2. 【客户端】APP拉起先享卡小程序领卡API

通过预受理领卡请求API拿到拉起先享卡领卡页面的必传参数“预领卡请求token”,然后我们就可以通过前端方法来拉起先享卡领卡页面了。前端方法详见APP拉起先享卡小程序领卡

3.2.3. 【服务端】用户领卡通知API

步骤说明:当用户领取先享卡成功时,微信会把相关信息异步回调的方式通知商户,商户需要接收处理,并按文档规范返回应答

注意

  • 支付结果通知是以POST 方法访问商户设置的通知url,通知的数据以JSON 格式通过请求主体(BODY)传输。通知的数据包括了加密的支付结果详情
  • 加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature。商户应当验证签名,以确认请求来自微信,而不是其他的第三方。签名验证的算法请参考微信支付API v3签名方案
  • 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx
  • 商户成功接收到回调通知后应返回成功的http应答码为200或204
  • 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
  • 如果在所有通知频率(4小时)后没有收到微信侧回调,商户应调用查询先享卡订单接口确认订单状态。

更多参数、响应详情及错误码请参见 用户领卡通知API接口文档

3.2.4. 【服务端】查询先享卡订单API

步骤说明:商户长时间没有收到通知,可通过查询接口主动查询订单状态。

示例代码

public void QueryCard() throws Exception{

  //请求URL
  HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/discount-card/cards/233bcbf407e87789b8e471f251774f95");
  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/discount-card/cards/233bcbf407e87789b8e471f251774f95', //请求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;
  }

更多参数、响应详情及错误码请参见 查询先享卡订单API接口文档

3.2.5. 【服务端】增加用户记录API

步骤说明:每新增一条用户消费记录,商户都要将消费信息同步站给微信。

示例代码

public void AddUserRecords() throws Exception{

//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/discount-card/cards/{out_card_code}/add-user-records");
// 请求body参数
String reqdata = "{"
        + "\"card_template_id\":\"87789b2f25177433bcbf407e8e471f95\","
        + "\"reward_usage_records\": ["
        + "{"
        + "\"usage_count\":100,"
        + "\"amount\":1,"
        + "\"usage_type\":\"INCREASE\","
        + "\"usage_time\":\"2015-05-20T13:29:35.120+08:00\","
        + "\"reward_usage_serial_no\":\"578354\","
        + "\"description\":\"购买商品\","
        + "\"reward_id\":\"123456\","
        + "\"remark\":\"特价商品\""
        + "}"
        + "],"
        + "\"objective_completion_records\": ["
        + "{"
        + "\"completion_time\":\"2015-05-20T13:29:35.120+08:00\","
        + "\"objective_completion_serial_no\":\"578354545\","
        + "\"description\":\"购买商品/取消购买商品\","
        + "\"completion_count\":1,"
        + "\"remark\":\"特价商品\","
        + "\"completion_type\":\"INCREASE\","
        + "\"objective_id\":\"123456\""
        + "},"
        + "{"
        + "\"completion_time\":\"2015-05-20T13:29:35.120+08:00\","
        + "\"objective_completion_serial_no\":\"578354545\","
        + "\"description\":\"购买商品/取消购买商品\","
        + "\"completion_count\":1,"
        + "\"remark\":\"特价商品\","
        + "\"completion_type\":\"INCREASE\","
        + "\"objective_id\":\"123456\""
        + "}"
        + "]"
        + "}";
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/discount-card/cards/{out_card_code}/add-user-records', //请求URL
    [
        // JSON请求体
        'json' => [
            "card_template_id" => "87789b2f25177433bcbf407e8e471f95",
            "reward_usage_records" => [
                [
                    "usage_count" => 100,
                    "amount" => 1,
                    "usage_type" => "INCREASE",
                    "usage_time" => "2015-05-20T13:29:35.120+08:00",
                    "reward_usage_serial_no" => "578354",
                    "description" => "购买商品",
                    "reward_id" => "123456",
                    "remark" => "特价商品",
                ],
            ],
            "objective_completion_records" => [
                [
                    "completion_time" => "2015-05-20T13:29:35.120+08:00",
                    "objective_completion_serial_no" => "578354545",
                    "description" => "购买商品/取消购买商品",
                    "completion_count" => 1,
                    "remark" => "特价商品",
                    "completion_type" => "INCREASE",
                    "objective_id" => "123456",
                ],
                [
                    "completion_time" => "2015-05-20T13:29:35.120+08:00",
                    "objective_completion_serial_no" => "578354545",
                    "description" => "购买商品/取消购买商品",
                    "completion_count" => 1,
                    "remark" => "特价商品",
                    "completion_type" => "INCREASE",
                    "objective_id" => "123456",
                ],
            ]
        ],
        '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;
}

更多参数、响应详情及错误码请参见 增加用户记录API接口文档

3.2.6. 【服务端】扣费状态变化通知

步骤说明:用户完成所有卡服务后,微信会对用户进行扣款,并将扣款状态回调通知给商户

注意:

  • 支付结果通知是以POST 方法访问商户设置的通知url,通知的数据以JSON 格式通过请求主体(BODY)传输。通知的数据包括了加密的支付结果详情
  • 加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature。商户应当验证签名,以确认请求来自微信,而不是其他的第三方。签名验证的算法请参考微信支付API v3签名方案
  • 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx
  • 商户成功接收到回调通知后应返回成功的http应答码为200或204
  • 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
  • 如果在所有通知频率(4小时)后没有收到微信侧回调,商户应调用查询先享卡订单接口确认订单状态。

更多参数、响应详情及错误码请参见 扣费状态变化通知API接口文档

3.2.7. 【服务端】守约状态变化通知

步骤说明:用户主动提前结束卡服务,微信会该消息回调通知给商户

注意:

  • 支付结果通知是以POST 方法访问商户设置的通知url,通知的数据以JSON 格式通过请求主体(BODY)传输。通知的数据包括了加密的支付结果详情
  • 加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature。商户应当验证签名,以确认请求来自微信,而不是其他的第三方。签名验证的算法请参考微信支付API v3签名方案
  • 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx
  • 商户成功接收到回调通知后应返回成功的http应答码为200或204
  • 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
  • 如果在所有通知频率(4小时)后没有收到微信侧回调,商户应调用查询先享卡订单接口确认订单状态。

更多参数、响应详情及错误码请参见 守约状态变化通知API接口文档

4. 常见问题

Q:商户小程序调用【小程序拉起先享卡小程序领取先享卡】接口报错“未通过申请,当前服务未上线”?

A:1)原因:领卡页-联系微信侧运营配置微信号白名单

Q:商户小程序调用【小程序拉起先享卡小程序领取先享卡】接口报错“未能领取先享卡,先享卡未开放领取,敬请关注”

A:1)原因:先享卡未激活或活动未开始,联系微信侧运营激活先享卡

Q:用户领卡通知、守约状态变化通知、用户领卡通知回调收不到怎么解决?

A:1)没设置apiv3秘钥是不发送回调的,需要在商户平台设置APIv3密钥,详情参看文档指引APIv3密钥设置介绍

2)检查下商户是否使用了阿里云的智能网关之类的加速服务,导致一个公网ip可能对应多个域名,这种情况微信目前还不支持,商户需要切下网络"

3)商户自行检查回调地址是否正常

Q:商户调用更新先享卡订单时,先享卡目标ID(objective_id)和先享卡奖励ID(reward_id)怎么获取?

A:)由先享卡平台生成,商户可以通过领卡回调获取

Q:扣费状态通知回调API是在什么情况下才会触发回调?

A:)扣费状态变化通知只有在最终状态时才会回调,比如前完成约定,提前退出约定,到期结算



技术咨询

反馈有奖