天天看點

小程式下單賬号與支付賬号不一緻不讓支付_微信小程式支付流程微信支付之小程式支付準備工作小程式支付流程支付前的操作1、小程式端:使用者向商戶伺服器發起支付請求2、商戶後端伺服器:簽名+生成預支付辨別3、給前端傳回支付參數+簽名4、小程式端:向微信伺服器發起請求5、微信伺服器:支付結果通知注意事項參考網址與接口

微信支付之小程式支付

微信的支付方式有以下幾種,不同的支付方式适用于不同的支付場景,而今天要給大家講的就是 小程式支付 方式

小程式下單賬号與支付賬号不一緻不讓支付_微信小程式支付流程微信支付之小程式支付準備工作小程式支付流程支付前的操作1、小程式端:使用者向商戶伺服器發起支付請求2、商戶後端伺服器:簽名+生成預支付辨別3、給前端傳回支付參數+簽名4、小程式端:向微信伺服器發起請求5、微信伺服器:支付結果通知注意事項參考網址與接口

說到支付功能就要涉及到金錢交易,必定是有比較嚴格的規範及流程,如要求小程式必須具備企業性質,必須擁有微信支付商戶平台的賬号

PS:申請微信支付商戶平台需要一個微信小程式或公衆号等,建議按照以下流程進行操作

準備工作

1、申請微信小程式賬号

申請成功可拿到 AppID(小程式 id)和 AppSecret(小程式密鑰)

申請類型為企業性質,否則無法接入微信支付

2、微信小程式認證

通過認證的小程式才能接入微信支付和綁定商戶平台

3、申請商戶平台賬号

需要第一步申請的 AppID

申請成功可拿到 MchID(商戶 id)和 MchKey(商戶密鑰)

4、信小程式關聯商戶号

微信和商戶都認證成功後,在微信背景微信支付菜單中進行關聯

5、接入微信支付

在微信背景微信支付菜單中進行接入

小程式支付流程

簡要支付流程如下:
  1. 使用者發起支付請求
  2. 後端調用統一下單接口得到 prepay_id
  3. 把支付所需參數傳回前端
  4. 前端調用支付接口進行支付操作
  5. 支付結果通知
  6. 前端根據不同的支付結果給使用者不同的提示

PS:難點在第 2、3、5 步,一定要仔細檢視相關接口文檔,否則容易出錯,接下來我們按照以上 6 個步驟詳細講解在微信小程式中的支付流程

支付前的操作

因為嚴格意義上來說這不屬于支付流程中的步驟,但支付過程中需要用到使用者唯一辨別openid,是以建議在使用者進入小程式時就進行這一步的操作
  1. 調用wx.login()接口擷取 code,并把code傳到伺服器
  2. 後端伺服器拿到 code 後調用code2Session 接口擷取 openid 和 session_key

    建議把openid存入資料庫,友善随時擷取,下面的步驟也會用到

  3. 後端伺服器保好 appid, secret, mch_id, mch_key(這些資料分别在小程式背景和商戶平台中獲得,我是把它們做成 commonjs 子產品并儲存在config/wx.js檔案中以友善調用)

PS:開發者需要自行維護使用者登入狀态(使用者登入狀态的維護本文不做展開,請自行查閱相關資料)

1、小程式端:使用者向商戶伺服器發起支付請求

這步沒什麼好說的,當使用者點選支付按鈕時,給我們自己的後端接口發起一個請求,攜帶必要的參數(如:body,total_fee 等),接口位址需要自行編寫,如我的接口位址為/payment/order

// http對象為wx.request()的二次封裝import http from "../utils";​// 向後端發請請求const res = await http.post("/payment/order", {  body: "騰訊QQ-購買會員", // 商品描述  total_fee: 998, // 總金額,機關為分});if (res.status === 200) {  try {    // 得到接口傳回的資料,向微信發起支付    const result = await wx.requestPayment({      ...res.data,    });    wx.showToast({      title: "支付成功",    });    console.log("支付結果:", result);  } catch (err) {    wx.showToast({      title: "支付失敗",    });  }}
           

PS:可能會有小夥伴産生疑惑,為什麼不直接通過 wx.requestPayment() 在小程式端發起請求而要先請求商戶自己的伺服器呢?原因很簡單,安全性問題,wx.requestPayment()需要 2 個重要參數paySign和package,需要 appid,secret,openid,mch_key 等私密資料,這些私密的資料不應該在前端暴露出來,而是放在自己的伺服器中更安全,是以需要向自己的伺服器發起這個請求拿到這些參數,下一步才能真正發起支付。接下來我們來看看後端是如果操作的

2、商戶後端伺服器:簽名+生成預支付辨別

