likes
comments
collection
share

五分钟带你实现基于Nodejs的微信小程序支付

作者站长头像
站长
· 阅读数 19

背景介绍

如果你作为一名前端工程师,想自己实现一个带支付功能的小程序,你还准备后端用Java帮你实现带支付功能的接口吗?大可不行,动起手来,让我们自己用Node.js实现一个微信小程序的接口。

环境搭建

  1. 前端框架:我选择了Tarojs
  2. 后端框架:我选择了Eggjs 作为一个前端工程师,必要的框架搭建我就不一一赘述了。

准备工作

  1. 小程序支付需要自行开通微信支付功能
  2. 微信支付文档

小程序端发起支付流程

发起支付流程图

五分钟带你实现基于Nodejs的微信小程序支付

调用创建订单的接口,返回订单ID

    network.post('/wx/create/order', createParams).then(res => {
        const {code} = res
        const { data: { appid, prepay_id } } = res;
        if (code === 0) {
          launchPay({ appid, prepay_id })
        } else {
          Taro.showToast({
            title: '创建订单失败'
          })
        }
      }).finally(() => {
        Taro.hideLoading()
  })

根据预支付交易会话标识(prepay_id)openId

js版本的签名算法 - getSign

// 加密支付
export const getSign = (params, partnerKey) => {
  const keys = Object.keys(params);
  keys.sort((a, b) => a.charAt(0).charCodeAt() - b.charAt(0).charCodeAt());
  const stringA = keys.map(it => it + '=' + params[it]).join('&');
  const stringSignTemp = `${stringA}&key=${partnerKey}`; //注:key为商户平台设置的密钥key
  return md5(stringSignTemp).toUpperCase();
}

Taro.requestPayment发起小程序的支付,launchPay方法的具体实现:

五分钟带你实现基于Nodejs的微信小程序支付

export const launchPay = (data: PayData) => {
  const { appid, prepay_id } = data;
  const timeStamp = getCurrentStimp();
  const params = {
    appId: appid,   // 小程序ID
    timeStamp: timeStamp.toString(), // 时间戳
    nonceStr: Math.random().toString(36).substr(2), // 随机串
    package: `prepay_id=${prepay_id}`, // 数据包
    signType: 'MD5', //签名方式
  };

  // 用户登录后返回的商户平台设置的密钥key
  const partner_key = Taro.getStorageSync('partner_key');
  // 
  const paySign = getSign(params, partner_key);
  Object.assign(params, { paySign });

  Taro.requestPayment({
    ...params,
    success: res => {
      Taro.showToast({
        title: "支付成功",
        icon: "success",
        duration: 1500,
        mask: false,
        success: res => {
          console.log('支付成功');
          console.log(res, '支付成功');
          Taro.showToast({ icon: 'success', title: '支付成功' })
        }
      });
    },
    fail: res => {
      console.log(res, '支付失败');
      Taro.showToast({ icon: 'none', title: '支付失败' })
    },
    complete: res => {
      console.log(res);
    }
  });
}

Node.js服务端支付接口具体实现

支付回调信息配置

  // payInfo
  const payInfo = {
    domain: 'http://a5iqw7.natappfree.cc', // 内网穿透的临时地址
    notify_url: '/wx/order/pay/callback',
  };

创建订单

import tenpay from 'tenpay';

// wx/create/order
async createApply() {
  
    const body = this.ctx.request.body;
    const { openid: user_id } = this.ctx.state.user;
    const order = {
      note,
      order_id,
      user_id,
      create_time: stemp,
      all_price,
      people_number,
      table_number,
      order_type,
      table_cost,
      pack_cost,
      status: ORDER_STATUS_KEYS.WAIT_PAY,
      create_at: stemp,
      update_at: stemp,
    };
   
  
    // 插入订单表
    await this.ctx.service.wx.order.create(order);

  
    // 微信统一下单
    const { payInfo: { domain, notify_url } } = this.ctx.app.config;
    const { partner_key, mchid, appid } = await this.ctx.service.shop.merchant.model.findOne({
      where: { id: this.ctx._shop.id },
    });
    const payConfig = {
      appid,
      mchid,
      partnerKey: partner_key, // partnerKey
    };
    const PAYAPI = new tenpay(payConfig);
    const payParams = {
      out_trade_no: order_id,
      body: 'xxxxx支付信息',
      total_fee: all_price, // 单位为分,最小为1
      openid: user_id,
      notify_url: domain + notify_url,
    };
    const result = await PAYAPI.unifiedOrder(payParams);
  
    this.ctx.success({
      data: result,
    });
  }

