微信h5支付流程,nodejs与云函数版本

前言

本文将介绍在nodejs开发环境下的微信h5支付流程,由于笔者被微信文档逼疯,又被网上的多种不完全的博客坑害,所以总结自己的经验,给出完整流程。注意!笔者的开发环境是uniapp+unicloud,运行环境是在微信浏览器中的公众号h5网页,依托于微信公众号。后端是nodejs,如果是使用java和php的朋友可以完全参考微信开放文档,因为有完整的sdk示例流程。只需要了解前端换取code的方式即可。

环境准备

  • h5的运行环境需要有微信浏览器的独特api,开发调试必须在微信浏览器中进行,所以我们需要下载微信开发者工具,点击微信公众号项目就可以打开微信浏览器,然后当做正常浏览器使用即可

image.png

  • 微信换取code的时候需要跳转外网,也就是说本地调试是无法正常进行的,需要使用内网穿透,在下面会具体介绍
  • 需要准备各种api和密匙,下文使用到时,会具体说明位置和配置方法

使用流程

设置安全域名

在使用微信h5的时候,为了保证在这里的监管,微信需要你跳转到它的指定页面,在链接后面带上你的各种参数,如APPID等,然后加上你的实际地址,然后微信从那个页面跳回你的页面,链接上就会给你放上它的code,你可以用这个code去换取openid,这也就是当前项目下,对该用户的唯一标识,后面很多操作都是基于这一点的。

完成这一步,需要配置授权域名,这里配置。

image.png
配置完成后就可以进行域名跳转了

设置开发环境

这里会遇到一个问题,就是在开发初期,我们可能并没有服务器,再者,如果传上服务器,那么调试将变的无比困难,这里笔者的解决办法是使用内网穿透软件。在我们开发的时候,一般前端会有一个本地的开发服务器,只需要将内网穿透的外网地址解析到我们本地开发服务器的地址,然后在安全域名配置的时候,将内网穿透软件提供的外网域名放上去就可以了,这个时候,我们就可以进行正常调试了。但是要注意,免费的内网穿透带宽特别低,而开发服务器又会占用较大的空间,可能会导致调试很慢,这个就需要各位自行解决了。例如笔者使用的花生壳软件。

image.png

设置初始页面

前面提到了,我们需要进入一个微信指定的页面,然后让微信跳转回来,以便获取code。只需要对页面url进行判断,没有code的情况下,跳转,有了code,就可以进入我们的主页面了

<template>
	<view>
	</view>
</template>
<script>
	import {
		utilWxTogo
	} from '@/utils/utils.js'
	export default {
		data() {
			return {
				APPID: "你的appID", // 应用ID
				mainPage: "https://314g552a52.vicp.fun/static"// 主页面域名
			}
		},
		onLoad() {
			const code = this.getCode() // 解析url上的code
			const mainP= encodeURIComponent(this.mainPage) // js自带函数,编译http格式
			const url_ =
				`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${this.APPID}&redirect_uri=${mainP}&response_type=code&scope=snsapi_base&state=123#wechat_redirect`
			if (!code) // 如果没有code则跳转
				window.location.href = url_
			else// 有了code,则直接跳转进主页面,code一直在url上,所以不需要保存
				utilWxTogo('/pages/login/login', {})() //自己写的跳转函数
		},
		methods: {
			getCode() {
				var url = new URL(window.location.href);
				// 获取参数对象
				var params = new URLSearchParams(url.search);
				// 获取特定参数的值
				return params.get('code');
			}
		}
	}
</script>

<style>

</style>

APPID可以公众号平台的这里找到

image.png

换取openid

得到code后,就可以发到后端,换取openid了,这里其实也是向微信接口发送请求,操作如下

// 获取openId云函数入口函数
exports.main = async (event, context) => {
  // 获取基础信息
  const {data} = await axois.get('https://api.weixin.qq.com/sns/oauth2/access_token',{
		params: {
			appid: '你的APPID',
			secret: '你的密匙', // 开发者密匙的位置,就在appid下面
			code: event.code, // 前端传过来的code
			grant_type: 'authorization_code'
		}
	})

  return data
}