後端代碼使用 egg 架構(基于 NodeJS+Koa)實作,文中涉及到 egg 用法和 koa 的用法不再額外說明,請自行查閱相關資料

調用統一下單接口擷取 預支付會話辨別 prepay_id

注意:該接口需要發送 xml 格式參數,同時傳回 xml 格式資料,需自行轉換(我使用的是xml-js第三方子產品)
    • 該接口必填參數:appid,mch_id,nonce_str,sign_type,body,out_trade_no,total_fee,spbill_create_ip,notify_url,trade_type,sign,其中 sign 為前面所有參數加密後的字元
async order(ctx) {      // egg架構寫法      const { service, request } = ctx;      // 擷取前端傳入參數      const { userid, total_fee, body } = request.body;      // 引入微信配置參數(上面準備工作中儲存的config/wx.js檔案,包含小程式id,密鑰,商戶id,商戶密鑰)      const { config } = require("../../config/wx");      // 生成訂單号(保證唯一性:我采用時間戳拼6位随機數的方式)      const tradeNo = Date.now() + '' + randomCode(100000, 999999);      // 統一下單簽名參數      const orderParams = {          appid: config.appid, // 小程式id          mch_id: config.mch_id, // 商戶id          nonce_str: service.wx.randomStr(), // 自定義生成随機字元方法          sign_type: "MD5", // 加密類型          body, // 商品簡單描述,有格式要求          out_trade_no: tradeNo, // 訂單号          total_fee, // 機關:分          spbill_create_ip: "121.34.253.98", // 伺服器ip          notify_url: "https://你的伺服器域名/payment/wxnotify", // 支付成功通知位址          trade_type: "JSAPI", // 支付方式(小程式支付選JSAPI)          openid: user.openid, // 使用者openid,步驟0儲存的資料      };      // 簽名:對上面所有參數加密(簽名算法請檢視接口文檔,下同)      const orderSign = service.wx.sign(orderParams);      // json->xml      const xmlData = convert.js2xml(          { xml: { ...orderParams, sign: orderSign } },          { compact: true }      );      // 調用統一下單接口(接口沒說明,但必須為post請求)      const { data } = await ctx.curl(          "https://api.mch.weixin.qq.com/pay/unifiedorder",          {          method: "post",          data: xmlData,          }      );      // xml->js      const result = convert.xml2js(data, { compact: true });      if (result.prepay_id) {          // 此處可以把訂單資訊儲存到資料庫          // 傳回prepay_id後,接着就是把參數傳回前端          // =>為了更清晰,我把這裡的代碼寫在下一步          // ...      }  }
           

3、給前端傳回支付參數+簽名

// 支付簽名參數    const payParams = {      appId: config.appid, // 商戶 id      timeStamp: Date.now(), // 時間戳      nonceStr: this.randomStr(), // 随機字元      package: "prepay_id=" + result.prepay_id, //預支付會話辨別(格式為:prepay_id=統一下單接口傳回資料)      signType: "MD5", //簽名類型(必須與上面的統一下單接口一緻)    };    // 簽名    const paySign = service.wx.sign(payParams);​    // 把參數+簽名傳回給前端    ctx.body = formatData({      data: {        timeStamp: payParams.timeStamp,        nonceStr: payParams.nonceStr,        package: payParams.package,        signType: payParams.signType,        paySign,      },    });
           
附上封裝好的簽名方法sign()和生成随機字元串的方法randomStr(),我寫在service/wx.js
"use strict";    const { Service } = require("egg");    const crypto = require("crypto");    // 微信基本配置    const { weapp } = require("../../config/wx");    class wxService extends Service {      randomStr(len = 24) {        const str =          "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";        let result = "";        for (let i = 0; i < len; i++) {          result += str[Math.floor(Math.random() * str.length)];        }        return result;      }      sign(data, signType = "MD5") {        const keys = [];        for (const key in data) {          if (data[key] !== undefined) {            keys.push(key);          }        }        // 字典排序=>key=value        const stringA = keys          .sort()          .map((key) => `${key}=${decodeURIComponent(data[key])}`)          .join("&");        // 拼接商戶key        const stringSignTemp = stringA + "&key=" + weapp.mch_key;        console.log("stringSignTemp", stringSignTemp);        // 加密        let hash;        if (signType === "MD5") {          hash = crypto.createHash("md5");        } else {          hash = crypto.createHmac("sha256", "laoxie");        }        hash.update(stringSignTemp);        const paySign = hash.digest("hex").toUpperCase();        return paySign;      }    }    module.exports = wxService;
           

4、小程式端:向微信伺服器發起請求

第 1 步的資料傳回後,向微信伺服器接口wx.requestPayment()發請求,喚起支付界面,請檢視第一步 try...catch 中的代碼

5、微信伺服器:支付結果通知

在第 2 步向統一下單接口發起請求時附帶了一個notify_url,此位址一定要是可外網通路的接口位址(商戶自行編寫),由微信伺服器調用該接口,不管支付成功與否,此接口都會調用,并傳回相應資料(檢視接口資料),是以商戶可以在此接口中編寫相關業務邏輯、如支付成功後寫入資料庫等操作

注意:商戶需要在此接口中做接收處理,并向微信伺服器傳回應答(按接口規範傳回特定資料)。如果微信收到商戶的應答不是成功或逾時,微信會認為通知失敗,微信會通過一定的政策定期重新發起通知,通知頻率為:15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h,但微信不保證通知最終一定能成功。
"use strict";​  const Controller = require("egg").Controller;  const getRawBody = require("raw-body");  const contentType = require("content-type");  const { formatData, randomCode, params, formatParams } = require("../utils");​  class PaymentController extends Controller {    // 微信支付回調位址    async notify(ctx) {      const { req } = ctx;​      // 微信調用該接口時傳入的資料為xml,是以先轉換      const data = await getRawBody(req, {        length: req.headers["content-length"],        limit: "1mb",        encoding: contentType.parse(req).parameters.charset,      });      const result = params.xml2js(data);​      // 驗簽:微信傳入的除sign外的所有資料進行簽名,拒後與sign進行對比是否一緻      // 一緻說明支付成功,否則支付失敗      // 并根據不同的結果通知微信伺服器(響應不同的xml資料,如下)      const resultSign = result.sign;      delete result.sign;      const mySign = ctx.service.wx.sign(result);      console.log("sign:", resultSign, mySign);​      ctx.set("content-type", "text/plain");​      if (resultSign === mySign) {        // 修改商戶訂單狀态        const {          device_info,          openid,          trade_type,          bank_type,          total_fee,          settlement_total_fee,          fee_type,          transaction_id,          time_end,          attach,        } = result;​        // 格式化自定義參數        let myattach = {};        if (attach) {          myattach = params.parse(attach);        }​        // 格式化支付時間:20200423161017=>2020/04/23 16:10:17        let pay_time = time_end.replace(          /(d{4})(d{2})(d{2})(d{2})(d{2})(d{2})/,          "$1/$2/$3 $4:$5:$6"        );        pay_time = new Date(pay_time);​        // 根據訂單号更新資料庫中的訂單狀态        const newData = {          device_info,          openid,          trade_type,          bank_type,          total_fee,          settlement_total_fee,          fee_type,          transaction_id,          pay_time,          status: 1,          ...myattach,        };        db.update(          "purchase",          {            out_trade_no: result.out_trade_no,          },          {            $set: newData,          }        );​        ctx.body = ``;      } else {        ctx.body = ``;      }    }  }​  module.exports = PaymentController;
           
附上以上代碼中會用的封裝好的方法parse()、xml2js()、js2xml(),我寫在utils/index.js中
const params = {    parse(queryString) {      // 'a=1&b=2' => {a:1,b:2}      return queryString.split("&").reduce((res, item) => {        const arr = item.split("=");        res[arr[0]] = arr[1];        return res;      }, {});    },    js2xml(data) {      return convert.js2xml({ xml: data }, { compact: true });    },    xml2js(xml) {      const result = convert.xml2js(xml, {        compact: true,        textKey: "value",        cdataKey: "value",      }).xml;      const data = {};      for (const key in result) {        data[key] = result[key].value;      }      return data;    },  };  module.exports = {    params  }
           

到此微信支付之小程式支付就完成了,過程比較繁雜,一定要一步步去實作,也許會踩坑,但相信我,這是每個程式員的必經這路,面對它,勇敢地走過去,你對能到達勝利的彼岸。

注意事項

  • appid、appsecret、mchid、mchkey、openid 為小程式或商戶私密資訊,應儲存在服務端
  • 注意參數大小寫:每個接口大小寫可能不同
  • 簽名算法:請檢視接口文檔
  • 一定要注意看文檔,根據我多冷踩坑的經曆,90%以上的問題都是沒有仔細看文檔所緻

參考網址與接口

  • 微信支付商戶平台:https://pay.weixin.qq.com
  • 微信公衆平台:https://mp.weixin.qq.com
  • 微信支付接口:https://developers.weixin.qq.com/miniprogram/dev/api/open-api/payment/wx.requestPayment.html
  • 統一下單接口:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1
  • 支付結果通知接口:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_7&index=8
  • 簽名算法:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3

[mp] https://mp.weixin.qq.com

[pay] https://pay.weixin.qq.com

[payment] https://developers.weixin.qq.com/miniprogram/dev/api/open-api/payment/wx.requestPayment.html

[notify] https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_7&index=8

[login] https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html

[unifiedorder] https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1

[code2session] https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html

[sign] https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3