智慧商圈開發(fā)指引
1. 接口規(guī)則
為了在保證支付安全的前提下,帶給商戶簡單、一致且易用的開發(fā)體驗(yàn),我們推出了全新的微信支付APIv3接口。該版本API的具體規(guī)則請(qǐng)參考“APIv3接口規(guī)則”
備注:當(dāng)前接口用于微信國內(nèi)錢包
2. 開發(fā)準(zhǔn)備
2.1. 搭建和配置開發(fā)環(huán)境
為了幫助開發(fā)者調(diào)用開放接口,我們提供了JAVA、PHP、GO三種語言版本的開發(fā)庫,封裝了簽名生成、簽名驗(yàn)證、敏感信息加/解密、媒體文件上傳等基礎(chǔ)功能(更多語言版本的開發(fā)庫將在近期陸續(xù)提供)
測試步驟:
1、根據(jù)自身開發(fā)語言,選擇對(duì)應(yīng)的開發(fā)庫并構(gòu)建項(xiàng)目,具體配置請(qǐng)參考下面鏈接的詳細(xì)說明:
? wechatpay-java(推薦)wechatpay-apache-httpclient,適用于Java開發(fā)者。
? wechatpay-php(推薦)、wechatpay-guzzle-middleware,適用于PHP開發(fā)者
注:當(dāng)前開發(fā)指引接口PHP示例代碼采用wechatpay-guzzle-middleware版本
? wechatpay-go,適用于Go開發(fā)者
更多資源可前往微信支付開發(fā)者社區(qū)搜索查看
2、創(chuàng)建加載商戶私鑰、加載平臺(tái)證書、初始化httpClient的通用方法
@Before
public void setup() throws IOException {
// 加載商戶私鑰(privateKey:私鑰字符串)
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
// 加載平臺(tái)證書(mchId:商戶號(hào),mchSerialNo:商戶證書序列號(hào),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;
// 商戶相關(guān)配置,
$merchantId = '1000100'; // 商戶號(hào)
$merchantSerialNumber = 'XXXXXXXXXX'; // 商戶API證書序列號(hào)
$merchantPrivateKey = PemUtil::loadPrivateKey('./path/to/mch/private/key.pem'); // 商戶私鑰文件路徑
// 微信支付平臺(tái)配置
$wechatpayCertificate = PemUtil::loadCertificate('./path/to/wechatpay/cert.pem'); // 微信支付平臺(tái)證書文件路徑
// 構(gòu)造一個(gè)WechatPayMiddleware
$wechatpayMiddleware = WechatPayMiddleware::builder()
->withMerchant($merchantId, $merchantSerialNumber, $merchantPrivateKey) // 傳入商戶相關(guān)配置
->withWechatPay([ $wechatpayCertificate ]) // 可傳入多個(gè)微信支付平臺(tái)證書,參數(shù)類型為array
->build();
// 將WechatPayMiddleware添加到Guzzle的HandlerStack中
$stack = GuzzleHttp\HandlerStack::create();
$stack->push($wechatpayMiddleware, 'wechatpay');
// 創(chuàng)建Guzzle HTTP Client時(shí),將HandlerStack傳入,接下來,正常使用Guzzle發(fā)起API請(qǐng)求,WechatPayMiddleware會(huì)自動(dòng)地處理簽名和驗(yàn)簽
$client = new GuzzleHttp\Client(['handler' => $stack]);
/*
Package core 微信支付api v3 go http-client 基礎(chǔ)庫,你可以使用它來創(chuàng)建一個(gè)client,并向微信支付發(fā)送http請(qǐng)求
只需要你在初始化客戶端的時(shí)候,傳遞credential以及validator
credential用來生成http header中的authorization信息
validator則用來校驗(yàn)回包是否被篡改
如果http請(qǐng)求返回的err為nil,一般response.Body 都不為空,你可以嘗試對(duì)其進(jìn)行序列化
請(qǐng)注意及時(shí)關(guān)閉response.Body
注意:使用微信支付apiv3 go庫需要引入相關(guān)的包,該示例代碼必須引入的包名有以下信息
"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) {
//商戶號(hào)
mchID := ""
//商戶證書序列號(hào)
mchCertSerialNumber := ""
//商戶私鑰文件路徑
privateKeyPath := ""
//平臺(tái)證書文件路徑
wechatCertificatePath := ""
// 加載商戶私鑰
privateKey, err := utils.LoadPrivateKeyWithPath(privateKeyPath)
if err != nil {
log.Printf("load private err:%s", err.Error())
return nil, err
}
// 加載微信支付平臺(tái)證書
wechatPayCertificate, err := utils.LoadCertificateWithPath(wechatCertificatePath)
if err != nil {
log.Printf("load certificate err:%s",err)
return nil, err
}
//設(shè)置header頭中authorization信息
opts := []option.ClientOption{
option.WithMerchant(mchID, mchCertSerialNumber, privateKey), // 設(shè)置商戶相關(guān)配置
option.WithWechatPay([]*x509.Certificate{wechatPayCertificate}), // 設(shè)置微信支付平臺(tái)證書,用于校驗(yàn)回包信息用
}
return opts, nil
}
3、基于接口的示例代碼,替換請(qǐng)求參數(shù)后可發(fā)起測試
說明:
? 上面的開發(fā)庫為微信支付官方開發(fā)庫,其它沒有審核或者控制下的第三方工具和庫,微信支付不保證它們的安全性和可靠性
通過包管理工具引入SDK后,可根據(jù)下面每個(gè)接口的示例代碼替換相關(guān)參數(shù)后進(jìn)行快速測試
? 開發(fā)者如果想詳細(xì)了解簽名生成、簽名驗(yàn)證、敏感信息加/解密、媒體文件上傳等常用方法的具體代碼實(shí)現(xiàn),可閱讀下面的詳細(xì)說明:
1.簽名生成
2.簽名驗(yàn)證
3.敏感信息加解密
4.merchantPrivateKey(私鑰)
5.wechatpayCertificates(平臺(tái)證書)
6.APIV3Key(V3 key)
? 如想更詳細(xì)的了解我們的接口規(guī)則,可查看我們的接口規(guī)則指引文檔
3. 快速接入
3.1. 業(yè)務(wù)流程圖
重點(diǎn)步驟說明:
智慧商圈接入前需先郵件申請(qǐng)接入權(quán)限,申請(qǐng)發(fā)放具體可查看智慧商圈接入流程,小程序插件可參考:小程序插件開發(fā)文檔
步驟10 商圈支付結(jié)果通知(已開通積分功能的用戶,在場內(nèi)發(fā)生交易時(shí),會(huì)將交易信息返回至開通時(shí)提交的回調(diào)地址)
步驟12 商圈積分同步(只有接入該接口,才會(huì)獲取到退款信息)
步驟13 商圈退款成功通知(對(duì)已同步過積分的交易,監(jiān)控30天內(nèi)的退款情況,若發(fā)生退款,則會(huì)把相關(guān)退款通知發(fā)送給商圈商戶,商戶的回調(diào)地址同支付結(jié)果回調(diào)地址保持一致)
3.2. API接入(含示例代碼)
文檔展示了如何使用微信支付服務(wù)端 SDK 快速接入智慧商圈產(chǎn)品,完成與微信支付對(duì)接的部分。
注意:
- 文檔中的代碼示例是用來闡述 API 基本使用方法,代碼中的示例參數(shù)需替換成商戶自己賬號(hào)及請(qǐng)求參數(shù)才能跑通。
- 以下接入步驟僅提供參考,請(qǐng)商戶結(jié)合自身業(yè)務(wù)需求進(jìn)行評(píng)估、修改。
3.2.1. 【客戶端】商圈快速積分小程序插件
微信支付智慧商圈,需先接入商圈快速積分小程序插件。小程序插件可參考:小程序插件開發(fā)文檔
3.2.2. 【服務(wù)端】接收商圈支付結(jié)果通知
步驟說明:當(dāng)用戶完成支付,微信會(huì)把相關(guān)支付結(jié)果將通過異步回調(diào)的方式通知商戶,商戶需要接收處理,并按文檔規(guī)范返回應(yīng)答。
注意:
- 支付結(jié)果通知是以POST 方法訪問商戶設(shè)置的通知url,通知的數(shù)據(jù)以JSON 格式通過請(qǐng)求主體(BODY)傳輸。通知的數(shù)據(jù)包括了加密的支付結(jié)果詳情
- 加密不能保證通知請(qǐng)求來自微信。微信會(huì)對(duì)發(fā)送給商戶的通知進(jìn)行簽名,并將簽名值放在通知的HTTP頭Wechatpay-Signature。商戶應(yīng)當(dāng)驗(yàn)證簽名,以確認(rèn)請(qǐng)求來自微信,而不是其他的第三方。簽名驗(yàn)證的算法請(qǐng)參考
《微信支付API v3簽名驗(yàn)證》。
- 支付通知http應(yīng)答碼為200或204才會(huì)當(dāng)作正常接收,當(dāng)回調(diào)處理異常時(shí),應(yīng)答的HTTP狀態(tài)碼應(yīng)為500,或者4xx
- 商戶成功接收到回調(diào)通知后應(yīng)返回成功的http應(yīng)答碼為200或204
- 同樣的通知可能會(huì)多次發(fā)送給商戶系統(tǒng)。商戶系統(tǒng)必須能夠正確處理重復(fù)的通知。 推薦的做法是,當(dāng)商戶系統(tǒng)收到通知進(jìn)行處理時(shí),先檢查對(duì)應(yīng)業(yè)務(wù)數(shù)據(jù)的狀態(tài),并判斷該通知是否已經(jīng)處理。如果未處理,則再進(jìn)行處理;如果已處理,則直接返回結(jié)果成功。在對(duì)業(yè)務(wù)數(shù)據(jù)進(jìn)行狀態(tài)檢查和處理之前,要采用數(shù)據(jù)鎖進(jìn)行并發(fā)控制,以避免函數(shù)重入造成的數(shù)據(jù)混亂
- 對(duì)后臺(tái)通知交互時(shí),如果微信收到商戶的應(yīng)答不符合規(guī)范或超時(shí),微信認(rèn)為通知失敗,微信會(huì)通過一定的策略定期重新發(fā)起通知,盡可能提高通知的成功率,但微信不保證通知最終能成功。(通知頻率為15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 總計(jì) 24h4m)
更多參數(shù)、響應(yīng)詳情及錯(cuò)誤碼請(qǐng)參見 商圈支付結(jié)果通知API接口文檔
3.2.3. 【服務(wù)端】商圈積分同步
步驟說明:商圈服務(wù)商針對(duì)微信支付前序推送給商圈系統(tǒng)的顧客商圈內(nèi)交易通知,告知微信支付系統(tǒng)該筆交易的積分情況
示例代碼
public void SyncPoints() throws Exception{
//請(qǐng)求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/businesscircle/points/notify");
// 請(qǐng)求body參數(shù)
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");
//完成簽名并執(zhí)行請(qǐng)求
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', //請(qǐng)求URL
[
// JSON請(qǐng)求體
'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) {
// 進(jìn)行錯(cuò)誤處理
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
}
//設(shè)置請(qǐng)求地址
URL := "https://api.mch.weixin.qq.com/v3/businesscircle/points/notify"
//設(shè)置請(qǐng)求信息,此處也可以使用結(jié)構(gòu)體來進(jìn)行請(qǐng)求
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,
}
// 發(fā)起請(qǐng)求
response, err := client.Post(ctx, URL, mapInfo)
if err != nil{
log.Printf("client post err:%s",err)
return
}
// 校驗(yàn)回包內(nèi)容是否有邏輯錯(cuò)誤
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))
}
重要入?yún)⒄f明:
? transaction_id:微信支付推送的商圈內(nèi)交易通知里攜帶的微信訂單號(hào)
? earn_points:用于標(biāo)明此單是否獲得積分,true為獲得積分,false為未獲得
? increased_points:顧客此筆交易新增的積分值
更多參數(shù)、響應(yīng)詳情及錯(cuò)誤碼請(qǐng)參見商圈積分同步接口文檔
4. 常見問題
Q:一個(gè)商圈擁有多個(gè)小程序,是否可以在多個(gè)小程序中都嵌入“商圈快速積分插件”?
A:可以。 若一個(gè)商圈擁有多個(gè)小程序,且在多個(gè)小程序場景里都適合引導(dǎo)會(huì)員開通【智慧商圈支付即積分】能力,請(qǐng)?jiān)谔峤恢Ц都捶e分申請(qǐng)時(shí),提交對(duì)應(yīng)的多個(gè)小程序的APPID,目前最多支持5個(gè)。
Q:是否必須使用微信支付會(huì)員卡能力,才能使用【智慧商圈支付即積分】能力?
A:建議使用微信支付會(huì)員卡能力,來提升會(huì)員開卡的體驗(yàn),但是若未使用微信支付會(huì)員卡能力,一樣可以使用【智慧商圈支付即積分】能力。
【智慧商圈支付即積分】能力的實(shí)現(xiàn)非常靈活,使用插件形式,嵌入商圈自有小程序中,由商圈自行進(jìn)行會(huì)員身份判斷,若判斷是會(huì)員則可進(jìn)入“商圈快速積分插件”,引導(dǎo)用戶開通【智慧商圈支付即積分】能力。
Q:提交積分申請(qǐng)時(shí)提示“網(wǎng)絡(luò)錯(cuò)誤,請(qǐng)稍后重試”
A:該問題可通過商圈自行解決,登錄商圈小程序后臺(tái),更新插件版本。
Q:提交積分申請(qǐng)時(shí)提示“270924805”
A:該問題可通過商圈自行解決:
1. 確認(rèn)是服務(wù)商模式還是商圈直連模式(登錄商戶平臺(tái)確認(rèn)證書配置):
a)若是服務(wù)商模式,則需要為服務(wù)商商戶號(hào)配置API-V3證書
b)若是商圈直連模式,則需要為商圈商戶號(hào)配置API-V3證書
2. 按照接口文檔進(jìn)行API證書配置,積分信息回調(diào)接口文檔, API證書配置
Q:用戶未自動(dòng)獲取到積分(商圈未收到回調(diào)信息)
A:該問題可以通過商圈自檢解決。
【用戶插件頁檢查】
1. 用戶通過小程序進(jìn)入插件頁面,查看是否有交易但未提交,若有,則證明提前未簽到,直接點(diǎn)擊提交即可獲取積分(注:插件頁只能展示當(dāng)天的場內(nèi)消費(fèi)信息,通過交易單號(hào)可識(shí)別是否為當(dāng)天交易)
【若用戶插件頁無交易-商圈可積分門店檢查】
1. 該門店未添加進(jìn)商圈內(nèi)(可能原門店的商戶號(hào)變更),自檢方式:使用用戶交易單號(hào)在小程序添加該門店,查看是否可添加,并且該門店已開啟“支持積分”(若用戶消費(fèi)前,該門店未被圈入商圈,則該筆交易訂單不會(huì)自動(dòng)推送,用戶可在商圈圈店后進(jìn)入插件頁手動(dòng)推送)
2. 用戶未在門店消費(fèi)