Vue Java 微信JS API 支付

一. 背景

最近在做Java整和微信JSAPI支付,遇到一些问题,现在把相关的注意点记录下,供大家参考,如有不对,还请指正。

二. 开发前准备工作

  1. 采用vue + springboot + weixin-js-sdk(1.6.0)
  2. 微信公众号(appId,appSecret)
  3. 微信公众号开通微信支付,具有商户号(mch_id)
  4. 微信支付API密钥(appKey).

三. 相关信息配置获取方式:

1. appKey,appSecret:

登录微信公众号 > 开发 > 基本配置 > 公众号开发信息

记得把服务器加入IP白名单
image.png

2. 配置JS接口安全域名,网页授权域名:

登录微信公众号 > 设置 > 公众号设置 > 功能设置

image.png

3. 商户号 mch_id:
登录微信公众号 > 微信支付 > 商户号管理 > 已关联商户号

image.png

4. 微信支付API密钥(appKey):
登录微信支付 > 账户中心 > API安全
如果没有API证书,则需要申请证书再设置API密钥

image.png

5. 支付授权目录配置:
登录微信支付 > 产品中心 > 开发配置
这里必须配置,不然支付的时候会弹出xxx页面未注册,我们配置JSAPI支付授权目录,新版可以支付域名根目录配置,例如你的支目录为 http://xxx.com/wx/pay,这里可...://xxx.com/ 注意最后的一个斜线,代表根目录,不能省略。

至此,配置工作全部完成。

四. 编码阶段

1. 引入 weixin-js-sdk
html方式: (支持https):http://res.wx.qq.com/open/js/...
npm方式: npm install weixin-js-sdk
使用 import wx from 'weixin-js-sdk'
建议使用微信支付sdk https://pay.weixin.qq.com/wik...
2. 通过config接口注入权限验证配置
所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客户端不支持pushState的H5新特性,所以使用pushState来实现web app的页面会导致签名失败,此问题会在Android6.2中修复)

wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '', // 必填,公众号的唯一标识 公众号已经获取到
timestamp: '', // 必填,生成签名的时间戳 WXPayUtil.getCurrentTimestamp()
nonceStr: '', // 必填,生成签名的随机串 WXPayUtil.generateNonceStr()
signature: '',// 必填,签名 需要获取jsapi_ticket,access_token,详情见官方文档 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#62
jsApiList: [] // 必填,需要使用的JS接口列表 ['chooseWXPay']
})

第一步.获取access_token:

https请求方式: GET https://api.weixin.qq.com/cgi...

第二步.获取jsapi_ticket:

https请求方式: GET https://api.weixin.qq.com/cgi...
后台代码示例:
@ResponseBody
@GetMapping(value = "/getConfigInfo", produces = MediaType.APPLICATION_JSON_VALUE)
public Object getConfigInfo(@RequestParam(name = "url") String url) {
    final String accessTokenResult = ticketClient.getAccessToken(APP_ID, APP_SECRET);
    final JSONObject tokenResult = JSON.parseObject(accessTokenResult);
    final String accessToken = tokenResult.getString("access_token");
    log.info("获取到的access_token为: {} ", accessToken);
    final String jsTicketResult = ticketClient.getTicket(accessToken);
    final JSONObject ticketResult = JSON.parseObject(jsTicketResult);
    final String ticket = ticketResult.getString("ticket");
    log.info("获取到的ticket为: {} ", ticket);
    final Map map = JsApiSignUtil.signature(ticket, url);
    log.info("map : {}", map);
    map.put("appId", APP_ID);
    return map;
}

输出结果:
image.png
前端代码示例:

//微信JSSDK授权配置
        config(data) {
            wx.config({
                debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
                appId: data.appId, // 必填,公众号的唯一标识 公众号已经获取到
                timestamp: data.timestamp, // 必填,生成签名的时间戳 WXPayUtil.getCurrentTimestamp()
                nonceStr: data.nonceStr, // 必填,生成签名的随机串 WXPayUtil.generateNonceStr()
                signature: data.signature,// 必填,签名 需要获取jsapi_ticket,access_token,详情见官方文档 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#62
                jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表 ['chooseWXPay']
            })
            //接口授权检查
            wx.checkJsApi({
                jsApiList: ['chooseWXPay'], // 需要检测的JS接口列表,所有JS接口列表见附录2,
                success: function (res) {
                    // 以键值对的形式返回,可用的api值true,不可用为false
                    // 如:{"checkResult":{"chooseImage":true},"errMsg":"checkJsApi:ok"}
                }
            });
        },

至此,通过config接口注入权限验证配置全部完成。
3. 商户server调用统一下单接口请求订单
这里需要用到到一个重要的参数 openId,详情见官方文档https://developers.weixin.qq....
第一步:用户同意授权,获取code
引导用户打开 https://open.weixin.qq.com/co...

