微信H5支付--微信JS-SDK支付--点金计划

微信H5支付--微信JS-SDK支付--点金计划

微信H5支付

微信H5支付 :H5支付是指商户在微信客户端外的移动端网页展示商品或服务,用户在前述页面确认使用微信支付时,商户发起本服务呼起微信客户端进行支付(简而言之:从外部浏览器唤起微信进行支付

产品介绍-H5支付 | 微信支付商户平台文档中心pay.weixin.qq.com/wiki/doc/ap...

相关配置参考:H5支付开始指引:pay.weixin.qq.com/wiki/doc/ap...

重点:微信内置浏览器无法使用微信H5支付(需要进行区分环境:微信内置浏览器环境 | 非微信环境);
重点:H5支付不建议在APP端使用(app内嵌,使用app支付);
重点:IOS 某些浏览器唤起微信支付进行成功后,会重新开 safari任务 导致token失效(代码已处理)

H5支付配置

注意:域名必须通过 ICP 备案;
注意:域名填写格式不包含http://或https://

H5支付开发

微信官网推荐H5支付:

JavaScript 复制代码
// payParam 后端返回h5支付链接: https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=*****************&package=*****************
// redirect_url 支付成功、支付失败回调地址(包含参数) 需要urlencode编码
location.href = `${payParam}&redirect_url=${encodeURIComponent(redirect_url)}`;

微信官方推荐存在的问题:

  • 浏览器需要跳转到微信安全页面环境(**存在空白页面**),然后在这个页面打开微信支付;
  • 部分手机支付完成后,浏览器会回退 会回退到微信安全页面(空白,再次拉起微信支付);

针对微信支付安全校验处理(规避以上微信官方存在的问题):

微信为保证支付安全:H5支付需要网站跳转微信安全页面: wx.tenpay.com/cgi-bin/mmp... (后端返回h5支付链接)页面进行唤起微信app支付;

代码如下:

html 复制代码
<!DOCTYPE html>
<html>
<head lang="en">
    <meta http-equiv=Content-Type content="text/html;charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="format-detection" content="telephone=no">
    <title>weixin</title>
    <style>.f10{font-size:10px}.f11{font-size:11px}.f12{font-size:12px}.f13{font-size:13px}.f14{font-size:14px}.f15{font-size:15px}.f16{font-size:16px}.f17{font-size:17px}.f18{font-size:18px}.f19{font-size:19px}.f20{font-size:20px}body{font-size:14px}h1,h2,h3,h4,h5{font-weight:400;font-style:normal}h1,.h1{font-size:20px}h2,.h2{font-size:18px}h3,.h3{font-size:16px}h4,.h4{font-size:14px}h5,.h5{font-size:12px}a,a:visited{color:#007aff}.text_color{color:#888}.title_color{color:#000}.desc{color:#b2b2b2}.warn{color:#b71414}.nickname{color:#576b95}.tips{font-size:13px;color:#b2b2b2}body{background-color:#fff}body.msg_dark{background-color:#2e3132;color:#fff}.page_msg{padding:75px 15px 0;text-align:center}.icon_area{margin-bottom:19px}.text_area{margin-bottom:25px}.text_area .title{margin-bottom:12px}.opr_area{margin-bottom:25px}.extra_area{margin-bottom:20px}@media screen and (min-height:416px){.extra_area{position:fixed;left:0;bottom:0;width:100%}}.btn{display:block;margin-left:auto;margin-right:auto;padding-left:14px;padding-right:14px;font-size:16px;text-align:center;text-decoration:none;overflow:visible;height:40px;border-radius:5px;-moz-border-radius:5px;-webkit-border-radius:5px;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;color:#fff;line-height:40px;-webkit-tap-highlight-color:rgba(255,255,255,0)}.btn.btn_inline{display:inline-block}.btn_default{background-color:#d1d1d1}.btn_default:not(.btn_disabled):visited{color:#fff}.btn_default:not(.btn_disabled):active{color:rgba(255,255,255,.4);background-color:#a7a7a7}.btn_primary{background-color:#04be02}.btn_primary:not(.btn_disabled):visited{color:#fff}.btn_primary:not(.btn_disabled):active{color:rgba(255,255,255,.4);background-color:#039702}.btn_warn{background-color:#ef4f4f}.btn_warn:not(.btn_disabled):visited{color:#fff}.btn_warn:not(.btn_disabled):active{color:rgba(255,255,255,.4);background-color:#c13e3e}.btn.btn_mini{height:25px;line-height:25px;font-size:14px}button.btn,input.btn{width:100%;border:0;outline:0;-webkit-appearance:none}button.btn:focus,input.btn:focus{outline:0}button.btn_inline,input.btn_inline{width:auto}.btn_disabled{color:rgba(255,255,255,.6)}.btn+.btn{margin-top:10px}.btn.btn_inline+.btn.btn_inline{margin-top:auto;margin-left:10px}.btn_area{margin-left:-5px;margin-right:-5px;font-size:0}.btn_area.btn_area_inline{margin-left:auto;margin-right:auto;display:-webkit-box;display:-webkit-flex;display:-moz-box;display:-ms-flexbox;display:flex}.btn_area.btn_area_inline .btn{margin-top:auto;margin-right:10px;width:100%;-webkit-box-flex:1;-webkit-flex:1;-moz-box-flex:1;-ms-flex:1;box-flex:1;flex:1;display:inline-block \9;width:48% \9;margin-left:1% \9;margin-right:1% \9}.btn_area.btn_area_inline .btn:last-child{margin-right:0}span.btn button{display:block;width:100%;height:100%;background-color:transparent;border:0;outline:0;color:#fff}span.btn button:active{color:rgba(255,255,255,.4)}span.btn.btn_loading button,span.btn.btn_disabled button{color:#fff}.icon_msg{width:100px;height:100px;vertical-align:middle;display:inline-block;border-radius:50%;-moz-border-radius:50%;-webkit-border-radius:50%}.icon_msg.warn{background-color:#f86161;color:#fff;font-size:60px;font-style:normal}html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{font:14px/1.5em "Helvetica Neue",Helvetica,Arial,sans-serif;background-color:#efeff4;line-height:1.6}body,h1,h2,h3,h4,h5,p,ul,ol,dl,dd,fieldset,textarea{margin:0}fieldset,legend,textarea,input,button{padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;*font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}ul,ol{padding-left:0;list-style-type:none}a img,fieldset{border:0}a{text-decoration:none}</style>
</head>
<body>
<div class="body">
    <div id="errpage" class="page_msg">
                <div class="icon_area"><i class="icon_msg warn">!</i></div>
        <div class="text_area">
            <h2 id="111" class="title">支付请求已失效,请重新发起支付</h2>
        </div>
            </div>
</div>
<script type="text/javascript">

    var is_postmsg="";
    if( 1!==0 && is_postmsg=="1" )
    {
        parent.postMessage(JSON.stringify({
            action : "send_deeplink_fail",
            data : {
                        deeplink : ""  // 成功 会有值: 
                   },
            error : {
                error_code : "1",
                error_msg : "支付请求已失效,请重新发起支付"
                    }
            }), "");
    }
    if( 1===0)
    {
        window.onload=function()
        {
            {
                var is_postmsg="";
                if(is_postmsg=="1")
                {
                    parent.postMessage(JSON.stringify({
                        action : "send_deeplink",
                        data : {
                            deeplink : "" // 成功 会有值: 
                        }
                    }), "");
                }
                else
                {
                    var url="";
                    var redirect_url="";
                    top.location.href=url;

                    if(redirect_url)
                    {
                        setTimeout(
                            function(){
                                top.location.href=redirect_url;
                            },
                            5000
                        );
                    }
                    else
                    {
                        setTimeout(
                            function(){
                                window.history.back();
                            },
                            5000);
                    }
                }
            }
            // );
        }
    }
</script>
</body>
</html>

以上代码解析:

deeplink 为深链接( URL Scheme),可以通过window.open('deepink') 或者 location.href = 'deepink'打开app
redirect_url 为回调地址(支付成功 | 支付失败), 5S默认跳转redirect_url地址; 没有配redirect_url 则返回上一级页面;

以下代码,由以微信安全支付页面代码解析得来:

nginx 配置转发服务:

nginx.config 复制代码
 location /cgi-bin/ {
    # https://wx.tenpay.com/cgi-bin/ 微信支付域名(禁止跨域)
    proxy_pass https://wx.tenpay.com/cgi-bin/;
    # Referer 微信商户安全支付域名(根据自己商户进行配置)
    proxy_set_header Referer "https://payh5-test.dab.tech";
}

接口请求:

JavaScript 复制代码
import axios from 'axios'; // axios 
/**TODO: 微信支付(绕过微信安全校验,跳转url) */
export const wxPay = (url) => {
  return axios({
    method: 'GET',
    url,
    headers: {
      'Content-Type': 'text/html; charset=utf-8',
    },
  });
};

支付代码:

JavaScript 复制代码
/**
 * @name isAndroid Is the environment 安卓
 * @return {boolean} True if value is an Android, otherwise false
 */
 const isAndroid = () => {
  const u = navigator.userAgent;
  return u.indexOf('Android') > -1 || u.indexOf('Adr') > -1;
};
/**深度链接正则 */
const deepLinkRule = /deeplink\s*:\s*"([^"]+)"/;
const iframeId = 'iframe-pay';
// 绕过微信安全校验失败
const WxApiError = (res, path) => {
    if (isAndroid()) {
      const iframe = document.createElement('iframe');
      document.body.appendChild(iframe);
      iframe.style.position = 'absolute';
      iframe.style.left = '-9999px';
      iframe.allow = 'payment';
      iframe.setAttribute('sandbox', 'allow-top-navigation allow-scripts');
      iframe.id = iframeId;
      iframe.src = `${res.payParam}&redirect_url=${encodeURIComponent(path)}`;
    } else {
      // ios 禁止iframe 顶层操作
      location.replace(`${res.payParam}&redirect_url=${encodeURIComponent(path)}`);
    }
};