微信统一下单

准备工作

在进行支付前,需要先进行统一下单操作。在此之前,我们需要配置支付安全目录,把我们h5的域名加入。在这里配置

image.png
准备一下以下几个参数

const Payment = require('wxpay-v3'); // 下单工具,直接npm下载
const appid = "你的appid"; //appid
const mchid = '商户号'; //商户号
const serial_no = '商户证书序列号'; //商户证书序列号
const notify_url = 'https://127.0.0.1' // 支付结果回调地址,需要https,如果不需要回调,就随便写

const filePath = path.join(__dirname, 'apiclient_key.pem'); // 准备证书
const private_key = require('fs').readFileSync(filePath).toString() // 读取证书

准备apiv3密匙,位置如下

image.png

统一下单

初始化下单工具

	const paymnet = new Payment({
		appid: appid,
		mchid: mchid,
		private_key: private_key, //或者直接复制证书文件内容
		serial_no: serial_no,
		apiv3_private_key: '你的apiv3密匙',
		notify_url: notify_url,
	})

生成订单号,这里就随便用时间戳生成,只要保证不重复即可

function orderCode() {
	let timestamp = new Date().getTime().toString();
	const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz'; //默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1
	const len = 31;
	const maxPos = chars.length;
	
	let pwd = 'D'+timestamp;
	for (let i = 0; i < len-timestamp.length; i++) {
		pwd += chars.charAt(Math.floor(Math.random() * maxPos));
	}
	return pwd;
}

const order = orderCode()

使用jsapi下单,传入订单参数

	let result = await paymnet.jsapi({
   	"mchid": mchid, //直连商户号
   	"out_trade_no": order, //商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一
   	"appid": appid, //应用ID
   	"description": '', //商品描述
   	"attach": JSON.stringify(data.attach), //附加数据,可空
   	"notify_url": notify_url, //通知地址
   	"amount": {
   		"total": +data.money, //总金额,单位为分
   		"currency": "CNY"
   	},
   	"payer": {
   		"openid": data.openid //用户标识
   	}
   })

生成签名,这个流程很奇怪,不复杂,但是跟着官网来总是失败,我去找了个别人写好的工具类,成功生成

const prepay_id = JSON.parse(result.data)['prepay_id'] // 注意返回的data是字符串json
const wxPay = new WxPayUtils(private_key, appid);
const sign_params = wxPay.paysign({
	prepay_id,
	order
})
return {
	appId: appid,
	...sign_params,
}

工具类内容如下

const RSA = require('./wx_rsa');
class WxPayUtils {
	constructor(privateKey, appId) {
		this.privateKey = privateKey;
		this.appId = appId;
	}
	signLong(data) {
		// let privateKey =private_key
		let sign_rsa = new RSA.RSAKey();
		sign_rsa = RSA.KEYUTIL.getKey(this.privateKey);
		let hashAlg = 'sha256'; //sha256//此处换成腾讯要求的方式
		let Sig = sign_rsa.signString(data, hashAlg);
		Sig = RSA.hex2b64(Sig); // hex 转 b64
		return Sig;
	}

	paysign(options) { //发起支付签的字段准备
		let timeStamp = this.createTimeStamp(), //时间戳
			nonceStr = options.order, //32位随机数
			Ppackage = `prepay_id=${options.prepay_id}`, //prepay_id
			signType = 'RSA'; //加签方式
		//appId、timeStamp、nonceStr、package
		let PpaySign = `${this.appId}\n${timeStamp}\n${nonceStr}\n${Ppackage}\n`; //需要加签的字段拼接
		let cryptStr = this.signLong(PpaySign); //生成签名
		let paySign = cryptStr;
		return {
			timeStamp,
			nonceStr,
			package: Ppackage,
			signType,
			paySign
		};
	}

	createTimeStamp() {
		return parseInt(new Date().getTime() / 1000) + ''
	}

	randomString() {
		const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; //默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1
		const len = 32;
		const maxPos = chars.length;
		let pwd = '';
		for (let i = 0; i < len; i++) {
			pwd += chars.charAt(Math.floor(Math.random() * maxPos));
		}
		return pwd;
	}
}
module.exports = WxPayUtils;

