一:開發文檔場景介紹
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支付文檔
二:集成步驟
dependency groupIdcom.github.wxpay/groupId artifactIdwxpay-sdk/artifactId version0.0.3/version/dependencydependency groupIdorg.webjars/groupId artifactIdjquery/artifactId version3.3.1/version/dependency
# 測試賬号 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: true spring: thymeleaf: prefix: classpath:/templates/ suffix: .html mode: HTML5 encoding: UTF-8
@Configuration public 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); } }
/** * 微信支付的參數配置 * * @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; } }
/** * WXPayClient * * 對WXPay的簡單封裝,處理支付密切相關的邏輯. * * @author Mengday Zhang * @version 1.0 * @since 2018/6/16 */ @Slf4j public 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 MapString, String microPayWithPOS(MapString, String reqData) throws Exception { // 開始時間(毫秒) long startTimestampMs = System.currentTimeMillis(); MapString, String 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)) { MapString, String orderQueryMap = null; MapString, String 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)) { MapString, String 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 MapString, String 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 MapString, String notifyMap = WXPayUtil.xmlToMap(resultXml); return notifyMap; } /** * 解密退款通知 * * a href=htttttants.SignType.MD5, wxPayConfig.getUseSandbox()); } }
!DOCTYPE htmlhtml lang=head meta charset=UTF-8 titleTitle/title/headbody style=font-size: 30px 購買商品:越南新娘 價格:20000 數量:10個 button style=width: 100%; height: 60px; alignment: center; background: #b49e8f onclick=commitOrder()提交訂單/buttonscript src=http://localhost:8080/webjars/jquery/3.3.1/jquery.js/scriptscript function commitOrder() { $.ajax({ type: POST, url: http://localhost:8080/wxpay/h5pay/order, data: null, success: function(data) { console.log(data); var redirectUrl = http://localhost:8080/h5PaySuccess var mwebUrl = data.mweb_url &redirect_url= encodeURIComponent(redirectUrl); window.location.href=mwebUrl; } }) } /script/body/htm
!DOCTYPE htmlhtml lang=head meta charset=UTF-8 titleTitle/title/headbodyh1微信支付-H5支付成功 /body/html
/** * 微信支付-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 { MapString, String reqData = new HashMap reqData.put(out_trade_no, String.valueOf(System.nanoTime())); reqData.put(trade_typeMWEB reqData.put(product_id1 reqData.put(body商戶下單 // 訂單總金額,單位為分 reqData.put(total_fee101 // APP和網頁支付提交用戶端ip,Native支付填調用微信支付API的機器IP。 reqData.put(spbill_create_ip14.23.150.211 // 異步接收微信支付結果通知的回調地址,通知url必須為外網可訪問的url,不能攜帶參數。 reqData.put(notify_urlhttp://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\: \騰訊充值\); MapString, String 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{ MapString, String 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 業務處理 MapString, String responseMap = new HashMap responseMap.put(return_codeSUCCESS responseMap.put(return_msgOK String responseXml = WXPayUtil.mapToXml(responseMap); response.setContentType(text/xml response.getWriter().write(responseXml); response.flushBuffer(); } } } }
三: 常見問題 一、回調頁面
正常流程用戶支付完成後會返回至發起支付的頁面,如需返回至指定頁面,則可以在MWEBURL後拼接上redirecturl參數,來指定回調頁面。
如,您希望用戶支付完成後跳轉至htt
更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!