一:开发文档场景介绍

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.1

2. 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-8     

3. 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

注意:

  1. 注意MWEB_URL是普通的链接,不是微信那种短链接(我在使用测试账号时就返回了短链接,没找到什么原因,估计使用真实账号,申请开通H5支付,然后做一些开发配置估计就正常了)
  2. 需对redirect_url进行urlencode处理
  3. 由于设置redirect_url后,回跳指定页面的操作可能发生在:
  • 微信支付中间页调起微信收银台后超过5秒
  • 用户点击“取消支付“或支付完成后点“完成”按钮。因此无法保证页面回跳时,支付流程已结束,所以商户设置的redirect_url地址不能自动执行查单操作,应让用户去点击按钮触发查单操作。

获取源码

关注并回复关键字“微信支付H5支付”获取源码。

h5封装去底部_干货分享 | 一步一步教你在SpringBoot中集成微信支付H5支付相关推荐

  1. 在sdk中添加源文件_实用干货 | 一步一步教你在SpringBoot中集成微信刷卡支付

    一:准备工作 使用微信支付需要先开通服务号,然后还要开通微信支付,最后还要配置一些开发参数,过程比较多. 申请服务号(企业) 开通微信支付 开发配置 具体准备工作请参考Spring Boot入门教程( ...

  2. 微擎支付返回商户单号_一步一步教你在SpringBoot中集成微信扫码支付

    一:准备工作 使用微信支付需要先开通服务号,然后还要开通微信支付,最后还要配置一些开发参数,过程比较多. 申请服务号(企业) 开通微信支付 开发配置 具体准备工作请参考Spring Boot入门教程( ...

  3. h5封装去底部_贪婪洞窟H5:也出微信小游戏了!还是原来贪婪的味道

    贪婪洞窟H5 关键词:地牢探险.闯关.贪婪洞窟 推荐星数:5星 官方介绍:贪婪洞窟,刺激的地下城冒险游戏. 沐沐带你发现好游戏! 今天沐沐给大家推荐的这款游戏叫 <贪婪洞窟h5>. 游戏改 ...

  4. h5封装去底部_Appium—Native+H5混合APP的自动化

    前言 小编所在项目的客户端是比较奇怪的一个APP,大部分页面Android和iOS的客户端只提供了webview的功能,都是由H5处理业务逻辑和用户交互.H5承担了和服务端.和客户端的交互. 虽然在开 ...

  5. 星淘惠跨境:100%纯干货分享,五个要点教你打造亚马逊爆款

    星淘惠跨境:100%纯干货分享,五个要点教你打造亚马逊爆款 跨境电商,是这两年被提起最多的词,提起跨境电商就不得不提起亚马逊,亚马逊作为当下主流电商平台,全球用户超2亿,Prime会员超1.12亿,很 ...

  6. vs code vue插件_干货分享 | Vue框架常见问题浅谈

    友情提示:全文7800多文字,预计阅读时间10分钟 Vue是一套用于构建用户界面的渐进式框架.与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用.Vue 的核心库只关注视图层,不仅易于上手, ...

  7. eureka 之前的服务如何关闭_干货分享 | 服务注册中心Spring Cloud Eureka部分源码分析...

    友情提示:全文13000多文字,预计阅读时间10-15分钟 Spring Cloud Eureka作为常用的服务注册中心,我们有必要去了解其内在实现机制,这样出现问题的时候我们可以快速去定位问题.当我 ...

  8. python怎么模拟浏览器交互_干货分享:python爬虫模拟浏览器的两种方法实例分析(赶紧收藏)...

    今天为大家带来的内容是:干货分享:python爬虫模拟浏览器的两种方法实例分析(赶紧收藏) 文章主要介绍了python爬虫模拟浏览器的两种方法,结合实例形式分析了Python爬虫模拟浏览器的两种常见操 ...

  9. python缺少标准库_干货分享:Python如何自动导入缺失的库

    很多同学在写Python项目时会遇到导入模块失败的情况:ImportError: No module named 'xxx'或者ModuleNotFoundError: No module named ...

最新文章

  1. Fragment中使用viewLifecycleOwner/getActivity/this
  2. 去重的Set解不出“斯诺登的密码”(洛谷P1603题题解,Java语言描述)
  3. 有人说,30岁是程序员的一个末日期,写给30岁的程序员,到底该怎么做呢
  4. Spring.Net配置多数据源
  5. 切图具体需要切什么内容_【切图】UI设计师要懂得切图技巧
  6. 药物用法拉丁文缩写词
  7. 移动端微信、QQ、浏览器调用qq临时会话功能
  8. 【原创】关于改变电脑默认安装地址后桌面快捷键显示“指定路径不存在”错误的解决方法之一
  9. 相关系数、相关指数和回归系数等概念含义
  10. 嗅探原理与反嗅探技术详解
  11. 【突发】Telsa致命车祸细节报告:人为设定超速15%(下载)
  12. [异常]kvm虚拟机卡顿连接不稳定
  13. 微信公众号 主动发生消息给用户
  14. Android心电数据分析,Android 根据心电图(ECG)数据分析绘制心电图
  15. Dava基础Day17
  16. Pinger为iOS版textfree增加语音邮件功能
  17. DDR4相比DDR3的变更点
  18. html5手指测速,网速html5网速测试进度条代码
  19. 【新星计划-2023】ARP“攻击”与“欺骗”的原理讲解
  20. css inport作用,浅谈css和@import区别及用法详解

热门文章

  1. 每日算法C语言1-求某整数
  2. md文件编辑器_File Cabinet Pro for Mac(菜单栏文件管理器)
  3. Java黑皮书课后题第5章:*5.43(数学:组合)编写程序,显示从整数1到7中选择两个数字的所有组合,同时显示所有组合的总个数
  4. 2016秋季阅读计划
  5. 02 docker的基本用法
  6. GridView隐藏列, 并能读取列值的解决方法(转载)
  7. python学习之函数的参数类型
  8. oracle 11g安装教程
  9. SQL AVG() 函数
  10. RTP/RTSP/RTCP 协议详解