// 支付
const { origin } = window.location;
// TODO: safari浏览器时支付完成后会新开一个页面(token不存在), 因此需要在url上设置token,保证系统运行
// TODO: orderSn: 订单号;请根据业务需求
// TODO: token:系统登录token;请根据业务需求
const token = '*!@&#26duefsdyki$&*!';
const path = `${origin}/#/order-detail?orderSn=${params.orderNo}&token=${token}`;
try {
    wxPay(`cgi-bin/${res.payParam.split('cgi-bin/')[1]}`)
    .then((response) => {
      const match = response.data.match(deepLinkRule);
      if (match && match.length > 1) {
        const deepLinkValue = match[1];
        location.href = deepLinkValue;
        setTimeout(function () {
          window.top!.location.href = path;
        }, 5000);
      } else {
        console.log('未找到deepLinkValue的值');
        WxApiError(res, path);
      }
    })
    .catch(() => WxApiError(res, path));
} catch (_) {
    WxApiError(res, path);
}

H5支付常见异常问题:

1. 商家参数格式有误,请联系商家解决:一般referer转发为空导致的;请参考上面:nginx服务转发配置;
2. 商家存在未配置的参数,请联系商家解决: 一般是因为支付域名配置原因导致;请登录微信支付商户端获取H5支付域名,并进行配置;
3. 支付请求已失效,请重新发起支付: 支付url有效期为5min,重新调用接口即可;

