微信支付之小程序支付

微信的支付方式有以下几种,不同的支付方式适用于不同的支付场景,而今天要给大家讲的就是 小程序支付 方式

说到支付功能就要涉及到金钱交易,必定是有比较严格的规范及流程,如要求小程序必须具备企业性质,必须拥有微信支付商户平台的账号

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

小程序下单账号与支付账号不一致不让支付_微信小程序支付流程相关推荐

  1. 小程序 多个 veb-view返回 返回了两个页面_微信小程序学习心得 - 忒扎心

    我们写小程序时都要跳转页面的,也会有底部导航来进行切换 这个时候就要介绍下窗口是怎样配置的 要在文件里写一个tabBer对象 里面在定义一个list数组里面放我们定义的几个需要切换的页面 如下 最多l ...

  2. 微信小程序怎么把获取的值传到引用组件内_微信小程序如何将接口获取的数据传递给自定义组件...

    2019-07-11 回答 不知道你是什么意思.帮你改了下 class program { static int n = 4; int i, m; dsd[] a = new dsd[n]; publ ...

  3. 视频教程-微信小程序系统教程python版[3/3阶段]_微信小程序支付-手游开发

    微信小程序系统教程python版[3/3阶段]_微信小程序支付 微信企业号星级会员.10多年软件从业经历,国家级软件项目负责人,主要从事软件研发.软件企业员工技能培训.已经取得计算机技术与软件资格考试 ...

  4. 小程序 微信统计表格_微信小程序登录机制

    " 不是0,也不是1,有0也有1 " 总有一个瞬间,你想记录当下的一些事情,所以有了这篇文章,不会口吐芬芳,我直接开门了,但愿能让你见山. 1. 背景 21 届的校园招聘已经打响了 ...

  5. 微信小程序python解析获取用户手机号_微信小程序获取用户手机号

    获取微信用户绑定的手机号,需先调用wx.login接口. 小程序获取code. 后台得到session_key,openid. 组件触发getPhoneNumber 因为需要用户主动触发才能发起获取手 ...

  6. 黑马优购_微信小程序

    黑马优购_微信小程序项目 介绍 2021年5月6日-2021年5月12日在校参加微信小程序培训,由黑马讲师授课,能够利用微信提供的组件和API实现轮播图.授权用户信息.上拉加载更多等功能,由于之前对u ...

  7. 600多个微信小程序源码_微信小程序在线音乐播放器及源码下载

    引言 自己刚开始学微信小程序的时候,自己做着玩玩的. 现在分享出来给大家学习用用,如果觉得有借鉴意义,我的目的就算达到了. 成果 效果图 废话不多说,直接上效果图: 这里 本来是GIF的图,但是太大了 ...

  8. 微信小程序系统教程Java版[3/3阶段]_微信小程序电商系统-翟东平-专题视频课程...

    微信小程序系统教程Java版[3/3阶段]_微信小程序电商系统-2445人已学习 课程介绍         微信小程序系统教程[初级阶段],微信小程序0基础学起,讲解微信小程序开发的基础知识. 微信小 ...

  9. 视频教程-微信小程序系统教程Java版[3/3阶段]_微信小程序电商系统-微信开发

    微信小程序系统教程Java版[3/3阶段]_微信小程序电商系统 微信企业号星级会员.10多年软件从业经历,国家级软件项目负责人,主要从事软件研发.软件企业员工技能培训.已经取得计算机技术与软件资格考试 ...

  10. 微信小程序页面栈_微信小程序之页面传值(路由、页面栈、globalData、缓存)

    1. 通过url带参数传递 1.1 固定参数传递 例如,从 list 页面到 detail 页面, 传递一个或多个固定值 list页面传值: 点此进入 detail detail页面取值: onLoa ...

最新文章

  1. 如果再写for循环,我就锤自己!
  2. 五年程序员败在阿里三面,还是Java底层原理的问题啊!
  3. 阿里九峰:云计算开启的基础设施新时代
  4. 计算机英语学情分析怎么写,2016年信息技术教学计划及学情分析(600字)
  5. P5782-[POI2001]和平委员会【2-SAT】
  6. 【算法设计与分析】05 有关函数的渐进的界的定理
  7. vue 获取请求url_vue 获取url里参数的两种方法小结
  8. 在互联网大环境下,IT编程以及网络营销,到底学哪个比较好就业?
  9. go语言slice使用的时候遇到的奇怪现象以及分析
  10. 中间环节越多,大家就越赚钱?
  11. matlab中构建Cuk变换器,CUK变换器的SIMULINK仿真与应用.pdf
  12. UEditor的使用方法
  13. CCNA学习指南第十章
  14. 数学建模常用模型10 :数据包络(DEA)分析法(投入产出法)
  15. 项目管理工具之SWOT分析法
  16. 电商api全境,Python网络爬虫与数据采集
  17. WebGL unsupported in this browser 谷歌浏览器,edge不支持WebGL
  18. 零基础自学Python好难?学起来很吃力,想放弃?看看别人是怎样学习的
  19. Bhuman应用篇——带球及踢球
  20. 这么多编程语言,初学者选择哪个比较好?

热门文章

  1. Boost出现error C2678
  2. jellyfin 字幕方框问题
  3. 视频怎么加水印上去,视频加水印怎么加?
  4. 丰巢取快递系统(一)
  5. Android 使用百度飞桨做OCR的本地识别
  6. VideoPlayer视频播放
  7. 手机厂商要和年轻人交朋友,性价比日渐式微?
  8. 123茶楼,众筹...
  9. 一键拼接微信好友头像/玩炫朋友圈
  10. Deployer让部署变得更加的简单