其中rsa文件太大,放到了百度网盘上,自取
链接:https://pan.baidu.com/s/1bxTygUSDrGuvKGKtZYplDQ?pwd=ac4p
提取码:ac4p

发起支付

统一下单接口返回的数据可以在微信浏览器中使用独有api-WeixinJSBridge直接发起支付,注意该api只存在于微信浏览器。

const {result} = await payBill({ // 向后端发起请求的方法
    money: this.price,
    openid: uni.getStorageSync('openId'),
});
weiPay(result).then((res) => {
//支付成功
})

weiPay函数如下

export function weiPay(params) {
return new Promise((resolve, reject) => {
    WeixinJSBridge.invoke('getBrandWCPayRequest', params,
        function(res) {
                if (res.err_msg == "get_brand_wcpay_request:ok") {
                        // 使用以上方式判断前端返回,微信团队郑重提示:
                        //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
                        resolve(res)
                } else {
                        reject(res)
                }
        });
})
}

注意事项

注意在第一步跳转微信制定页面的时候,给微信的返回页面必须是https,如果是http,那么支付就会出现问题,比如说提示你某个参数不能为空,实际上根本没有这个参数,但是用https就不会有这个问题

<think>嗯,用户想了解如何使用Node.js实现微信支付的Native支付功能。首先,我需要回忆一下微信支付流程,特别是Native支付的部分。Native支付适用于PC网站,用户扫描二维码后完成支付。所以,我需要考虑整个流程,包括生成订单、获取二维码、处理回调通知等步骤。 首先,用户可能需要了解准备工作,比如申请商户号、设置API密钥等。这部分很重要,因为后续的接口调用都需要这些信息。然后,生成预支付订单,调用统一下单接口,这里需要用到的参数比如appid、mch_id、nonce_str、sign_type、body、out_trade_no、total_fee、spbill_create_ip、notify_url、trade_type等。需要注意的是,Native支付的trade_type是NATIVE,product_id可能也需要,但根据微信文档,可能需要确认是否需要这个参数。 接下来,生成签名。微信支付要求对参数进行签名,使用HMAC-SHA256或者MD5,现在可能默认是HMAC-SHA256。生成签名的步骤是将参数按ASCII码排序,拼接成字符串,加上key,然后进行加密。这可能会容易出错,比如参数顺序不对或者拼接错误,需要特别注意。 然后,调用统一下单接口,获取返回的code_url,这个就是生成二维码的URL。用户需要用这个URL生成二维码图片,供用户扫描支付。生成二维码可以使用第三方库,比如qrcode,这个在Node.js中比较常用。 处理异步通知也是关键的一步。微信支付成功后,会向notify_url发送POST请求,携带XML格式的数据。服务器需要验证签名,确认请求的合法性,然后处理业务逻辑,比如更新订单状态。处理完成后,需要返回成功的XML响应,否则微信会多次通知。 查询订单状态可能也是需要的,特别是在用户支付后,前端可能需要轮询检查支付状态,或者处理异步通知时出现问题,需要主动查询。 可能还需要考虑异常处理,比如网络错误、签名错误、参数错误等。此外,商户订单号的生成需要保证唯一性,通常可以使用时间戳加随机数的方式。 用户可能对具体的代码实现感兴趣,所以给出一个示例代码会很有帮助。代码应该包括生成订单、签名生成、处理回调等部分。同时,需要注意安全,比如API密钥的保管,防止泄露。 另外,需要注意微信支付的API版本,不同版本可能有不同的要求。比如现在V3版本的API可能和之前的V2有所不同,参数和签名方式可能会有变化,所以需要参考最新的官方文档。 可能用户在实际开发中会遇到的问题包括签名错误、回调通知处理不当导致重复通知、二维码生成失败等。需要提醒用户仔细检查参数是否正确,尤其是金额单位是否正确(分),以及异步通知的URL是否可访问,并且正确处理响应。 总结来说,回答需要涵盖准备工作、生成预支付订单、生成签名、调用接口、生成二维码、处理回调通知、查询订单等步骤,并提供示例代码和注意事项。同时,引导用户参考官方文档,确保信息的准确性。以下是</think>使用 Node.js 实现微信支付 Native 支付(PC网站二维码支付)的核心流程和代码示例: --- ### **一、准备工作** 1. 申请微信支付商户号(mchid)和 AppID 2. 设置 API 密钥(`API_KEY`) 3. 准备证书文件(部分接口需要) 4. 确保服务器 IP 加入微信支付白名单 --- ### **二、核心流程** 1. **生成预支付订单** ```javascript const crypto = require('crypto'); const axios = require('axios'); const xml2js = require('xml2js'); // 微信支付配置 const config = { appid: '你的AppID', mch_id: '商户号', key: 'API密钥', notify_url: 'https://yourdomain.com/notify' // 支付结果回调地址 }; // 生成随机字符串 function generateNonceStr() { return Math.random().toString(36).substr(2, 15); } // 生成签名(HMAC-SHA256) function createSign(params) { const sortedParams = Object.keys(params).sort().map(key => `${key}=${params[key]}`).join('&'); const signStr = sortedParams + `&key=${config.key}`; return crypto.createHash('sha256').update(signStr, 'utf8').digest('hex').toUpperCase(); } // 统一下单接口 async function createOrder(orderData) { const params = { appid: config.appid, mch_id: config.mch_id, nonce_str: generateNonceStr(), body: orderData.description, // 商品描述 out_trade_no: orderData.tradeNo, // 商户订单号 total_fee: orderData.amount, // 金额(单位:分) spbill_create_ip: orderData.clientIp, notify_url: config.notify_url, trade_type: 'NATIVE' }; params.sign = createSign(params); // 构造 XML 请求体 const builder = new xml2js.Builder(); const xmlBody = builder.buildObject({ xml: params }); // 发送请求 const response = await axios.post('https://api.mch.weixin.qq.com/pay/unifiedorder', xmlBody, { headers: { 'Content-Type': 'application/xml' } }); // 解析 XML 响应 const result = await xml2js.parseStringPromise(response.data, { explicitArray: false }); return result.xml; } ``` 2. **生成支付二维码** ```javascript const QRCode = require('qrcode'); // 使用返回的 code_url 生成二维码 async function generateQRCode(codeUrl) { try { const qr = await QRCode.toDataURL(codeUrl); return `<img src="${qr}">`; // 返回 base64 图片或保存为文件 } catch (err) { throw new Error('生成二维码失败'); } } ``` 3. **处理支付回调** ```javascript const express = require('express'); const app = express(); app.post('/notify', express.text({ type: 'application/xml' }), async (req, res) => { // 解析 XML 通知 const result = await xml2js.parseStringPromise(req.body, { explicitArray: false }); const notifyData = result.xml; // 验证签名 const sign = notifyData.sign; delete notifyData.sign; const localSign = createSign(notifyData); if (sign === localSign) { if (notifyData.result_code === 'SUCCESS') { // 处理支付成功逻辑(更新订单状态) console.log('支付成功:', notifyData.out_trade_no); res.send('<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>'); } } else { res.status(403).send('签名验证失败'); } }); ``` --- ### **三、调用示例** ```javascript // 创建订单 const orderData = { tradeNo: 'ORDER_123456', description: '测试商品', amount: 100, // 1元 = 100分 clientIp: '用户IP' }; createOrder(orderData) .then(response => { if (response.return_code === 'SUCCESS') { generateQRCode(response.code_url).then(qr => { // 将 qr 返回给前端展示 }); } }) .catch(console.error); ``` --- ### **四、注意事项** 1. **金额单位**:微信支付使用 `分` 作为单位(例如 1元 = 100分) 2. **订单号唯一性**:`out_trade_no` 必须全局唯一 3. **超时处理**:二维码有效期为2小时,需处理超时未支付订单 4. **安全验证**:务必验证回调签名,防止伪造请求 5. **证书**:部分敏感接口(如退款)需使用商户证书 --- ### **五、参考文档** - [微信支付 Native 开发文档](https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1) - 最新 API 请以微信官方文档为准
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值