微信JS-SDK支付

JSAPI 支付是指商户通过调用微信支付提供的JSAPI接口,在支付场景中调起微信支付模块完成收款(为了满足微信内置浏览器唤起微信支付);

[JSAPI支付产品介绍 | 微信支付商户平台文档中心 ]( pay.weixin.qq.com/wiki/doc/ap... )

相关配置参考:[JSAPI支付开发指引 ] ( pay.weixin.qq.com/wiki/doc/ap...

注:需要提前申请微信公众号; 特约商户支付成功回调微信已收回,需要配置点金计划(参考下文);

微信JS-SDK支付配置

  • 登录 微信公众平台(例如公众号:wx2421b1c4370ec43b )

  • 登录 微信公众平台 ---> 公众号设置(申请认证)

  • 登录 微信公众平台 ---> 基本配置(生成AppSecret, 服务端使用)

  • 登录 微信公众平台 ---> 基本配置(配置ip白名单, 服务端使用)

  • 登录 微信公众平台 ---> 公众号设置 ---> 功能设置 (配置js接口域名安全, 提供后端服务调用)

  • 登录 微信公众平台 ---> 公众号设置 ---> 功能设置 (网页授权域名,提供前端微信公众号授权登录)

  • 登录 微信公众平台 ---> 公众号设置 ---> 功能设置 ---> 网页授权域名 ---> 设置 (下载文件,放置服务器根目录)

  • 登录 微信支付商户平台

  • 登录【 微信支付商户平台 --->产品中心--->开发配置】,配置;设置后一般5分钟内生效;

  • 进行JSAPI支付域名 ;

微信JSAPI(JS-SDK)支付开发

微信公众号网页授权请参考: 网页授权 | 微信开放文档 (qq.com)
JS-SDK请参考:概述 | 微信开放文档 (qq.com)
JS-SDK支付请参考:微信支付-开发者文档 (qq.com)

Plan A(微信前期写法)

  • 微信授权登录(公众号授权,js-sdk注入用于支付):
JavaScript 复制代码
// 微信公众号授权登录参考:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
const State = 'state' // 需要带参数 请自定义
const ScopeEnum =  {
  /**不弹出授权页面,直接跳转,只能获取用户openid */
  BASE = 'snsapi_base',
  /**弹出授权页面,可通过openid拿到昵称、性别:强制 0、所在地:强制 ""。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 */
  USERINFO = 'snsapi_userinfo',
}
const oauth = ({
    redirectUri = location.href, // 授权成功后重定向地址(需要进行编码)
    scope = ScopeEnum.USERINFO, // 应用授权作用域
    state = State, // 重定向后会带上state参数(最多128字节)
}) => {
    const url =  encodeURIComponent(redirectUri);
    window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx2421b1c4370ec43b&redirect_uri=${url}&scope=${scope}&response_type=code&state=${state}#wechat_redirect`;
};

微信公众号授权snsapi_userinfo获取返回值有所变化;变化如下

重点: 授权地址不可以有端口号;
重点:授权成功后重定向地址带上code参数;
重点: hash路由会导致code参数再地址拼接异常(例如:https://baidu.com/#/order?id=123123 授权后会变成 https://baidu.com/?code=123123123123#/order?id=123123 需要特殊处理,才能获得code);

  • 支付代码:
JavaScript 复制代码
 // 微信支付
wx.chooseWXPay({
    "appId": "wx2421b1c4370ec43b // 公众号ID,由商户传入 (需要与公众号授权登录的appId保持一致) 
    "timestamp": "1395712654",  // 时间戳,自1970年以来的秒数  (timestamp 's' 是小写的 服务端返回的是一般大写的)   
    "nonceStr": "e61463f8efa94090b1f366cccfbbb444",      // 随机字符串     
    "package": "prepay_id=wx21201855730335ac86f8c43d1889123400",
    "signType": "RSA",     //微信签名方式:     
    "paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq/xDg==" //微信签名 
 })

重点:微信支付---1方式 需要微信授权登录,并且需要js-sdk的注入(SPA程序 最好在router处理);
重点:timestamp: 是小写的,后端一般返回的是驼峰命名的;需要注意;
重点:微信支付---1方式 特约商户需要保证js-sdk授权注入的appid与商户授权的appid保持一致(如果不一致,请使用微信支付---2方法)

Plan B(微信目前推荐使用)

  • 支付代码:
JavaScript 复制代码
// 微信官方js-sdk支付请求示例
function onBridgeReady() {
      WeixinJSBridge.invoke('getBrandWCPayRequest',{
          "appId": "wx2421b1c4370ec43b", //公众号ID,由商户传入
          "timeStamp": "1395712654", //时间戳,自1970年以来的秒数
          "nonceStr": "e61463f8efa94090b1f366cccfbbb444", //随机串
          "package": "prepay_id=up_wx21201855730335ac86f8c43d1889123400",
          "signType": "RSA", //微信签名方式:
          "paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq/xDg==" //微信签名 
        },
        function(res) {
            // TODO:特约商户已被微信收回支付成功回调 ;
            // 商户配置了点金计划,支付成功后直接跳转点金计划;关闭了点金计划,直接关闭程序
            if (res.err_msg == "get_brand_wcpay_request:ok") {
                // 使用以上方式判断前端返回,微信团队郑重提示:
                // res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
            }
          });
      }
      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();
      }

重点:微信支付---1方式 timeStamp 是驼峰命名规则;
重点:特约商户支付成功后不会执行 ·res.err_msg == "get_brand_wcpay_request:ok"· 方法;会默认跳转 点金计划 或 退出 微信浏览器

JSAPI(JS-SDK)支付常见异常问题:

  • 签名错误:

    1. 使用wx.chooseWXPay 中 timestamp是小写, 可能采用了驼峰大写S
    2. 使用JSAPI支付 config注入得appid 与 wx.chooseWXPay 传输的appid不一致;
    3. 后端签名生成错误;请后端进行签名校验;

点金计划

点金计划,就是微信支付官方提供的支付(JS-SDK)后回调能力的升级计划(被一些商户乱用导致的😑), 支付平台提供支付后页面模板(使用iframe进行嵌套的),支持账单展示、服务性内容展示、流量位推广(广告推广无孔不入呀)等功能。

点金计划配置

自定义开发点金计划中商家小票页面(支付成功界面)

  • 配置商家小票连接之前需要下载配置文件到服务器里(同上文微信授权一致)

特约商户开发的JS-SDK微信支付会默认加入点金计划(微信支付完成后会进入官方小票界面,res.err_msg == "get_brand_wcpay_request:ok"逻辑将不再继续执行, 支付失败的逻辑会继续执行),如果关闭了点金计划,则在支付完成后直接关闭窗口😒!!!)

