javascript
h5封装去底部_干货分享 | 一步一步教你在SpringBoot中集成微信支付H5支付
一:開發文檔場景介紹
H5支付是指商戶在微信客戶端外的移動端網頁展示商品或服務,用戶在前述頁面確認使用微信支付時,商戶發起本服務呼起微信客戶端進行支付。
主要用于觸屏版的手機瀏覽器請求微信支付的場景。可以方便的從外部瀏覽器喚起微信支付。
申請入口:登錄商戶平臺-->產品中心-->我的產品-->支付產品-->H5支付 注意:需要開通H5支付,并且做一些配置
微信官方體驗鏈接:http://wxpay.wxutil.com/mch/pay/h5.v2.php,請在微信外瀏覽器打開。
1、用戶在商戶側完成下單,使用微信支付進行支付
2、由商戶后臺向微信支付發起下單請求(調用統一下單接口)注:交易類型trade_type=MWEB
3、統一下單接口返回支付相關參數給商戶后臺,如支付跳轉url(參數名“mweburl”),商戶通過mweburl調起微信支付中間頁
4、中間頁進行H5權限的校驗,安全性檢查(此處常見錯誤請見下文)
5、如支付成功,商戶后臺會接收到微信側的異步通知
6、用戶在微信支付收銀臺完成支付或取消支付,返回商戶頁面(默認為返回支付發起頁面)
7、商戶在展示頁面,引導用戶主動發起支付結果的查詢
8,9、商戶后臺判斷是否接到收微信側的支付結果通知,如沒有,后臺調用我們的訂單查詢接口確認訂單狀態
10、展示最終的訂單支付結果給用戶
H5支付文檔
二:集成步驟
1. 引入依賴
com.github.wxpay wxpay-sdk 0.0.3org.webjars jquery 3.3.12. application.yml
# 測試賬號pay: wxpay: appID: wxab8acb865bb1637e mchID: 11473623 key: 2ab9071b06b9f739b950ddb41db2690d sandboxKey: 3639bc1370e105aa65f10cd4fef2a3ef certPath: /var/local/cert/apiclient_cert.p12 notifyUrl: http://65ta5j.natappfree.cc/wxpay/refund/notify useSandbox: truespring: thymeleaf: prefix: classpath:/templates/ suffix: .html mode: HTML5 encoding: UTF-83. WebMvcConfiguration
@Configurationpublic class WebMvcConfiguration extends WebMvcConfigurationSupport { @Override protected void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/gotoWapPage").setViewName("gotoWapPay"); registry.addViewController("/gotoPagePage").setViewName("gotoPagePay"); registry.addViewController("/gotoH5Page").setViewName("gotoH5Page"); registry.addViewController("/h5PaySuccess").setViewName("h5PaySuccess"); super.addViewControllers(registry); } @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); super.addResourceHandlers(registry); }}4. MyWXPayConfig
/** * 微信支付的參數配置 * * @author mengday zhang */@Data@Slf4j@ConfigurationProperties(prefix = "pay.wxpay")public class MyWXPayConfig implements WXPayConfig{ /** 公眾賬號ID */ private String appID; /** 商戶號 */ private String mchID; /** API 密鑰 */ private String key; /** API 沙箱環境密鑰 */ private String sandboxKey; /** API證書絕對路徑 */ private String certPath; /** 退款異步通知地址 */ private String notifyUrl; private Boolean useSandbox; /** HTTP(S) 連接超時時間,單位毫秒 */ private int httpConnectTimeoutMs = 8000; /** HTTP(S) 讀數據超時時間,單位毫秒 */ private int httpReadTimeoutMs = 10000; /** * 獲取商戶證書內容 * * @return 商戶證書內容 */ @Override public InputStream getCertStream() { File certFile = new File(certPath); InputStream inputStream = null; try { inputStream = new FileInputStream(certFile); } catch (FileNotFoundException e) { log.error("cert file not found, path={}, exception is:{}", certPath, e); } return inputStream; } @Override public String getKey(){ if (useSandbox) { return sandboxKey; } return key; }}5. WXPayClient
/** * WXPayClient ** 對WXPay的簡單封裝,處理支付密切相關的邏輯. * * @author Mengday Zhang * @version 1.0 * @since 2018/6/16 */@Slf4jpublic class WXPayClient extends WXPay { /** 密鑰算法 */ private static final String ALGORITHM = "AES"; /** 加解密算法/工作模式/填充方式 */ private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS5Padding"; /** 用戶支付中,需要輸入密碼 */ private static final String ERR_CODE_USERPAYING = "USERPAYING"; private static final String ERR_CODE_AUTHCODEEXPIRE = "AUTHCODEEXPIRE"; /** 交易狀態: 未支付 */ private static final String TRADE_STATE_NOTPAY = "NOTPAY"; /** 用戶輸入密碼,嘗試30秒內去查詢支付結果 */ private static Integer remainingTimeMs = 10000; private WXPayConfig config; public WXPayClient(WXPayConfig config, WXPayConstants.SignType signType, boolean useSandbox) { super(config, signType, useSandbox); this.config = config; } /** * * 刷卡支付 * * 對WXPay#microPay(Map)增加了當支付結果為USERPAYING時去輪詢查詢支付結果的邏輯處理 * * 注意:該方法沒有處理return_code=FAIL的情況,暫時不考慮網絡問題,這種情況直接返回錯誤 * * @param reqData * @return * @throws Exception */ public Map microPayWithPOS(Map reqData) throws Exception { // 開始時間(毫秒) long startTimestampMs = System.currentTimeMillis(); Map responseMapForPay = super.microPay(reqData); log.info(responseMapForPay.toString()); // // 先判斷 協議字段返回(return_code),再判斷 業務返回,最后判斷 交易狀態(trade_state) // 通信標識,非交易標識 String returnCode = responseMapForPay.get("return_code"); if (WXPayConstants.SUCCESS.equals(returnCode)) { String errCode = responseMapForPay.get("err_code"); // 余額不足,信用卡失效 if (ERR_CODE_USERPAYING.equals(errCode) || "SYSTEMERROR".equals(errCode) || "BANKERROR".equals(errCode)) { Map orderQueryMap = null; Map requestData = new HashMap<>(); requestData.put("out_trade_no", reqData.get("out_trade_no")); // 用戶支付中,需要輸入密碼或系統錯誤則去重新查詢訂單API err_code, result_code, err_code_des // 每次循環時的當前系統時間 - 開始時記錄的時間 > 設定的30秒時間就退出 while (System.currentTimeMillis() - startTimestampMs < remainingTimeMs) { // 商戶收銀臺得到USERPAYING狀態后,經過商戶后臺系統調用【查詢訂單API】查詢實際支付結果。 orderQueryMap = super.orderQuery(requestData); String returnCodeForQuery = orderQueryMap.get("return_code"); if (WXPayConstants.SUCCESS.equals(returnCodeForQuery)) { // 通訊成功 String tradeState = orderQueryMap.get("trade_state"); if (WXPayConstants.SUCCESS.equals(tradeState)) { // 如果成功了直接將查詢結果返回 return orderQueryMap; } // 如果支付結果仍為USERPAYING,則每隔5秒循環調用【查詢訂單API】判斷實際支付結果 Thread.sleep(1000); } } // 如果用戶取消支付或累計30秒用戶都未支付,商戶收銀臺退出查詢流程后繼續調用【撤銷訂單API】撤銷支付交易。 String tradeState = orderQueryMap.get("trade_state"); if (TRADE_STATE_NOTPAY.equals(tradeState) || ERR_CODE_USERPAYING.equals(tradeState) || ERR_CODE_AUTHCODEEXPIRE.equals(tradeState)) { Map reverseMap = this.reverse(requestData); String returnCodeForReverse = reverseMap.get("return_code"); String resultCode = reverseMap.get("result_code"); if (WXPayConstants.SUCCESS.equals(returnCodeForReverse) && WXPayConstants.SUCCESS.equals(resultCode)) { // 如果撤銷成功,需要告訴客戶端已經撤銷訂單了 responseMapForPay.put("err_code_des", "用戶取消支付或尚未支付,后臺已經撤銷該訂單,請重新支付!"); } } } } return responseMapForPay; } /** * 從request的inputStream中獲取參數 * @param request * @return * @throws Exception */ public Map getNotifyParameter(HttpServletRequest request) throws Exception { InputStream inputStream = request.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int length = 0; while ((length = inputStream.read(buffer)) != -1) { outSteam.write(buffer, 0, length); } outSteam.close(); inputStream.close(); // 獲取微信調用我們notify_url的返回信息 String resultXml = new String(outSteam.toByteArray(), "utf-8"); Map notifyMap = WXPayUtil.xmlToMap(resultXml); return notifyMap; } /** * 解密退款通知 * * 退款結果通知文檔 * @return * @throws Exception */ public Map decodeRefundNotify(HttpServletRequest request) throws Exception { // 從request的流中獲取參數 Map notifyMap = this.getNotifyParameter(request); log.info(notifyMap.toString()); String reqInfo = notifyMap.get("req_info"); //(1)對加密串A做base64解碼,得到加密串B byte[] bytes = new BASE64Decoder().decodeBuffer(reqInfo); //(2)對商戶key做md5,得到32位小寫key* ( key設置路徑:微信商戶平臺(pay.weixin.qq.com)-->賬戶設置-->API安全-->密鑰設置 ) Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING); SecretKeySpec key = new SecretKeySpec(WXPayUtil.MD5(config.getKey()).toLowerCase().getBytes(), ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, key); //(3)用key*對加密串B做AES-256-ECB解密(PKCS7Padding) // java.security.InvalidKeyException: Illegal key size or default parameters // https://www.cnblogs.com/yaks/p/5608358.html String responseXml = new String(cipher.doFinal(bytes),"UTF-8"); Map responseMap = WXPayUtil.xmlToMap(responseXml); return responseMap; } /** * 獲取沙箱環境驗簽秘鑰API * 獲取驗簽秘鑰API文檔 * @return * @throws Exception */ public Map getSignKey() throws Exception { Map reqData = new HashMap<>(); reqData.put("mch_id", config.getMchID()); reqData.put("nonce_str", WXPayUtil.generateNonceStr()); String sign = WXPayUtil.generateSignature(reqData, config.getKey(), WXPayConstants.SignType.MD5); reqData.put("sign", sign); String responseXml = this.requestWithoutCert("https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey", reqData, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs()); Map responseMap = WXPayUtil.xmlToMap(responseXml); return responseMap; }}
6. WXPayConfiguration
/** * 微信支付配置 * * @author mengday zhang */@Configuration@EnableConfigurationProperties(MyWXPayConfig.class)public class WXPayConfiguration { @Autowired private MyWXPayConfig wxPayConfig; /** * useSandbox 沙盒環境 * @return */ @Bean public WXPay wxPay() { return new WXPay(wxPayConfig, WXPayConstants.SignType.MD5, wxPayConfig.getUseSandbox() ); } @Bean public WXPayClient wxPayClient() { return new WXPayClient(wxPayConfig, WXPayConstants.SignType.MD5, wxPayConfig.getUseSandbox()); }}7. gotoH5Page.html
Title購買商品:越南新娘
價格:20000
數量:10個
提交訂單8. h5PaySuccess.html
Title微信支付-H5支付成功
9. WXPayH5PayController
/** * 微信支付-H5支付. ** detailed description * * @author Mengday Zhang * @version 1.0 * @since 2018/6/18 */@Slf4j@RestController@RequestMapping("/wxpay/h5pay")public class WXPayH5PayController { @Autowired private WXPay wxPay; @Autowired private WXPayClient wxPayClient; /** * 使用沙箱支付的金額必須是用例中指定的金額,也就是 1.01 元,1.02元等,不能是你自己的商品的實際價格,必須是這個數。 * 否則會報錯:沙箱支付金額(2000)無效,請檢查需要驗收的case * @return * @throws Exception */ @PostMapping("/order") public Object h5pay() throws Exception { Map reqData = new HashMap<>(); reqData.put("out_trade_no", String.valueOf(System.nanoTime())); reqData.put("trade_type", "MWEB"); reqData.put("product_id", "1"); reqData.put("body", "商戶下單"); // 訂單總金額,單位為分 reqData.put("total_fee", "101"); // APP和網頁支付提交用戶端ip,Native支付填調用微信支付API的機器IP。 reqData.put("spbill_create_ip", "14.23.150.211"); // 異步接收微信支付結果通知的回調地址,通知url必須為外網可訪問的url,不能攜帶參數。 reqData.put("notify_url", "http://3sbqi7.natappfree.cc/wxpay/h5pay/notify"); // 自定義參數, 可以為終端設備號(門店號或收銀設備ID),PC網頁或公眾號內支付可以傳"WEB" reqData.put("device_info", ""); // 附加數據,在查詢API和支付通知中原樣返回,可作為自定義參數使用。 reqData.put("attach", ""); reqData.put("scene_info", "{"h5_info": {"type":"Wap","wap_url": "http://3sbqi7.natappfree.cc","wap_name": "騰訊充值"}}"); Map responseMap = wxPay.unifiedOrder(reqData); log.info(responseMap.toString()); String returnCode = responseMap.get("return_code"); String resultCode = responseMap.get("result_code"); if (WXPayConstants.SUCCESS.equals(returnCode) && WXPayConstants.SUCCESS.equals(resultCode)) { // 預支付交易會話標識 String prepayId = responseMap.get("prepay_id"); // 支付跳轉鏈接(前端需要在該地址上拼接redirect_url,該參數不是必須的) // 正常流程用戶支付完成后會返回至發起支付的頁面,如需返回至指定頁面,則可以在MWEB_URL后拼接上redirect_url參數,來指定回調頁面 // 需對redirect_url進行urlencode處理 // TODO 正常情況下這里應該是普通的鏈接,不知道這里為何是weixin://這樣的鏈接,不知道是不是微信公眾平臺上的配置少配置了; // 由于沒有實際賬號,還沒找到為啥不是普通鏈接的原因 String mwebUrl = responseMap.get("mweb_url"); } return responseMap; } /** * 注意:如果是沙箱環境,一提交訂單就會立即異步通知,而無需拉起微信支付收銀臺的中間頁面 * @param request * @throws Exception */ @RequestMapping("/notify") public void payNotify(HttpServletRequest request, HttpServletResponse response) throws Exception{ Map reqData = wxPayClient.getNotifyParameter(request); log.info(reqData.toString()); String returnCode = reqData.get("return_code"); String resultCode = reqData.get("result_code"); if (WXPayConstants.SUCCESS.equals(returnCode) && WXPayConstants.SUCCESS.equals(resultCode)) { boolean signatureValid = wxPay.isPayResultNotifySignatureValid(reqData); if (signatureValid) { // TODO 業務處理 Map responseMap = new HashMap<>(2); responseMap.put("return_code", "SUCCESS"); responseMap.put("return_msg", "OK"); String responseXml = WXPayUtil.mapToXml(responseMap); response.setContentType("text/xml"); response.getWriter().write(responseXml); response.flushBuffer(); } } }}
三: 常見問題
一、回調頁面
正常流程用戶支付完成后會返回至發起支付的頁面,如需返回至指定頁面,則可以在MWEBURL后拼接上redirecturl參數,來指定回調頁面。
如,您希望用戶支付完成后跳轉至https://www.wechatpay.com.cn,則可以做如下處理:
假設您通過統一下單接口獲到的MWEBURL= https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepayid=wx20161110163838f231619da20804912345&package=1037687096
則拼接后的地址為MWEBURL= https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepayid=wx20161110163838f231619da20804912345&package=1037687096&redirect_url=https%3A%2F%2Fwww.wechatpay.com.cn
注意:
- 微信支付中間頁調起微信收銀臺后超過5秒
- 用戶點擊“取消支付“或支付完成后點“完成”按鈕。因此無法保證頁面回跳時,支付流程已結束,所以商戶設置的redirect_url地址不能自動執行查單操作,應讓用戶去點擊按鈕觸發查單操作。
獲取源碼
關注并回復關鍵字“微信支付H5支付”獲取源碼。
總結
以上是生活随笔為你收集整理的h5封装去底部_干货分享 | 一步一步教你在SpringBoot中集成微信支付H5支付的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python读取一行数组_python
- 下一篇: 正则表达式不包含某个字符串_JMeter