小程序 微信支付官方文檔
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_11&index=2
https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_0.shtml
前提
在進行對接微信支付之前,我們首先需要將以下幾點準備好:
- 申請APPID
- 申請商戶號
- 小程序開通微信支付,綁定已經(jīng)申請好的商戶號。登錄小程序后臺(mp.weixin.qq.com)。點擊左側(cè)導(dǎo)航欄的微信支付,在頁面中進行開通。(開通申請要求小程序已發(fā)布上線)
注意事項
- appid必須為最后拉起收銀臺的小程序appid;
- mch_id為和appid成對綁定的支付商戶號,收款資金會進入該商戶號;
- trade_type請?zhí)顚慗SAPI;
- openid為appid對應(yīng)的用戶標識,即使用wx.login接口獲得的openid。
本文主要記錄后端步驟,前端步驟無非就是獲取后端數(shù)據(jù)然后調(diào)用提供的API進行支付,大家可自行查看官方文檔。
1. 整體流程
小程序支付的交互圖如下:清晰圖片可以到官網(wǎng)查看.
商戶系統(tǒng)和微信支付系統(tǒng)主要交互:
1、小程序內(nèi)調(diào)用登錄接口,獲取到用戶的openid,api參見公共api【小程序登錄API】
2、商戶server調(diào)用支付統(tǒng)一下單,api參見公共api【統(tǒng)一下單API】
3、商戶server調(diào)用再次簽名,api參見公共api【再次簽名】
4、商戶server接收支付通知,api參見公共api【支付結(jié)果通知API】
5、商戶server查詢支付結(jié)果,如未收到支付通知的情況,商戶后臺系統(tǒng)可調(diào)用【查詢訂單API】 (查單實現(xiàn)可參考:支付回調(diào)和查單實現(xiàn)指引)
2. 后端項目搭建
大致了解流程之后,我們就可以進行代碼的編寫了,首先大家自行創(chuàng)建一個Spring Boot項目即可。這里就不演示了,大家自行根據(jù)自己的需求搭建即可,需要注意的是我們需要添加微信支付的sdk以及相關(guān)依賴。
<!--微信支付請求-->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.13</version>
</dependency>
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.10</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-avro</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
3. 獲取open_id
我們在小程序端通過login方法獲取code,然后調(diào)用我們編寫的接口請求微信服務(wù)端獲取open_id。
該步驟具體可以查看 微信小程序?qū)游⑿诺卿?。
wx.login({
success (res) {
if (res.code) {
//發(fā)起網(wǎng)絡(luò)請求
wx.request({
url: 'https://example.com/onLogin',
data: {
code: res.code
}
})
} else {
console.log('登錄失??!' + res.errMsg)
}
}
})
4. 小程序調(diào)起支付API
小程序調(diào)起支付數(shù)據(jù)簽名字段列表:
字段名 | 變量名 | 必填 | 類型 | 示例值 | 描述 |
---|---|---|---|---|---|
小程序ID | appId | 是 | String | wxd678efh567hg6787 | 微信分配的小程序ID |
時間戳 | timeStamp | 是 | String | 1490840662 | 時間戳從1970年1月1日00:00:00至今的秒數(shù),即當前的時間 |
隨機串 | nonceStr | 是 | String | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 隨機字符串,不長于32位。推薦隨機數(shù)生成算法 |
數(shù)據(jù)包 | package | 是 | String | prepay_id=wx2017033010242291fcfe0db70013231072 | 統(tǒng)一下單接口返回的 prepay_id 參數(shù)值,提交格式如:prepay_id=wx2017033010242291fcfe0db70013231072 |
簽名方式 | signType | 是 | String | MD5 | 簽名類型,默認為MD5,支持HMAC-SHA256和MD5。注意此處需與統(tǒng)一下單的簽名類型一致 |
舉例如下:
paySign = MD5(appId=wxd678efh567hg6787&nonceStr=5K8264ILTKCH16CQ2502SI8ZNMTM67VS&package=prepay_id=wx2017033010242291fcfe0db70013231072&signType=MD5&timeStamp=1490840662&key=qazwsxedcrfvtgbyhnujmikolp111111) = 22D9B4E54AB1950F51E0649E8810ACD6
從官方文檔給出的參數(shù)可以看出,小程序調(diào)用支付API,有一些參數(shù)需要我們從后端接口返回,所以我們來進行后端支付接口的編寫。
5. 下單及回調(diào)接口
實現(xiàn)步驟
- 定義配置類 保存支付相關(guān)參數(shù)
- 定義支付接口,組裝數(shù)據(jù)發(fā)起請求
- 小程序調(diào)用支付接口
相關(guān)配置類,簽名,隨機字符串的代碼在文末給出,本文就不貼出來了。
@RestController
@RequestMapping(value = "/api/v1")
public class WXPayController {
/**
* 下單
*/
@RequestMapping(
value = "/weixin/payment",
method = RequestMethod.POST
)
public Map payment() {
Map<String, Object> map = new HashMap<>();
String money = "10";
String title = "商品名字";
try {
OrderInfo order = new OrderInfo();
order.setAppid(Configure.getAppID());
order.setMch_id(Configure.getMch_id());
order.setNonce_str(RandomStringGenerator.getRandomStringByLength(32));
order.setBody(title);
order.setOut_trade_no(RandomStringGenerator.getRandomStringByLength(32));
order.setTotal_fee(Integer.parseInt(money)); // 該金錢其實10 是 0.1元
order.setSpbill_create_ip("127.0.0.1");
order.setNotify_url(Configure.getNotify_url());
order.setTrade_type(Configure.getTrade_type());
//這里直接使用當前用戶的openid
order.setOpenid("ohfYG5O2mdXZLoszDAWwqtodOZRM");
order.setSign_type("MD5");
//生成簽名
String sign = Signature.getSign(order);
order.setSign(sign);
String result = HttpRequest.sendPost(Configure.getUrl(), order);
System.out.println(result);
XStream xStream = new XStream();
xStream.alias("xml", OrderReturnInfo.class);
OrderReturnInfo returnInfo = (OrderReturnInfo) xStream.fromXML(result);
// 二次簽名
if ("SUCCESS".equals(returnInfo.getReturn_code()) && returnInfo.getReturn_code().equals(returnInfo.getResult_code())) {
SignInfo signInfo = new SignInfo();
signInfo.setAppId(Configure.getAppID());
long time = System.currentTimeMillis() / 1000;
signInfo.setTimeStamp(String.valueOf(time));
signInfo.setNonceStr(RandomStringGenerator.getRandomStringByLength(32));
signInfo.setRepay_id("prepay_id=" + returnInfo.getPrepay_id());
signInfo.setSignType("MD5");
//生成簽名
String sign1 = Signature.getSign(signInfo);
Map<String, String> payInfo = new HashMap<>();
payInfo.put("timeStamp", signInfo.getTimeStamp());
payInfo.put("nonceStr", signInfo.getNonceStr());
payInfo.put("package", signInfo.getRepay_id());
payInfo.put("signType", signInfo.getSignType());
payInfo.put("paySign", sign1);
map.put("status", 200);
map.put("msg", "統(tǒng)一下單成功!");
map.put("data", payInfo);
// 此處可以寫喚起支付前的業(yè)務(wù)邏輯
// 業(yè)務(wù)邏輯結(jié)束 回傳給小程序端喚起支付
return map;
}
map.put("status", 500);
map.put("msg", "統(tǒng)一下單失敗!");
map.put("data", null);
return map;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 微信小程序支付成功回調(diào)函數(shù)
*/
@RequestMapping(value = "/weixin/callback")
public void wxNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream) request.getInputStream()));
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
//sb為微信返回的xml
String notityXml = sb.toString();
String resXml = "";
System.out.println("接收到的報文:" + notityXml);
Map map = PayUtil.doXMLParse(notityXml);
String returnCode = (String) map.get("return_code");
if ("SUCCESS".equals(returnCode)) {
//驗證簽名是否正確
Map<String, String> validParams = PayUtil.paraFilter(map); //回調(diào)驗簽時需要去除sign和空值參數(shù)
String validStr = PayUtil.createLinkString(validParams);//把數(shù)組所有元素,按照“參數(shù)=參數(shù)值”的模式用“&”字符拼接成字符串
String sign = PayUtil.sign(validStr, Configure.getKey(), "utf-8").toUpperCase();//拼裝生成服務(wù)器端驗證的簽名
// 因為微信回調(diào)會有八次之多,所以當?shù)谝淮位卣{(diào)成功了,那么我們就不再執(zhí)行邏輯了
//根據(jù)微信官網(wǎng)的介紹,此處不僅對回調(diào)的參數(shù)進行驗簽,還需要對返回的金額與系統(tǒng)訂單的金額進行比對等
if (sign.equals(map.get("sign"))) {
/**此處添加自己的業(yè)務(wù)邏輯代碼start**/
// bla bla bla....
/**此處添加自己的業(yè)務(wù)邏輯代碼end**/
//通知微信服務(wù)器已經(jīng)支付成功
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
} else {
System.out.println("微信支付回調(diào)失敗!簽名不一致");
}
} else {
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[報文為空]]></return_msg>" + "</xml> ";
}
System.out.println(resXml);
System.out.println("微信支付回調(diào)數(shù)據(jù)結(jié)束");
BufferedOutputStream out = new BufferedOutputStream(
response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
}
}
6. 項目地址
微信支付案例文章來源:http://www.zghlxwxcb.cn/news/detail-485835.html
具體代碼大家自行查看,相關(guān)商戶號沒有給出,大家自行申請。文章來源地址http://www.zghlxwxcb.cn/news/detail-485835.html
到了這里,關(guān)于微信小程序?qū)游⑿胖Ц对敿毥坛痰奈恼戮徒榻B完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!