微信支付

前言

总结一下最近业务开发中对微信公众号支付的开发过程,微信支付的开发前提是已经具备可上线微信公众号开发的基础上进行的,如果你的开发阶段目前停留在起步,建议参考这篇文章开始。

好了,来聊一聊微信支付。不论是今天的分享,还是网上其他的分享,开头总是在吐槽微信的文档。我也不例外,刚开始总是觉得文档写的不够具体,写的模棱两可。后来发现一个是自己太浮躁,不能沉下心去分析文档的细节,另一方面是习惯性先去网上找相关的教程,然后发现教程传递过来的第一感受就是——微信开发是个大坑。

网上的经验分享还是很有帮助的,但是首先要清楚地明白微信支付的整个流程以及自己目前的进展,这样才能有目的的去找到自己需要的东西。

微信支付开发和微信其他功能开发有一个共同点,就是需要耐心。而这似乎也是微信团队的初衷,通过零散的文档,不清晰的说明来过滤掉一批缺乏耐心的程序员(帮微信团队圆个场)。所以开始接触微信开发之前,会有人告诉你这里会有很多坑,会怎么样怎么样,其实完全不用担心,因为这里的坑并不是在考验技术。

关于微信支付的调试过程,由于微信支付对安全要求较高,不能做内网穿透本机测试,所以需要先将项目部署到线上,开发调试不是很方便。可能会有一些本地支付沙箱之类的工具,我没有去研究,希望有做过此类工作的小伙伴留个言提醒一下。而且微信支付是不能够通过微信开发者工具测试的,只能在真机上跑。如果在开发者工具上遇到报错,不妨在手机上跑一下。

微信支付文档分析

不管什么开发,都要从官方文档开始。关于公众号支付的部分,需要关注文档的两个部分,一个是“公众号支付”一个是“API列表”,其他部分不是重点。

公众号支付

  • 场景介绍
  • 案例介绍
  • 开发步骤
  • 业务流程
  • 获取微信版本号
  • 微信内H5调起支付
  • 收获地址共享
  • 支持常见问题

这一级的目录,重点是业务流程微信内H5调起支付,因为这两块内容搞明白了,整个支付流程就清晰了。

首先需要了解一下公众号支付的具体场景,也就是需要阅读第一部分“场景介绍”,了解一下这个场景是否符合具体业务。

如果这就是你要的,那就来到重点了,也就是“业务流程”。

看到业务流程的流程图,估计有计算机专业背景的朋友会很熟悉,也很容易理解。不理解也没关系,我这里准备了一副含有分析过程的流程图,结合实际业务,来帮助理解。

先来看黄色的甬道,这两块也就是实际的前台页面和后台服务,我们的阅读顺序是自上而下,流程图中的文字是微信官方提供的,右边的说明文字是我根据业务写的,看哪部分都可以。我把整个流程用颜色分为了三大块帮助理解。

从红色部分开始,红色部分主要工作是后台生成预付款单,然后通过回调信息将内容发送到前台。

接下来是蓝色部分,这一部分包含两块,一个是付款前,一个是付款后。

首先前台拿到红色部分由后台发来的信息,然后再微信内H5页面调起支付,此时页面的付款都是由微信来控制。

付款结束后,会发送两个回调,一个发送给后台服务,也就是图片蓝色部分中的绿色区块,告诉后台具体哪个订单现在的完成状态。另一个发送给客户前端,通知前端交易状态。

从这部分内容了解到,需要先了解统一下单API,之后是微信H5内调起支付,然后处理支付结果通知。

开始开发

准备

微信服务配置

开始开发前,需要对现有项目设置支付目录和设置授权域名,具体可以参考这里。另外需要注意的是,也是微信文档里没有提到的地方。需要在微信支付平台设置API密钥。需要提醒的是,微信支付平台的配置需要超级管理员账号登录才可以进行配置操作。

收集信息

上一步配置完成后,需要收集一些信息为后续开发做准备。

  • token 微信公众号后台取得
  • appid 微信公众号后台取得
  • appsecret 微信公众号后台取得
  • encodingAESKey 微信公众号后台取得
  • mch_id 商户号(微信支付平台取得)
  • notifyUrl 微信支付回调地址(服务端后台接口:POST)
  • partnerKey 微信支付API密钥(微信支付平台取得)

准备好以上信息后,就可以开始着手写代码了。

开工

首先,需要准备一个前台界面,模拟用户访问商品页面点击购买。

后端部分

后台这边,node开发微信支付有很多现成的封装库可以使用,这里使用wechat-pay

首先在项目开始处初始化wechat-pay