支付回调方法,当用户支付成功后,微信会多次调用我们提供的回调地址

*   接口:/wx/order/pay/callback
*   实现:
*   加密方法和处理支付时间的方法:
```js
    export const getSign = (params: any, partnerKey: string) => {
        const keys = Object.keys(params);

        keys.sort((a, b) => a.charCodeAt(0) - b.charCodeAt(0));
        const stringA = keys.map(it => it + '=' + params\[it]).join('&');
        const stringSignTemp = `${stringA}&key=${partnerKey}`; //注:key为商户平台设置的密钥key
        return md5(stringSignTemp).toUpperCase();
    }

    // 处理微信的支付时间
    export const toPayTime = (date: string) => {
        if (date.length !== 14) return date;
        const YYYY = date.substring(0, 4);
        const MM = date.substring(4, 6);
        const DD = date.substring(6, 8);
        const HH = date.substring(8, 10);
        const mm = date.substring(10, 12);
        const ss = date.substring(12, 14);
        return `${YYYY}-${MM}-${DD} ${HH}:${mm}:${ss}`;
    };

```
/**
   * 支付回调
   * return_code SUCCESS/FAIL  此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
   * appid  小程序ID
   * mch_id 商户号
   * nonce_str 随机字符串
   * sign 签名
   * sign_type 签名类型	
   * trade_type JSAPI
   * openid 用户标识
   * bank_type 付款银行		 
   * total_fee 订单金额	
   * cash_fee 现金支付金额	
   * transaction_id 微信支付订单号	
   * order_id  商户订单号	
   * time_end 支付完成时间
   */
  async payCallback() {
    const { xml } = JSON.parse(xml2json.toJson(this.ctx.request.body));
    const params: any = {};
    Object.keys(xml).forEach(it => {
      params[it] = xml[it].toString();
    });
    console.log(params, 'params')

    const {
      return_code,
      mch_id,
      openid: user_id,
      sign,
      out_trade_no: order_id,
      bank_type,
      total_fee,
      cash_fee,
      transaction_id,
      time_end,
    } = params;
    const signBody = { ...params };
    delete signBody.sign;

    Object.keys(signBody).forEach(it => {
      if (!signBody[it]) {
        delete signBody[it];
      }
    });
    const order = await this.ctx.model.Order.findOne({
      where: {
        user_id,
        order_id,
      }
    });
    const { partner_key } = await this.ctx.service.shop.merchant.model.findOne({
      where: { id: order.shop_id }
    });

    const paySign = getSign(signBody, partner_key);

    // 支付成功
    if (return_code === 'SUCCESS') {
      // 校验签名
      if (paySign === sign) {
        console.log('签名对比成功');
        // 待支付状态才支付
        if (order && order.status === ORDER_STATUS_KEYS.WAIT_PAY) {
          await this.ctx.model.Order
            .update({
              mch_id,
              total_fee,
              bank_type,
              cash_fee,
              transaction_id,
              pay_time: toPayTime(time_end),
              pay_status: 1, // 支付状态
              status: ORDER_STATUS_KEYS.PAY
            },
              {
                where: {
                  user_id,
                  order_id,
                }
              }
            );

          this.ctx.success({
            return_code: 'SUCCESS',
            return_msg: 'OK'
          })
        } else {

          this.ctx.failure({
            return_code: 'FAIL',
            return_msg: '签名失败'
          });
        }
      } else {
        this.ctx.failure({
          return_code: 'FAIL',
          return_msg: '支付失败'
        });
      }
    }
  }

微信的支付回调如何调试?

总结

本文主要讲述了从前端小程序调用创建订单接口,到Nodejs后端接口返回prepay_id,然后前端发起支付,然后前端加密支付信息,微信调用我们提供的回调接口,我们对比加密信息一致,完成整个支付流程。

转载自:https://juejin.cn/post/7238097629288235065
评论
请登录