实战: 聚合收银台-手把手教你uniapp小程序间跳转以及webview接sass服务,完成支付等;

404 阅读5分钟

1.聚合收银台-手把手教你uniapp小程序间跳转以及webview接sass服务,完成支付等;

选型:vue3+typeScript+uniapp

原因:符合旧技术栈;结合ts校验,方便数据格式确定; 需要同时开发h5端和小程序端; 插件支付审核未通过,只能以小程序的形式;

1.目的

公司旗下有多个小程序未来可能都涉及到支付,有的小程序未开通支付功能,或者支付的appId与商户号不符等无法在本app内部发起支付,因此想要开发一个app专做公司的聚合支付,包括小程序支付,h5支付,小程序内嵌webview支持支付等。

2.需求

在聚合收银台需要满足以下几个场景:

1.收银台小程序内部可以选择是积分支付还是现金支付;

2.如果选择积分支付,积分充足直接积分支付,积分不足先用剩余积分支付一部分,剩余金额拉起微信支付;

3.从小程序跳转过来,拉起支付小程序,支付结束后跳转回原小程序;

4.从webview跳转,跳转进编译好的h5页面,选择支付方式后如果涉及到现金支付,跳转回原小程序页拉起支付小程序,直接进入微信支付流程;

5.从微信公众号等h5页进入,跳转到h5页面,通过jssdk拉起微信支付(暂无需实现);

3.流程图(前两种)

image-20240715112941468.png

4.功能实现

1.跳转小程序支付

从小程序a跳转到支付小程序;

1.原小程序

选购好商品,在支付详情页生成该商品的参数信息,携带生成信息跳转到支付小程序

// 小程序a订单使用
export function orderJumpToMini(res) {
  return new Promise((resolve, reject) => {
    uni.navigateToMiniProgram({
      appId: PAY_APPID, //收银小程序appid
      path: 'pages/pay/pay', //收银小程序中对应的支付页
      extraData: {
       goodsInfo: goodsInfo
      },
      envVersion: env === 'production' ? 'release' : 'trial',
      success(res) {
        resolve();
      },
      fail(err) {
        reject(err);
      }
    });
  });
}
​
//在调用函数的地方通过.catch,.then来进入对应的事件处理
2.收银-小程序

调起微信支付的情况

 requestWxPay(params: ToPayParams) {
  uni.requestPayment({
    provider: "wxpay",
    orderInfo: "orderInfo",
    timeStamp: params?.timeStamp,
    nonceStr: params?.nonceStr, // 支付签名随机串,不长于 32 位
    package: params?.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
    signType: params?.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
    paySign: params?.paySign, // 支付签名
    success: (res) => {
     //告知支付成功的处理
    },
    fail: (err) => {
     //告知支付失败的处理
    },
    complete: (res) => {
     //集成处理支付结束的情况
     this.backToOrigin();
    },
  });
},

调用返回原小程序方法,携带extraData回去,告诉原小程序当前订单支付状态

uni.navigateBackMiniProgram({
    extraData: {
      state: this.afterPayState 
    },
    success(res) {
      console.log("返回原小程序页成功");
    },
  });

2.webview跳转支付(注意坑)

注:在webview内部是无法通过jssdk拉起微信原生支付,且无法跳转到别的小程序的,所以需要跳转到小程序原生页面,然后再跳转支付小程序。

1.收银-H5

1.webview在结算时会通过路由重定向,携带参数跳到收银h5页

App.vue
//判断当前为h5环境时
//!options?.referrerInfo?.appId(未携带小程序appid,非小程序跳转)
// #ifdef H5 (条件编译) 
   //h5平台的代码
// #endif
originWay = "H5";
const query = options.query;
orderData = {
  goodsInfo:goodsInfo
};
//存储到全局或者状态机中

2.选择对应支付方式

1.如果积分充足,直接支付接口积分支付成功,无需微信原生能力支持,重定向到原sass的支付结果页;

if (this.payPlace === PayPlace.H5) {
    //积分充足时(不存在微信支付参数的情况)
      const jumpUrl = await this.getPayOverH5Url();
      window.location.replace(jumpUrl);  //jumpUrl是需要跳转到的h5的路由
}

2.若积分不足,需要用微信补足现金部分

调后台支付接口,后台会返回小程序支付需要的支付参数:payInfo

携带支付参数,跳转到原小程序空白页(触发onMessage事件),拉起支付小程序,发起微信支付

注意:小程序webview与小程序间的数据传递需要通过postMessage来完成(wx.miniProgram.postMessage),且需要特定时机触发

属性类型默认值必填说明最低版本
bindmessageeventhandler网页向小程序 postMessage 时,会在以下特定时机触发并收到消息:小程序后退、组件销毁、分享、复制链接(2.31.1)。e.detail = { data },1.6.4

在uniapp中会用到uni.webview.1.5.5.js(原生小程序开发下载jssdk)

注意:uniapp中引入uni.webview.1.5.5.js文件时,跟其余的script标签不同,其script标签应该放置于标签后

<body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
</body>
<script src="https://xxx/jweixin-1.6.0.js"></script> 
<script
    type="text/javascript"
    src="https://xxx/uni.webview.1.5.5.js"
></script>
<script>
    document.addEventListener("UniAppJSBridgeReady", function () {
      uniWebview.getEnv(function (res) {
        console.log("当前环境:" + JSON.stringify(res));
      });
    });
</script>
/**
 * @description: 传参给原小程序
 * @param {*} params
 * @return {*}
 */