默认会是这个样子:

点金计划开发

尝试着写一个自定义点金计划小票页面(支付成功后会跳转微信商户平台提供的:payapp.weixin.qq.com , 开发页面以iframe的形式被嵌套);

那这个时候是不是就很好奇了,怎么去 开发呢???
首先要明确点金计划提供了哪些功能,api让我们去操作;

点金计划请参考: 微信支付点金计划产品文档(wx.gtimg.com/pay/downloa...)

从《微信支付点金计划产品文档》中可以知晓:点金计划页面上方为商家小票提供了一个 Iframe 框架,服务商在点金计划管理页面配置"商家小票链 接"后,商家小票链接会嵌入该 Iframe 框架内,同时,点金计划页面会与服务商交互订单信息;

点金计划页面会提供与服务商交互订单的三个信息:特约商户号(sub_mch_id)商户订单号(out_trade_no)md5 校验码 (check_code)三个字段的信息;

例如:

html 复制代码
<!-- src: 商家小票(支付成功)页面路径;  -->
<iframe src="https://example.org?sub_mch_id=2039471652&out_trade_no=T123444303844372&check_code=55304935873921" >
</iframe>

注:由于是iframe实现内嵌商家小票页面;所以不建议商家小票界面使用缓存数据;建议走后端接口

