智慧商圈开发指引
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)
• 如想更详细的了解我们的接口规则,可查看我们的接口规则指引文档
3. 快速接入
3.1. 业务流程图
重点步骤说明:
智慧商圈接入前需先邮件申请接入权限,申请发放具体可查看智慧商圈接入流程,小程序插件可参考:小程序插件开发文档
步骤10 商圈支付结果通知(已开通积分功能的用户,在场内发生交易时,会将交易信息返回至开通时提交的回调地址)
步骤12 商圈积分同步(只有接入该接口,才会获取到退款信息)
步骤13 商圈退款成功通知(对已同步过积分的交易,监控30天内的退款情况,若发生退款,则会把相关退款通知发送给商圈商户,商户的回调地址同支付结果回调地址保持一致)
3.2. API接入(含示例代码)
文档展示了如何使用微信支付服务端 SDK 快速接入智慧商圈产品,完成与微信支付对接的部分。
注意:
- 文档中的代码示例是用来阐述 API 基本使用方法,代码中的示例参数需替换成商户自己账号及请求参数才能跑通。
- 以下接入步骤仅提供参考,请商户结合自身业务需求进行评估、修改。
3.2.1. 【客户端】商圈快速积分小程序插件
微信支付智慧商圈,需先接入商圈快速积分小程序插件。小程序插件可参考:小程序插件开发文档
3.2.2. 【服务端】接收商圈支付结果通知
步骤说明:当用户完成支付,微信会把相关支付结果将通过异步回调的方式通知商户,商户需要接收处理,并按文档规范返回应答。
注意:
- 支付结果通知是以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)
更多参数、响应详情及错误码请参见 商圈支付结果通知API接口文档
3.2.3. 【服务端】商圈积分同步
步骤说明:商圈服务商针对微信支付前序推送给商圈系统的顾客商圈内交易通知,告知微信支付系统该笔交易的积分情况
示例代码
public void SyncPoints() throws Exception{
//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/businesscircle/points/notify");
// 请求body参数
String reqdata = "{"
+ "\"transaction_id\":\"4200000533202000000000000000\","
+ "\"appid\":\"wx8828b70xxxxxxx8\","
+ "\"openid\":\"otPAN5xxxxxxxxrOEG6lUv_pzacc\","
+ "\"earn_points\":true,"
+ "\"increased_points\":100,"
+ "\"points_update_time\":\"2020-05-20T13:29:35.120+08:00\","
+ "\"total_points\":888888"
+ "}";
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/businesscircle/points/notify', //请求URL
[
// JSON请求体
'json' => [
"transaction_id" => "4200000533202000000000000000",
"appid" => "wx8828b70xxxxxxx8",
"openid" => "otPAN5xxxxxxxxrOEG6lUv_pzacc",
"earn_points" => true,
"increased_points" => 100,
"points_update_time" => "2020-05-20T13:29:35.120+08:00",
"total_points" => 888888,
],
'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 SyncPoints() {
// 初始化客户端
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/businesscircle/points/notify"
//设置请求信息,此处也可以使用结构体来进行请求
mapInfo := map[string]interface{}{
"transaction_id": "4200000533202000000000000000",
"appid": "wx8828b70xxxxxxx8",
"openid": "otPAN5xxxxxxxxrOEG6lUv_pzacc",
"earn_points": true,
"increased_points": 100,
"points_update_time": "2020-05-20T13:29:35.120+08:00",
"total_points": 888888,
}
// 发起请求
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))
}
重要入参说明:
• transaction_id:微信支付推送的商圈内交易通知里携带的微信订单号
• earn_points:用于标明此单是否获得积分,true为获得积分,false为未获得
• increased_points:顾客此笔交易新增的积分值
更多参数、响应详情及错误码请参见商圈积分同步接口文档
4. 常见问题
Q:一个商圈拥有多个小程序,是否可以在多个小程序中都嵌入“商圈快速积分插件”?
A:可以。 若一个商圈拥有多个小程序,且在多个小程序场景里都适合引导会员开通【智慧商圈支付即积分】能力,请在提交支付即积分申请时,提交对应的多个小程序的APPID,目前最多支持5个。
Q:是否必须使用微信支付会员卡能力,才能使用【智慧商圈支付即积分】能力?
A:建议使用微信支付会员卡能力,来提升会员开卡的体验,但是若未使用微信支付会员卡能力,一样可以使用【智慧商圈支付即积分】能力。
【智慧商圈支付即积分】能力的实现非常灵活,使用插件形式,嵌入商圈自有小程序中,由商圈自行进行会员身份判断,若判断是会员则可进入“商圈快速积分插件”,引导用户开通【智慧商圈支付即积分】能力。
Q:提交积分申请时提示“网络错误,请稍后重试”
A:该问题可通过商圈自行解决,登录商圈小程序后台,更新插件版本。
Q:提交积分申请时提示“270924805”
A:该问题可通过商圈自行解决:
1. 确认是服务商模式还是商圈直连模式(登录商户平台确认证书配置):
a)若是服务商模式,则需要为服务商商户号配置API-V3证书
b)若是商圈直连模式,则需要为商圈商户号配置API-V3证书
2. 按照接口文档进行API证书配置,积分信息回调接口文档, API证书配置
Q:用户未自动获取到积分(商圈未收到回调信息)
A:该问题可以通过商圈自检解决。
【用户插件页检查】
1. 用户通过小程序进入插件页面,查看是否有交易但未提交,若有,则证明提前未签到,直接点击提交即可获取积分(注:插件页只能展示当天的场内消费信息,通过交易单号可识别是否为当天交易)
【若用户插件页无交易-商圈可积分门店检查】
1. 该门店未添加进商圈内(可能原门店的商户号变更),自检方式:使用用户交易单号在小程序添加该门店,查看是否可添加,并且该门店已开启“支持积分”(若用户消费前,该门店未被圈入商圈,则该笔交易订单不会自动推送,用户可在商圈圈店后进入插件页手动推送)
2. 用户未在门店消费