生活随笔
收集整理的這篇文章主要介紹了
手把手教你实现Java微信JSAPI支付
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
需求
想在微信瀏覽器里面實(shí)現(xiàn)微信支付. 查看微信支付文檔, 發(fā)現(xiàn)要用微信JSAPI公眾號(hào)支付, 微信H5支付是不能實(shí)現(xiàn)的.
自己在實(shí)現(xiàn)的時(shí)候踩了許多坑, 花費(fèi)了很多時(shí)間. 特此記錄, 希望能幫助到看到該文章的開發(fā)者.
開發(fā)環(huán)境
后端SpringBoot, 前端VUE
準(zhǔn)備工作
1.首先我們要在微信公眾號(hào)平臺(tái)和微信商戶平臺(tái) 注冊(cè)賬號(hào)
2.準(zhǔn)備JSAPI支付, 和調(diào)用微信統(tǒng)一下單的時(shí)候, 所需要的信息.
appid: 這個(gè)appid是公眾號(hào)的appId.
重要的一步. 這里appid如果想使用, 必需要在微信商戶號(hào)那里關(guān)聯(lián)并授權(quán). 可根據(jù) 查看指引 這一步如何操作
appsecret: 開發(fā)者密碼. 可在微信公眾號(hào)平臺(tái), 自己設(shè)置. 切記,設(shè)置后保存一下, 因?yàn)闆]有地方可以回顯.
mer_id: 微信商戶號(hào)id
key: 微信商戶號(hào)API秘鑰. 配置完之后也需要保存, 因?yàn)闆]有地方可以回顯
body: 訂單備注
nonce_str: 即用來標(biāo)識(shí)一筆單, 是個(gè)隨機(jī)數(shù), 可自行實(shí)現(xiàn).
openid: 這個(gè)重中之重, 需要前后端一起配合, 后面講解如何獲取.
out_trade_no: 訂單編號(hào)
spbill_create_ip: ip地址,可以獲取當(dāng)前請(qǐng)求的ip地址, 實(shí)現(xiàn)方式有很多, 可自行百度實(shí)現(xiàn).
total_fee: 支付金額, 需要注意這個(gè)金額的單位是分, 所以在使用的時(shí)候, 要自己訂單金額 *100, 可能有時(shí)候失敗, 也是因?yàn)檫@個(gè)原因.
sign_type: 加簽方式, 也就是生成 sign 的方式, sign后面會(huì)提到, 這里默認(rèn)是MD5, 建議也寫上MD5, 方便調(diào)起JSAPI支付的時(shí)候, 做統(tǒng)一.
trade_type: 調(diào)用的支付方式, 因?yàn)檫@里是JSAPI支付, 所以值為 JSAPI
notify_url: 微信支付的回調(diào), 用于支付成功后處理的業(yè)務(wù)邏輯. 這個(gè)地址必須是外網(wǎng)可以訪問的地址, 如果支付成功沒有進(jìn)入回調(diào), 可能就是這里的原因.
sign: 簽名, 這個(gè)需要程序自己生成, 是根據(jù)上面的信息生成, 具體方式可看下面的代碼.
其中openid還沒有獲取到, 最費(fèi)勁的也就是它了. 下面給出獲取openid的步驟, 需要前后端一起配合. 具體實(shí)現(xiàn)流程, 可根據(jù)自身情況.
獲取openid
用戶同意授權(quán), 獲取code, 這里獲取code的方式, 是我們給前端實(shí)現(xiàn)了.
https
://open.weixin
.qq
.com
/connect
/oauth2
/authorize
?appid
=appid
&redirect_uri
=redirect_uri
&response_type
=code
&scope
=SCOPE
&state
=STATE#wechat_redirect
根據(jù)code獲取openid
有一點(diǎn)需要注意. 這里的code有效時(shí)間是5分鐘, 且只能用一次, 如果獲取openid失敗了, 看一下這個(gè)code是不是失效了,或者重復(fù)使用了, 我們之前前端獲取openid失敗, 就是因?yàn)闆]有刷新, code重復(fù)使用了.
@ApiOperation("根據(jù)code獲取openId")@GetMapping("openid")public Result getOpenIdByCode(@RequestParam String code
){log
.info("根據(jù)code:{}獲取openId", code
);String getopenid_url
= "https://api.weixin.qq.com/sns/oauth2/access_token";String param
= "appid="+appid
+"&secret="+secret
+"&code="+code
+"&grant_type=authorization_code";String openIdStr
= HttpUtils.sendGet(getopenid_url
, param
);JSONObject json
= JSONObject.parseObject(openIdStr
);log
.info("查詢結(jié)果: "+json
.toString());String openId
= json
.getString("openid");return Result.success(ResultEnum.SELECT_SUCCESS
, openId
);}
目前為止, 我們已經(jīng)獲取到了微信JSAPI支付所需要的全部數(shù)據(jù), 如果您能跟著實(shí)現(xiàn)到了這里, 就已經(jīng)成功了百分之八十.
下面開始代碼實(shí)現(xiàn)
后端接口準(zhǔn)備. 編寫支付接口中的JSAPI. 這里主要是為了給前端調(diào)起JSAPI支付所需要的數(shù)據(jù). 也就是后面返回的這幾個(gè)值
appId: 也就是這里一直用的appid
timeStamp: 注意,這里是時(shí)間戳, 我們之前調(diào)起失敗, 也是因?yàn)檫@里沒用時(shí)間錯(cuò)
nonceStr: 隨機(jī)字符
signType: 簽名方式, 和我們調(diào)用統(tǒng)一下單生成的簽名方式一樣. 也要設(shè)置為MD5
package: 包名. 注意: 不能寫 package, 因?yàn)槭顷P(guān)鍵字
paySign: 簽名
String requestIp
= CommonUtils.getIpAddr(request
);SortedMap<String,String> params
= new TreeMap<>();params
.put("appid", appid
);params
.put("body", remark
);params
.put("mch_id", merId
);params
.put("nonce_str", CommonUtils.generateUUID());params
.put("openid", openId
);params
.put("out_trade_no", out_trade_no
);params
.put("spbill_create_ip", requestIp
);params
.put("total_fee","100");params
.put("sign_type", "MD5");params
.put("trade_type", "JSAPI");params
.put("notify_url", weChatConfig
.notify_url
);String sign
= WXPayUtil.createSign(params
, weChatConfig
.key
);params
.put("sign", sign
);String payXml
;try {payXml
= WXPayUtil.mapToXml(params
);log
.info("payXml: "+payXml
);String orderStr
= HttpUtils.doPost("https://api.mch.weixin.qq.com/pay/unifiedorder",payXml
,4000);log
.info("統(tǒng)一下單返回結(jié)果: "+orderStr
);if(StringUtils.isEmpty(orderStr
)){log
.error("微信支付失敗.原因: 調(diào)用微信統(tǒng)一下單接口失敗");throw new TPlusException(ErrorEnum.WECHAT_PAY_ERROR
);}String prepay_id
= "";if (orderStr
.indexOf("SUCCESS") != -1) {Map<String, String> map
= WXPayUtil.xmlToMap(orderStr
);prepay_id
= map
.get("prepay_id");}SortedMap<String,String> payMap
= new TreeMap<>();payMap
.put("appId", appid
);payMap
.put("timeStamp", String.valueOf(new Date().getTime()/1000));payMap
.put("nonceStr", CommonUtils.generateUUID());payMap
.put("signType", "MD5");payMap
.put("package", "prepay_id="+prepay_id
);String paySign
= WXPayUtil.createSign(payMap
, key
);payMap
.put("paySign", paySign
);return payMap
;} catch (Exception e
) {log
.error("微信支付失敗.原因: map參數(shù)轉(zhuǎn)xml失敗({})", e
.getMessage());throw new TPlusException(ErrorEnum.WECHAT_PAY_ERROR
);}
得到需要的數(shù)據(jù)之后, 前端來說就很簡單了, 直接可以根據(jù)返回的數(shù)據(jù)調(diào)起微信支付.
也可參考: JSAPI調(diào)起支付文檔
function
onBridgeReady(){WeixinJSBridge.invoke('getBrandWCPayRequest', {"appId":appId
, "timeStamp":timeStamp
, "nonceStr":nonceStr
, "package":package, "signType":signType
, "paySign":paySign
},function(res
){if(res
.err_msg
== "get_brand_wcpay_request:ok" ){} });
}
if (typeof
WeixinJSBridge == "undefined"){if( document
.addEventListener
){document
.addEventListener('WeixinJSBridgeReady', onBridgeReady
, false);}else if (document
.attachEvent
){document
.attachEvent('WeixinJSBridgeReady', onBridgeReady
); document
.attachEvent('onWeixinJSBridgeReady', onBridgeReady
);}
}else{onBridgeReady();
}
最后一個(gè)需要注意的問題是, 前端在調(diào)起微信JSAPI支付的時(shí)候, 通過本地是無法調(diào)用成功的, 必須發(fā)布到線上, 而且要在微信商戶平臺(tái), 配置線上地址的授權(quán)域名.
特別注意: 該域名必須和前端訪問的域名一致, 不可以配置主域名. 我們一直無法調(diào)起支付, 其中有一個(gè)坑也是這個(gè)原因.
上面Java調(diào)用統(tǒng)一下單時(shí)所需要的工具類
CommonUtils
public class CommonUtils {public static String generateUUID(){String uuid
= UUID
.randomUUID().toString().replaceAll("-","").substring(0,32);return uuid
;}public static String getIpAddr(HttpServletRequest request
) {String ipAddress
= request
.getHeader("x-forwarded-for");if (ipAddress
== null || ipAddress
.length() == 0 || "unknown".equalsIgnoreCase(ipAddress
)) {ipAddress
= request
.getHeader("Proxy-Client-IP");}if (ipAddress
== null || ipAddress
.length() == 0 || "unknown".equalsIgnoreCase(ipAddress
)) {ipAddress
= request
.getHeader("WL-Proxy-Client-IP");}if (ipAddress
== null || ipAddress
.length() == 0 || "unknown".equalsIgnoreCase(ipAddress
)) {ipAddress
= request
.getRemoteAddr();if (ipAddress
.equals("127.0.0.1") || ipAddress
.equals("0:0:0:0:0:0:0:1")) {InetAddress inet
= null;try {inet
= InetAddress.getLocalHost();} catch (UnknownHostException e
) {e
.printStackTrace();}ipAddress
= inet
.getHostAddress();}}if (ipAddress
!= null && ipAddress
.length() > 15) { if (ipAddress
.indexOf(",") > 0) {ipAddress
= ipAddress
.substring(0, ipAddress
.indexOf(","));}}return ipAddress
;}
到此為止, 就已經(jīng)完成了微信JSAPI支付, 以上純手寫, 如有錯(cuò)誤的地方, 歡迎指教. 謝謝!
總結(jié)
以上是生活随笔為你收集整理的手把手教你实现Java微信JSAPI支付的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。