async h5PostMessage(params: ToPayParams) {
  const jumpUrl = await this.getJumpUrl();
  uniWebview.redirectTo({
    url: "/pages/xxx/xxx", //通过该动作触发postMessage方法
  });
  uniWebview.postMessage({
    data: {
      action: "navigateToAnotherMiniProgram", //触发行为
      extraData: {
        payInfo: params,     //携带参数
        webviewUrl: jumpUrl,  //完成支付后页面的跳转地址
      },
    },
  });
  console.log("传递成功");
},
2.原小程序

点击对应图标,跳转到对应的sass服务中,在sass服务中选购好对应商品后,结算时跳转到收银台的h5页面同收银小程序页面内容);用户选择支付方式,如果是积分支付,且积分充足,直接完成支付,重定向到sass订单页;除此以外,需要拉起收银小程序进行微信支付;

1.在webview中响应h5页面的事件;

//webview.vue

 <div class="container">
    <web-view id="web-view" :src="webviewUrl" @message="onMessageHandler"> </web-view>
  </div>
onMessageHandler: function (e) {
  const data = e.detail.data[0]; //传递的参数
  const extraData = data.extraData; //对应h5的结构
  this.extraData = extraData;
  extraData?.webviewUrl && uni.setStorageSync('backWebviewUrl', extraData?.webviewUrl);  //全局存储webviewUrl(支付后的回调跳转路由)
  uni.setStorageSync('miniProgramjump', true);    //判断是由webview中事件导致的跳转,而非切换微信前后台(场景值)
  if (data.action === 'navigateToAnotherMiniProgram') {    //响应webview中的h5触发的事件      
    uni.navigateToMiniProgram({             //跳转到对应收银台小程序中
      appId: 'appid',
      path: 'pages/pay/pay',
      extraData: extraData,
      envVersion: env === 'production' ? 'release' : 'trial',
      success(res) {
        // 打开成功
        uni.removeStorageSync('miniProgramjump');
      },
      fail(err) {           //第二次从h5传递消息回来调起navigateToMiniProgram,可能会直接自动进入失败回调,需要手动让用户点按钮二次调起收银台
        uni.redirectTo({
          url: `/pages/xxx/xxx?open=mini&extraData=${encodeURIComponent(
            JSON.stringify(extraData)
          )}`
        });
      }
    });
  }
}
//blank.vue
<view class="container">
    <button class="pay-button" v-if="open === 'mini'" @click="toPay">若无反应,点我继续支付</button>
</view>
​
<script>
    methods: {
        toPay() {
          uni.navigateToMiniProgram({
            appId: 'appId',
            path: 'pages/pay/pay',  
            extraData: this?.extraData && JSON.parse(decodeURIComponent(this.extraData)), //支付参数
            envVersion: env === 'production' ? 'release' : 'trial',
            success(res) {
              // 打开成功
              uni.removeStorageSync('miniProgramjump');
            },
            fail(err) {
              uni.removeStorageSync('miniProgramjump');
            }
          });
        }
    },
   onLoad(options) {
    const baseUrl = options.url;
    this.open = options.open;
    this.extraData = options?.extraData || '';
    let myExtraData = this?.extraData && JSON.parse(decodeURIComponent(this.extraData));
    // 如果是原小程序跳转到webview页url
    if (options.url) {
      uni.redirectTo({
        url: this.concatUrl(baseUrl)
      });
    }
    if (myExtraData.webviewUrl) {
      this.backWebviewUrl = myExtraData.webviewUrl;
    }
  },
  onShow() {
    const backWebviewUrl = uni.getStorageSync('backWebviewUrl') || this.backWebviewUrl;
    const scene = uni.getStorageSync('scene');
    const miniProgramjump = uni.getStorageSync('miniProgramjump');
    if (miniProgramjump) return;  //如果是本小程序切后台再切前台则无需处理
    if (scene === 1038 && backWebviewUrl) {     //支付成功后跳转到webview中支付详情或者订单页
      uni.redirectTo({
        url: this.concatUrl(backWebviewUrl),
        success(res) {
          uni.removeStorageSync('backWebviewUrl');
          uni.removeStorageSync('scene');
        },
      });
    } else {
      //如果是原小程序跳转到webview页url
      if (this.$mp.query.url) {
        uni.redirectTo({
          url: this.concatUrl(this.$mp.query.url)
        });
      }
    }
  },
</script>

注意之所以需要blank页是因为webview与小程序的通信(postmessage)并非实时的,需要小程序后退、组件销毁、分享、复制链接等事件来触发组件的message事件,所以需要一个空白页来承接后退行为。

所以到sass平台h5页面的顺序是: 小程序首页-blank页-sass webview页-收银台h5页

结算时的顺序是:sass webview页-收银台h5页-blank页-支付小程序

3.收银-小程序

1.原小程序携带支付参数跳转到收银小程序中;

2.在收银小程序onShow中获得传递的payInfo数据,存在全局或状态机中,在支付页中获取该状态;

3.当支付页中,该状态有值时直接调起支付;

支付结束后,回调统一的返回方法,根据进入方式来返回。

/**
 * @description: 返回原小程序/原h5页
 * @return {*}
 */
async backToOrigin() {
    const extraData = {
      state: this.afterPayState,
    };
​
    if (this.payPlace === PayPlace.H5) {
      if (!this.wxPayParams) {
        const jumpUrl = await this.getJumpUrl();
        window.location.replace(jumpUrl);
      } else {
        navigateBack(extraData, "h5");
      }
    } else {
      navigateBack(extraData, "mini");
    }
​
    function navigateBack(extraData, type) {
      uni.navigateBackMiniProgram({
        extraData,
        success(res) {
          console.log(`返回原小程序列表页成功, ${type}`);
        },
      });
    }
}