appid: 公众号的唯一标识 公众号已经获取到
redirect_uri: 授权回调的地址
response_type:  返回类型,请填写code
scope: 应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息
state: 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
#wechat_redirect: 无论直接打开还是做页面302重定向时候,必须带此参数

后台代码示例:

/**
 * 网页授权跳转
 *
 * @param response
 * @throws IOException
 */
@GetMapping(value = "/authorize", produces = MediaType.APPLICATION_JSON_VALUE)
public void authorize(HttpServletResponse response) throws IOException {
    String redirectUrl = "http://miao.com/wx/accessTokenWeb";
    String state = UUID.randomUUID().toString().replace("-", "");
    final String authorizeUrl = unifiedOrderClient.authorizeUrl(APP_ID, redirectUrl, state);
    response.sendRedirect(authorizeUrl);
}

第二步:通过code换取网页授权access_token
获取code后,请求以下链接获取access_token: https://api.weixin.qq.com/sns...

   appid: 公众号的唯一标识 公众号已经获取到
   secret: 公众号appSecret 公众号已经获取到
   code: 第一步获取到的code
   grant_type: authorization_code 

后台代码示例:

/**
 * 网页授权获取 openId 我这里为了测试直接简化把openId放在url返回到pay页面,实际开发应存储在后台并从后台获取
 *
 * @param code
 * @param state
 */
@GetMapping(value = "/accessTokenWeb", produces = MediaType.APPLICATION_JSON_VALUE)
public void accessTokenWeb(@RequestParam(name = "code") String code, @RequestParam(name = "state") String state, HttpServletResponse response) throws IOException {
    final String accessTokenResult = unifiedOrderClient.accessTokenWeb(code);
    final JSONObject resultJson = JSON.parseObject(accessTokenResult);
    response.sendRedirect("http://miao.com/pay.html?openId=" + resultJson.getString("openid"));
}

至此,openId获取全部完成。
发起统一下单接口:
前端代码示例:

// 统一下单
        unifiedOrder() {
            const that = this
            const data = {
                params: {
                    total: this.total,
                    openId: this.openId
                }
            }
            axios.get('/wx/unifiedOrder', data).then((response) => {
                that.pay(response.data)
            })

        },
        pay(data) {
            wx.chooseWXPay({
                timestamp: data.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
                nonceStr: data.nonceStr, // 支付签名随机串,不长于 32 位
                package: data.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
                signType: data.signType, // 签名方式,必须跟统一下单接口签名方式一致
                paySign: data.paySign, // 支付签名
                success: function (res) {
                    if (res.errMsg == "chooseWXPay:ok") {
                        //支付成功后跳转的页面
                        console.log('支付成功')
                    }
                },
                // 支付取消回调函数
                cencel: function (res) {
                    console.log('支付取消')
                },
                // 支付失败回调函数
                fail: function (res) {
                    console.log('支付失败')
                }
            });
        }

后台代码示例:

/**
 * 统一下单接口
 *
 * @param openId
 * @param total
 * @return
 * @throws Exception
 */
@ResponseBody
@GetMapping(value = "/unifiedOrder", produces = MediaType.APPLICATION_JSON_VALUE)
public Object unifiedOrder(@RequestParam(name = "openId") String openId, @RequestParam(name = "total") BigDecimal total, HttpServletRequest request) throws Exception {
    WXPayConfig config = new WxPayConfig(APP_ID, MCH_ID, APP_KEY, DOMAIN_API);
    WXPay wxpay = new WXPay(config);
    Map data = new HashMap();
    data.put("body", "支付0.01元");
    //订单号,这里自己生成 ,对应自己实际唯一订单号 最长32位
    data.put("out_trade_no", UUID.randomUUID().toString().replace("-", ""));
    data.put("device_info", "WEB");
    data.put("fee_type", "CNY");
    //单位分,所以要把金额*100
    data.put("total_fee", total.multiply(new BigDecimal(100)).intValue() + "");
    data.put("spbill_create_ip", request.getRemoteAddr());
    //支付成功微信回调地址,见 com.stone.demo.wxpay.controller.WxController.notify
    data.put("notify_url", "http://miao.com/wx/notify");
    // 此处指定为JS API支付
    data.put("trade_type", "JSAPI");
    data.put("openid", openId);
    Map resp = wxpay.unifiedOrder(data);
    log.info("统一下单返回值: {}", resp);
    //封装返回参数
    return JsApiSignUtil.paySign(APP_ID, resp.get("prepay_id"), APP_KEY);
}

至此,微信支付开发完成。

五. 最后

因为篇幅有限,有些代码没有放在上面,如果大家想要源码,可以在下面两个地址下载,如有不正确的地方,请指正。
gitee: https://gitee.com/HuiGuoGuo/w...
github: https://github.com/HuiGuoGuo/...

你可能感兴趣的:(java,微信js-sdk,微信支付)