日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

手把手教你实现Java微信JSAPI支付

發(fā)布時(shí)間:2023/12/31 javascript 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 手把手教你实现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)了.
  • // appid: 需要把我們的appid給前端 // redirect_uri: 回調(diào)地址, 前端自己填寫, 用來獲取code的 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);// 轉(zhuǎn)成Json格式log.info("查詢結(jié)果: "+json.toString());String openId = json.getString("openid");// 獲取openIdreturn 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: 簽名
  • // 獲取當(dāng)前請(qǐng)求的ip地址String requestIp = CommonUtils.getIpAddr(request);// 拼接統(tǒng)一下單地址參數(shù)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);//訂單號(hào)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);// 生成簽名之后, 注意一定要存進(jìn)去.params.put("sign", sign);String payXml;try {// 微信支付要求這里必須是一個(gè)xmlpayXml = WXPayUtil.mapToXml(params);log.info("payXml: "+payXml);// 調(diào)用微信的統(tǒng)一下單接口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);}// 預(yù)支付idString prepay_id = "";if (orderStr.indexOf("SUCCESS") != -1) {// 這個(gè)工具類可以用微信官方案例中的Map<String, String> map = WXPayUtil.xmlToMap(orderStr);prepay_id = map.get("prepay_id");}/** 返回給前端所需要的數(shù)據(jù) */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, //公眾號(hào)ID,由商戶傳入 "timeStamp":timeStamp, //時(shí)間戳,自1970年以來的秒數(shù) "nonceStr":nonceStr, //隨機(jī)串 "package":package, "signType":signType, //微信簽名方式: "paySign":paySign //微信簽名 },function(res){if(res.err_msg == "get_brand_wcpay_request:ok" ){// 使用以上方式判斷前端返回,微信團(tuán)隊(duì)鄭重提示://res.err_msg將在用戶支付成功后返回ok,但并不保證它絕對(duì)可靠。} }); } 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 {/*** 生成uuid,即用來標(biāo)識(shí)一筆單,也用做 微信支付的nonce_str* @return*/public static String generateUUID(){String uuid = UUID.randomUUID().toString().replaceAll("-","").substring(0,32);return uuid;}/*** 獲取用戶請(qǐng)求ip* @param request* @return*/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")) {//根據(jù)網(wǎng)卡取本機(jī)配置的IPInetAddress inet = null;try {inet = InetAddress.getLocalHost();} catch (UnknownHostException e) {e.printStackTrace();}ipAddress = inet.getHostAddress();}}//對(duì)于通過多個(gè)代理的情況,第一個(gè)IP為客戶端真實(shí)IP,多個(gè)IP按照','分割if (ipAddress != null && ipAddress.length() > 15) { //"***.***.***.***".length() = 15if (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ò),歡迎將生活随笔推薦給好友。