其中:src 'example.org' 为支付商家小票界面(支付成功界面);

'sub_mch_id=2039471652&out_trade_no=T123444303844372&check_code=55304935873921'为微信支付点金计划产品提供的订单信息;可以根据这三个信息去与后端进行交互;

为了商户更灵活的操作点金计划,《微信支付点金计划产品》提供提供了点金计划页面JSAPI

以下介绍两个API
onIframeReady : 按订单确认是否展示商家小票、调整 Iframe 框架高度(以宽度 640px 为基准,商家小票高度最小 600px,最大 960px。若服务商通过 jsapi 传入高度小 于 600px,页面会展示 600px,传入大于 960px,页面仅展示 960px)

重点:加载商家小票到响应 onIframeReady 事件之间的用时不可超过 3s ,因此需要考虑前端框架设计(多数使用html进行设计
重点: 所有订单必须响应 onIframeReady 事件,否则无法正常进行商家小票的展示
重点: 通信方式:iframe postMessage 事件通知 数据格式:json 字符串

示例:(展示商家小票)

js 复制代码
    /** 点金计划路径 */
    var GOIDPLAN_URL = "https://payapp.weixin.qq.com";
    /** 点金计划事件列表: 通信方式:iframe postMessage 事件通知 */
    var ACTION_LIST = {
      /**渲染小票:从加载商家小票到响应 onIframeReady 事件之间的用时不可超过 3s */
      onIframeReady: "onIframeReady",
      /**外跳:商家页面有外跳需求,且用户主动点击时(此处不可模拟用户点击自动外跳,否则相关能力会被处罚) */
      jumpOut: "jumpOut",
    };
    /** 点金计划需展示的页面 */
    var DISPLAY_STYLE = {
      商家小票: "SHOW_CUSTOM_PAGE",
      官方小票: "SHOW_OFFICIAL_PAGE",
    };

    /** onIframeReady 事件配置 */
    var onIframeReady = JSON.stringify({
      action: ACTION_LIST.onIframeReady,
      displayStyle: DISPLAY_STYLE.商家小票,
    });
    parent.postMessage(onIframeReady, GOIDPLAN_URL);

其中parent为 window.parent,是当前窗口的父窗口对象

jumpOut : 外跳新页面(支持从点金计划页面上方的商家小票区域,点击外跳到商家的完 整页面(此处不可模拟用户点击自动外跳,否则相关能力会被处罚))

重点: 通信方式:iframe postMessage 事件通知 数据格式:json 字符串
重点: 触发时机:商家页面有外跳需求,且用户主动点击时(此处不可模拟用户点击自动外跳,否则相关 能力会被处罚)

示例:(实现外跳)

html 复制代码
    <button class="button" id="viewDetail" @click="handleJump">
        点击查看订单详情
    </button>
js 复制代码
     /** 点金计划路径 */
    var GOIDPLAN_URL = "https://payapp.weixin.qq.com";
    /** 点金计划事件列表: 通信方式:iframe postMessage 事件通知 */
    var ACTION_LIST = {
      /**渲染小票:从加载商家小票到响应 onIframeReady 事件之间的用时不可超过 3s */
      onIframeReady: "onIframeReady",
      /**外跳:商家页面有外跳需求,且用户主动点击时(此处不可模拟用户点击自动外跳,否则相关能力会被处罚) */
      jumpOut: "jumpOut",
    };
    /** 点金计划需展示的页面 */
    var DISPLAY_STYLE = {
      商家小票: "SHOW_CUSTOM_PAGE",
      官方小票: "SHOW_OFFICIAL_PAGE",
    };
    //获取返回页面参数
    function getQueryString(name) {
      var query = window.location.search.substring(1);
      var vars = query.split("&");
      for (var i = 0; i < vars.length; i++) {
        var pair = vars[i].split("=");
        if (pair[0] == name) {
          return pair[1];
        }
      }
      return null;
    }

    /**商户订单号*/
    var out_trade_no = getQueryString("out_trade_no");
     /**查看订单详情*/
    btnEl.addEventListener("click", () => {
     var jumpOutUrl = `${location.origin}/#/order-detail?orderSn=${orderNoEl.innerText}`;
      var mchData = JSON.stringify({
        action: ACTION_LIST.jumpOut,
        jumpOutUrl: jumpOutUrl,
      });
      console.log("jumpOutUrl==>", jumpOutUrl);
      parent.postMessage(mchData, GOIDPLAN_URL);
    });

以下为完整代码(以下代码没有做md5校验,如有需要自行添加)示例:

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>支付完成</title>
    <script
      type="text/javascript"
      charset="UTF-8"
      src="https://wx.gtimg.com/pay_h5/goldplan/js/jgoldplan-1.0.0.js"
    ></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/vConsole/3.15.0/vconsole.min.js"></script>
    <style>
      body {
        margin: 0;
      }
      .pay-result {
        padding: 8.53333vw;
        text-align: center;
      }
      .order-info {
        padding-top: 8vw;
        padding-bottom: 7.73333vw;
        font-size: 3.73333vw;
        color: #595959;
      }
      .order-info .item {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 6.4vw;
      }
      .button {
        width: 100%;
        height: 11.73333vw;
        font-size: 4.26667vw;
        background: #2c68ff;
        line-height: 11.73333vw;
        color: #ffffff;
        border-radius: 50px;
        border: none;
      }
    </style>
  </head>
  <body>
    <section class="pay-result">
      <div class="order-info">
        <div class="item">
          <span class="label">订单号</span>
          <span class="value" id="orderNo"></span>
        </div>
        <div class="item">
          <span class="label">订单金额</span>
          <span class="value" id="payPrice"></span>
        </div>
      </div>
      <button class="button" id="viewDetail" @click="handleJump">
       点击查看订单详情
      </button>
    </section>
  </body>
  <script>
    /** 点金计划路径 */
    var GOIDPLAN_URL = "https://payapp.weixin.qq.com";
    /** 点金计划事件列表: 通信方式:iframe postMessage 事件通知 */
    var ACTION_LIST = {
      /**渲染小票:从加载商家小票到响应 onIframeReady 事件之间的用时不可超过 3s */
      onIframeReady: "onIframeReady",
      /**外跳:商家页面有外跳需求,且用户主动点击时(此处不可模拟用户点击自动外跳,否则相关能力会被处罚) */
      jumpOut: "jumpOut",
    };
    /** 点金计划需展示的页面 */
    var DISPLAY_STYLE = {
      商家小票: "SHOW_CUSTOM_PAGE",
      官方小票: "SHOW_OFFICIAL_PAGE",
    };

    /** onIframeReady 事件配置 */
    var onIframeReady = JSON.stringify({
      action: ACTION_LIST.onIframeReady,
      displayStyle: DISPLAY_STYLE.商家小票,
    });
    // 接口地址
    var api = "/api/circ/order/queryOrderSn";

    /**添加调试工具vConsole*/
    new window.VConsole({
      theme: "dark",
      maxLogNumber: 100,
    });

    //获取返回页面参数
    function getQueryString(name) {
      var query = window.location.search.substring(1);
      var vars = query.split("&");
      for (var i = 0; i < vars.length; i++) {
        var pair = vars[i].split("=");
        if (pair[0] == name) {
          return pair[1];
        }
      }
      return null;
    }

    /**商户订单号*/
    var out_trade_no = getQueryString("out_trade_no");
    /**button*/
    var btnEl = document.getElementById("viewDetail");
    /**订单金额El*/
    var payPriceEl = document.getElementById("payPrice");
    /**订单号El*/
    var orderNoEl = document.getElementById("orderNo");
    parent.postMessage(onIframeReady, GOIDPLAN_URL);

    console.log("out_trade_no====>", out_trade_no);
    if (out_trade_no) {
      // 此处为接口请求
      fetch(api, {
        method: "POST",
        mode: "cors",
        headers: {
          "Content-Type": "application/json",
          clientCode: "CONSUMER",
        },
        body: JSON.stringify({ payTradeNo: out_trade_no }),
      })
        .then((response) => {
          response.json().then((res = {}) => {
            console.log("res====>", res);
            if (res.code !== 0) {
              parent.alert(res.msg);
              return;
            }
            var data = res.data ? res.data : {};
            console.log("data====>", data);
            orderNoEl.innerText = data.orderNo;
            payPriceEl.innerText = data.payPrice + "元";
          });
        })
        .catch((err) => console.error(err));
    }

    /**查看订单详情*/
    // TODO: location.origin 因为点金计划和项目在同一个域名下;
     btnEl.addEventListener("click", () => {
         var jumpOutUrl = `${location.origin}/#/order-detail?orderSn=${orderNoEl.innerText}`;
          var mchData = JSON.stringify({
            action: ACTION_LIST.jumpOut,
            jumpOutUrl: jumpOutUrl,
          });
          console.log("jumpOutUrl==>", jumpOutUrl);
          parent.postMessage(mchData, GOIDPLAN_URL);
    });
  </script>
</html>

点金计划常见问题

  • 支付成功后,跳转的是无法获取订单信息:

    1. 商户未配置点金计划商家小票连接;
    2. 商户小票页面加载缓慢,超过3s未调用nIframeReady事件;
    3. IOS无法加载商家小票,使用https;
    4. 点金计划配置文件(xxx.txt)未放置在服务器中;

虽然微信提供了点金计划页面JSAPI,但是还是有不如意的地方:

  • 不如意一:无法返回上一页,只能是关闭当前页面(商家小票页面属于新开窗口....)
  • 不如意二:点金计划无法配置多个地址 同一个项目无法针对不环境配置不同小票路径
  • 不如意三:商家小票页面jumpOut事件触发后出现的弹窗会显示商户名称
  • ... 剩下的 你们补充吧!!!

溜了溜了~~~~

相关推荐
hackeroink1 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者2 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-3 小时前
验证码机制
前端·后端
燃先生._.4 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235245 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240256 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar6 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人6 小时前
前端知识补充—CSS
前端·css