需求
想在微信瀏覽器里面實現微信支付. 查看微信支付文檔, 發現要用微信JSAPI公眾號支付, 微信H5支付是不能實現的.
自己在實現的時候踩了許多坑, 花費了很多時間. 特此記錄, 希望能幫助到看到該文章的開發者.
開發環境
后端SpringBoot, 前端VUE
準備工作
1.首先我們要在微信公眾號平臺和微信商戶平臺 注冊賬號
2.準備JSAPI支付, 和調用微信統一下單的時候, 所需要的信息.
appid: 這個appid是公眾號的appId.
重要的一步. 這里appid如果想使用, 必需要在微信商戶號那里關聯并授權. 可根據 查看指引 這一步如何操作
appsecret: 開發者密碼. 可在微信公眾號平臺, 自己設置. 切記,設置后保存一下, 因為沒有地方可以回顯.
mer_id: 微信商戶號id
key: 微信商戶號API秘鑰. 配置完之后也需要保存, 因為沒有地方可以回顯
body: 訂單備注
nonce_str: 即用來標識一筆單, 是個隨機數, 可自行實現.
openid: 這個重中之重, 需要前后端一起配合, 后面講解如何獲取.
out_trade_no: 訂單編號
spbill_create_ip: ip地址,可以獲取當前請求的ip地址, 實現方式有很多, 可自行百度實現.
total_fee: 支付金額, 需要注意這個金額的單位是分, 所以在使用的時候, 要自己訂單金額 *100, 可能有時候失敗, 也是因為這個原因.
sign_type: 加簽方式, 也就是生成 sign 的方式, sign后面會提到, 這里默認是MD5, 建議也寫上MD5, 方便調起JSAPI支付的時候, 做統一.
trade_type: 調用的支付方式, 因為這里是JSAPI支付, 所以值為 JSAPI
notify_url: 微信支付的回調, 用于支付成功后處理的業務邏輯. 這個地址必須是外網可以訪問的地址, 如果支付成功沒有進入回調, 可能就是這里的原因.
sign: 簽名, 這個需要程序自己生成, 是根據上面的信息生成, 具體方式可看下面的代碼.
其中openid還沒有獲取到, 最費勁的也就是它了. 下面給出獲取openid的步驟, 需要前后端一起配合. 具體實現流程, 可根據自身情況.
獲取openid
用戶同意授權, 獲取code, 這里獲取code的方式, 是我們給前端實現了.
https
://open.weixin
.qq
.com
/connect
/oauth2
/authorize
?appid
=appid
&redirect_uri
=redirect_uri
&response_type
=code
&scope
=SCOPE
&state
=STATE#wechat_redirect
根據code獲取openid
有一點需要注意. 這里的code有效時間是5分鐘, 且只能用一次, 如果獲取openid失敗了, 看一下這個code是不是失效了,或者重復使用了, 我們之前前端獲取openid失敗, 就是因為沒有刷新, code重復使用了.
@ApiOperation("根據code獲取openId")@GetMapping("openid")public Result getOpenIdByCode(@RequestParam String code
){log
.info("根據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("查詢結果: "+json
.toString());String openId
= json
.getString("openid");return Result.success(ResultEnum.SELECT_SUCCESS
, openId
);}
目前為止, 我們已經獲取到了微信JSAPI支付所需要的全部數據, 如果您能跟著實現到了這里, 就已經成功了百分之八十.
下面開始代碼實現
后端接口準備. 編寫支付接口中的JSAPI. 這里主要是為了給前端調起JSAPI支付所需要的數據. 也就是后面返回的這幾個值
appId: 也就是這里一直用的appid
timeStamp: 注意,這里是時間戳, 我們之前調起失敗, 也是因為這里沒用時間錯
nonceStr: 隨機字符
signType: 簽名方式, 和我們調用統一下單生成的簽名方式一樣. 也要設置為MD5
package: 包名. 注意: 不能寫 package, 因為是關鍵字
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("統一下單返回結果: "+orderStr
);if(StringUtils.isEmpty(orderStr
)){log
.error("微信支付失敗.原因: 調用微信統一下單接口失敗");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參數轉xml失敗({})", e
.getMessage());throw new TPlusException(ErrorEnum.WECHAT_PAY_ERROR
);}
得到需要的數據之后, 前端來說就很簡單了, 直接可以根據返回的數據調起微信支付.
也可參考: JSAPI調起支付文檔
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();
}
最后一個需要注意的問題是, 前端在調起微信JSAPI支付的時候, 通過本地是無法調用成功的, 必須發布到線上, 而且要在微信商戶平臺, 配置線上地址的授權域名.
特別注意: 該域名必須和前端訪問的域名一致, 不可以配置主域名. 我們一直無法調起支付, 其中有一個坑也是這個原因.
上面Java調用統一下單時所需要的工具類
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
;}
到此為止, 就已經完成了微信JSAPI支付, 以上純手寫, 如有錯誤的地方, 歡迎指教. 謝謝!
總結
以上是生活随笔為你收集整理的手把手教你实现Java微信JSAPI支付的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。