下单
const Payment = require('wechat-pay').Payment;
const initConfig = {partnerKey: config.wechat.partnerKey,appId: config.wechat.appid,mchId: config.wechat.mch_id,notifyUrl: config.wechat.notifyUrl,
};
const payment = new Payment(initConfig);
复制代码

然后编写前台界面用户点击购买后的接口业务代码:

async genAdvanceOrder(ctx) {try {// 1. 通过前台发来的商品ID查询商品const { user } = ctx.state;const { product_id, comment, school_id } = ctx.request.body;const product = await prodDao.findOneProduct(product_id);if (product.length <= 0) {return ctx.body = new Error(C.ERROR_CODE.QUERY_EMPTY, '没有找到商品');}// 2. 通过查询结果填写预付款单// 获取client ip地址const clientIp = getClientIp(ctx.req);const order = {body: product[0].course_title,        // 商品描述attach: product[0].comment,           // 商品附加数据out_trade_no: UUID.v1().replace(/\-/g, ''),   //  商户系统内部订单号,自己生成32位随机串 uniquetotal_fee: product[0].price,          // 费用(单位:分) spbill_create_ip: clientIp,           // 客户端IPopenid: user.openid,                  // 用户openidtrade_type: 'JSAPI'};// 3. 根据预付款单回调结果往数据库插入数据(判断错误码,修改订单状态)// 向微信请求生成预付款单let payargs = await payment.getBrandWCPayRequestParams(order);await orderDao.add({user_id: user.id,school_id: school_id, // TODO: 增加并分配学校ID,业务逻辑需要变动scene: product[0].scene,product_id: product_id,price: product[0].price,product_price: product[0].price,status: C.PAY_STATUS.NO_PAY,wx_open_id: user.openid,wx_out_trade_no: order.out_trade_no,wx_prepay_id: payargs.package.split('=')[1], // 取prepay_idcomment: comment});// 4. 发送预付款单内容ctx.body = new Success(payargs);} catch (e) {ctx.body = new Error('', '未知错误', e)}}function getClientIp(req) {const ip = req.headers['x-forwarded-for'] ||req.connection.remoteAddress ||req.socket.remoteAddress ||req.connection.socket.remoteAddress;return ip.replace(/:|\wf/g, '');
}
复制代码

这里需要注意的是填写预付款单这里的操作。

out_trade_no是要自己生成32位随机字符串,相当于是保存在自己数据库中的订单唯一值,以后在微信回执付款信息时也会用到这个字段。

total_fee的单位是分,在开发过程中,也应当使用分作为数据库价钱的单位,这样可以有效避免浮点数精读损失问题。

spbill_create_ip是用户端下单设备的ip,是必填项,微信那边处于安全要求每份订单都必须要填写。这个也很好获取(上面代码中贴出来了),只不过要做一些处理,因为直接通过koa ctx.ip获取的地址可能会被Nginx或者其他服务器配置服务转发成127.0.0.1。这就不是我们需要的真实的客户端ip。最后要处理字符串前缀,一般直接拿到ip的格式是:fff:54.00.00.1

支付通知

这里就是之前的流程图中,蓝色区块中的绿色部分。可以对比流程图理解支付流程。

微信开发中,大量来自微信发送的通知都是xml格式,所以为了方便使用,需要先增加以下中间件来帮助开发。

const bodyParser = require('koa-bodyparser');
const xmlParser = require('koa-xml-body');app.use(xmlParser({key: 'body'
}));
// app.use(bodyParser());
app.use(bodyParser({enableTypes: ['json', 'form', 'text'],extendTypes: {text: ['text/xml', 'application/xml']}
}));
复制代码

之后是通知部分的代码:

async wxPayNotify(ctx) {// TODO: 安全验证 签名验证 并校验返回的订单金额是否与商户侧的订单金额一致/*// 微信发送通知的内容{appid: [ '**********' ],attach: [ '附加内容' ],bank_type: [ 'CFT' ],cash_fee: [ '1' ],fee_type: [ 'CNY' ],is_subscribe: [ 'Y' ],mch_id: [ '1498496372' ],nonce_str: [ '4KewHbQvsQPaGsaeoICLbKD1ySFDlPdL' ],openid: [ 'oZZUx0X2LSM1j652P6r2R*******' ],out_trade_no: [ '8ff9fdd0e33411e8a07c833c43c4e4e7' ],result_code: [ 'SUCCESS' ],return_code: [ 'SUCCESS' ],sign: [ '6A14538FE1651CECDFCDFE375383B9AA' ],time_end: [ '20181108165920' ],total_fee: [ '1' ],trade_type: [ 'JSAPI' ],transaction_id: [ '4200000235201811***********' ]}*/try {// 1. 通过回调信息查询订单const content = ctx.request.body['xml'];const order = await orderDao.selectOne({ wx_out_trade_no: content['out_trade_no'][0] });if (!order) {//  TODO: 处理查询不到订单的通知}// 2. 安全验证,对比签名和订单金额if (checkWeChatPaySign(content) && order.price === parseInt(content['total_fee'][0])) {await orderDao.update({wx_notify_backup: JSON.stringify(content),status: C.PAY_STATUS.PAID,wx_transaction_id: content['transaction_id'][0]}, { wx_out_trade_no: content['out_trade_no'][0] })} else {//  TODO: 处理验证不通过的通知}// 3. 回调通知微信ctx.body = '<xml>' +'<return_code><![CDATA[SUCCESS]]></return_code>' +'<return_msg><![CDATA[OK]]></return_msg>' +'</xml>'} catch (e) {//  TODO: 收款回调出错通知}}function checkWeChatPaySign(obj) {// 1.字典排序数据集合let arr = [];for (let [k, v] of Object.entries(obj)) {let string = '';// 排除 sign 字段if (k === 'sign') continue;string += k + '=' + v[0];arr.push(string);}// 按字典排序arr.sort();// 2.拼接上key得到stringSignTemp字符串arr.push('key=' + config.wechat.partnerKey);const stringSignTemp = arr.join('&');const md5String = md5(stringSignTemp).toUpperCase();// 3.比较md5String 与 sign字段return md5String === obj.sign[0];
}
复制代码

这里比较不好理解的是验证签名,而微信文档也没有给出样例代码,所以比较混乱。而且拼签名拼串验证又容易出错,多一个空格少一个字符都不一样。这里就得结合官方给出的签名算法和效验工具耐着性子调试了。

前端部分

然后就回到我们的前端部分。

由于微信H5支付是基于腾讯浏览器的,所以只有在手机微信中或者开发者工具中打开的网址,才能调用到WeixinJSBridge

我这边前端是拿Angular写的,不过代码不复杂,着重理解业务流程。

import { Component, OnInit } from '@angular/core';
import {UserService} from '../../../services/user.service';
import {OrderService} from '../../../services/order.service';
import {ActivatedRoute} from '@angular/router';@Component({selector: 'app-wx-pay-test',templateUrl: './wx-pay-test.component.html',styleUrls: ['./wx-pay-test.component.scss']
})
export class WxPayTestComponent implements OnInit {wxBridge;logs = [];productId;constructor(private orderService: OrderService, // APIprivate activateRouter: ActivatedRoute) { }// 页面初始化就会执行的钩子函数, React 应该使用ComponentDidMountngOnInit() {// 这里是为了获取WeixinJSBridgeif (typeof window['WeixinJSBridge'] === 'undefined') {if (document.addEventListener ) {document.addEventListener('WeixinJSBridgeReady', this.onBridgeReady, false);} else if (document['attachEvent']) {document['attachEvent']('WeixinJSBridgeReady', this.onBridgeReady);document['attachEvent']('onWeixinJSBridgeReady', this.onBridgeReady);}} else {this.onBridgeReady();}// 获取url中的参数this.activateRouter.params.subscribe( params => {if (params.id) {this.productId = params.id;}});}onBridgeReady = () => {this.wxBridge = window['WeixinJSBridge'];}// 点击购买触发该函数genPreOrder(ev) {const that = this;// 1、向后台发送请求 对应后台 genAdvanceOrderthis.orderService.genAdvanceOrder({product_id: this.productId}).then(data => {// 2、拿到服务端回执数据,调用invoke,请求微信支付that.wxBridge.invoke('getBrandWCPayRequest', data, function(res) {// 3、处理微信支付回执结果if (res.err_msg === 'get_brand_wcpay_request:ok') {// TODO: 显示支付成功页面alert('支付成功');// 这里可以跳转到订单完成页面向用户展示} else {// TODO: 显示支付失败页面alert('支付失败,请重试');}});}).catch(err => {console.log(err);});}
}
复制代码

前端的代码还是相对容易理解的,前提是理解文章开头部分的流程图。知道每一步是处理什么问题,需要干什么。

在调用微信支付后,返回的结果代码中我只处理了支付成功的部分,但是回调还会有支付失败、超时等等,就不一一列举。

结语

文章到这里,大概也就讲清楚了微信公众号支付的整个环节。但是在流程图中最后一部分灰色区块的业务没有讲,因为觉得前两部分是最主要的,后面可以自行理解流程图,根据具体业务开发。

折腾微信支付这块内容大概也有几天了,总结一下整个开发流程,分享一下,希望能够帮助大家理解整个支付业务。

要说难吗其实也不难,主要就是考察耐心吧。

微信公众号支付开发手记(node)相关推荐

  1. 微信公众号支付开发步骤Java(超详细)

    做为一个刚刚做完微信公众号的小白,我不得不吐槽一下微信给的官方文档,里面那坑一个接一个,我这是跳进去再爬出来,一下给做了四天,本来技术就不够好,还被文档带的跑偏跑偏...我在这给大家整理一份超级详细的 ...

  2. java微信公众号支付开发平台_微信公众号支付demo,微信公众号支付Java DEMO

    1.5.4微信验证的控制方法: /** * 微信验证 * 请填写接口配置信息,此信息需要你有自己的服务器资源,填写的URL需要正确响应微信发送的Token验证 * 验证服务器地址的有效性 * 开发者提 ...

  3. 微信公众号支付开发全过程(java版)

    文章有不当之处,欢迎指正,如果喜欢微信阅读,你也可以关注我的微信公众号:好好学java,获取优质学习资源. 一.微信官方文档微信支付开发流程(公众号支付) 首先我们到微信支付的官方文档的开发步骤部分查 ...

  4. java微信公众号支付开发平台_Java微信公众平台开发之公众号支付(微信内H5调起支付)...

    官方文档 准备工作:已通过微信认证的公众号,必须通过ICP备案域名(否则会报支付失败) 借鉴了很多大神的文章,在此先谢过了 整个支付流程,看懂就很好写了 一.设置支付目录 在微信公众平台设置您的公众号 ...

  5. 微信公众号支付开发 --Java

    公众号支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付.应用场景有: ◆ 用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付 ...

  6. 公众号支付demo java_微信公众号支付开发全过程(java版)

    文章有不当之处,欢迎指正,如果喜欢微信阅读,你也可以关注我的微信公众号:好好学java,获取优质学习资源. 一.微信官方文档微信支付开发流程(公众号支付) 首先我们到微信支付的官方文档的开发步骤部分查 ...

  7. 微信公众号支付开发(一):前期准备

    微信公众号认证(服务号认证) 说明:如果没有发布文章的需建议认证服务号,如果每个自然月每天都要发文章认证订阅号,这里是认证服务号. 准备工作: 注册一个新邮箱. 1. 注册微信公众号账号:注册一个微信 ...

  8. 关于微信学习之微信公众号支付开发

    关于微信支付初学者一定有很多的疑问,首先我们需要结合一下自己的程序操作微信支付所需要的业务流程. 1.如我这边需要开发一个公众号支付: 首先用户扫码访问后台(或者通过公众号直接进入,点击一个支付请求按 ...

  9. 微信公众号支付开发配置

    一.微信公众平台配置 登陆微信公众平台 微信支付->开发配置 1.测试白名单:把自己的微信号加上 2.测试授权目录:修改为项目所属域名,不加端口号.例如:http://d****n.g**.ne ...

最新文章

  1. vscode+MinGW+cmake设置轻量ide
  2. WIFI只sta和ap建立连接的过程
  3. 2020-11-10(进程的优先级)
  4. 这个搞定系统监控的妙招,不来学可惜了
  5. 如何用SMS2003部署WindowsXPSP3
  6. ie6 offsetWidth/offsetHeight无效
  7. 小程序 - 数组追加兼本地存储
  8. creo管道设计教程_Creo产品设计教程:握力器弹簧建模,一个技巧轻松搞定
  9. OpenFeign 的 9 个坑,每个都能让你的系统奔溃
  10. zabbix 自定义监控 排除带报错提示
  11. yui compressor php,通过yuicompressor-2.4.7压缩css或js的php应用文件
  12. unity3D游戏素材素材哪家强?Top3都在这!
  13. linux卸载pm2,Linux服务器部署Nodejs项目,使用pm2管理
  14. Android CameraX 使用入门
  15. 简简单单,做自己的视频加密软件
  16. Kotlin真的值得学习吗?
  17. NOIP 2017 游记
  18. 台式计算机重装系统,台式机重装系统其实很简单!
  19. Roguelike+单机玩法游戏保护案例
  20. 基于可穿戴传感器和深度卷积神经网络运动状态进行下半身监控算法

热门文章

  1. 数据对象与对象之间相似度与相异度的度量
  2. 邮箱html页面无法显示图片,邮箱内嵌入html页面需要注意的
  3. 免费领取微软OneDrive网盘5T容量,非扩容!
  4. 人人商城 邀请人 成功购买会员卡之后返现
  5. 小心黑客入侵,六种黑客入侵手机的常见方式
  6. 详解SAN存储技术的前世今生
  7. .vip域名是什么?
  8. 机械师星辰15电脑开机一直黑屏怎么重装电脑系统?
  9. 专注儿童编程,核桃编程招人啦~
  10. vscode 侧边栏源代码管理不见了