最近一直搞nestjs 成功的测试了小程序支付,在这里要强调下,小程序支付开发测试是完全可以在本地完成的。
并不需要想网上说的那样要配置生产环境等等。用微信开发工具点击支付,会出现二维码,当你的扫码一样是可以支付成功的。
基本原来在这简单介绍一下:
第一步: 前端发起请求
是客户端发起拿到后端给的统一下单的支付数据包,然后前端拿到代码像这样:
//使用uniapp的登陆模板创建的小程序
startPay() {
const self = this
let url = '/api/payment/pay'
let data = {
id: 1
}
this.$http.post(url, data).then(resdata => {
if (resdata.nonceStr) {
uni.requestPayment({
// appId: resdata.appId,
nonceStr: resdata.nonceStr,
package: resdata.package,
signType: resdata.signType,
paySign: resdata.paySign,
timeStamp: resdata.timeStamp.toString(),
//五个字段参与签名(区分大小写):appId,nonceStr,package,signType,timeStamp(需要注意的是,这5个参数签名排序的顺序按照ASCII字典序排序)
success: function (res) {
uni.showToast({
title: res,
icon: 'right'
})
},
fail: function (e) {
console.log(e, '_____')
},
complete: function (e) {
console.log(e, '+++++')
}
})
} else {
uni.showModal({
title: '提示',
content: data.retMsg
})
}
})
只要你返回的数据包签名这些关键的东西是正确的就ok。
第二步:后端返回正确的签名和package数据
首先准备好统一下单的数包,最终会是一个xml的格式
wxSendData(appId, productIntro, mchId, nonceStr, notifyUrl, openId, tradeId, ip, price, sign) {
const sendData = '' +
'' + appId + ' ' +
'' + productIntro + '' +
'' + mchId + ' ' +
'' + nonceStr + ' ' +
'' + notifyUrl + ' ' +
'' + openId + ' ' +
'' + tradeId + ' ' +
'' + ip + ' ' +
'' + price + ' ' +
'JSAPI ' +
'' + sign + ' ' +
' '
return sendData
}
把这个数据用 http客户端工具发给微信的后台url
https://api.mch.weixin.qq.com/pay/unifiedorder
然后会得到如下数据说明成功:
//这个数据是xml转换得来的
{ return_code: 'SUCCESS',
return_msg: 'OK',
appid: 'wxbdbfdessssssssa1feb25XXX',
mch_id: '15312ssd57841',
nonce_str: '3sKDU6oQ2nnvxP3t',
sign: '7E437FF7ED365FDB1C16CF8A03892E38',
result_code: 'SUCCESS',
prepay_id: 'wx161ss75813173293688b7230361648931100',
trade_type: 'JSAPI' }
把这个数据包的里面的两个关键数据拿出来包装下,返回给前端
(此时不用高兴地太早,你很可能会遇到各种支付签名的错误)
getPayParams(prepayId, tradeId) {
const nonceStr = this.getNonceStr()
const timeStamp = new Date().getTime().toString()
const packages = 'prepay_id=' + prepayId
const paySign = this.getPaySign(this.appid, timeStamp, nonceStr, packages)
// 前端需要的所有数据, 都从这里返回过去
const payParamsObj = {
nonceStr: nonceStr,
timeStamp: timeStamp,
package: packages,
paySign: paySign,
signType: 'MD5',
tradeId: tradeId,
}
return payParamsObj
}
基本到这里就完事了,有几个关键的东西注意别填错,尤其是商户apikey。
最终上完整代码是完全可以拿过去使用的,但是希望你仔细看好代码搞懂逻辑:
import { Injectable, HttpService } from '@nestjs/common';
import * as crypto from 'crypto-js';
import * as xml2json from 'xml2json';
import { map } from 'rxjs/operators';
@Injectable()
export class PaymentService {
constructor(private readonly httpService: HttpService) { }
private appid = 'wxbdb18';
private appsecret = '5cdbf0e435213b5ac';
private mchid = '1531241';
private mchkey = 'kajzhongy';
private notifyUrl = 'https://xcgshi.com/api/payment/notify';
private wxpayUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
async peyPackge(openid: string) {
let signData = {
//签名
appid: this.appid,
body: 'test',
mch_id: this.mchid,
nonce_str: this.getNonceStr(),
notify_url: this.notifyUrl,
openid: openid,
out_trade_no: this.getTradeId('kd'),
spbill_create_ip: '127.0.0.1',
total_fee: 1,
};
const sign = this.getPrePaySign(signData.appid, signData.body, signData.mch_id, signData.nonce_str, signData.notify_url, signData.openid, signData.out_trade_no, signData.spbill_create_ip, signData.total_fee)
const sendXmlData = this.wxSendData(signData.appid, signData.body, this.mchid, signData.nonce_str, signData.notify_url, signData.openid, signData.out_trade_no, signData.spbill_create_ip, signData.total_fee, sign)
let paydata = await this.httpService
.post(this.wxpayUrl, sendXmlData)
.pipe(map(response => response.data))
.toPromise();
paydata = JSON.parse(xml2json.toJson(paydata)).xml;
console.log(paydata)
const resData = this.getPayParams(paydata.prepay_id, signData.out_trade_no)
return resData
}
getNonceStr() {
var text = ""
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
for (var i = 0; i < 16; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length))
}
return text
}
getPaySign(appId, timeStamp, nonceStr, packages) {
var stringA = 'appId=' + appId +
'&nonceStr=' + nonceStr +
'&package=' + packages +
'&signType=MD5' +
'&timeStamp=' + timeStamp
var stringSignTemp = stringA + '&key=' + this.mchkey
var sign = crypto.MD5(stringSignTemp).toString().toUpperCase()
return sign
}
getTradeId(attach) {
var date = new Date().getTime().toString()
var text = ""
var possible = "0123456789"
for (var i = 0; i < 5; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length))
}
var tradeId = 'ty_' + attach + '_' + date + text
return tradeId
}
getPrePaySign(appId, productIntro, mchId, nonceStr, notifyUrl, openId, tradeId, ip, price) {
var stringA = 'appid=' + appId +
'&body=' + productIntro +
'&mch_id=' + mchId +
'&nonce_str=' + nonceStr +
'¬ify_url=' + notifyUrl +
'&openid=' + openId +
'&out_trade_no=' + tradeId +
'&spbill_create_ip=' + ip +
'&total_fee=' + price +
'&trade_type=JSAPI'
var stringSignTemp = stringA + '&key=' + this.mchkey
var sign = crypto.MD5(stringSignTemp).toString().toUpperCase()
return sign
}
wxSendData(appId, productIntro, mchId, nonceStr, notifyUrl, openId, tradeId, ip, price, sign) {
const sendData = '' +
'' + appId + ' ' +
'' + productIntro + '' +
'' + mchId + ' ' +
'' + nonceStr + ' ' +
'' + notifyUrl + ' ' +
'' + openId + ' ' +
'' + tradeId + ' ' +
'' + ip + ' ' +
'' + price + ' ' +
'JSAPI ' +
'' + sign + ' ' +
' '
return sendData
}
getPayParams(prepayId, tradeId) {
const nonceStr = this.getNonceStr()
const timeStamp = new Date().getTime().toString()
const packages = 'prepay_id=' + prepayId
const paySign = this.getPaySign(this.appid, timeStamp, nonceStr, packages)
// 前端需要的所有数据, 都从这里返回过去
const payParamsObj = {
nonceStr: nonceStr,
timeStamp: timeStamp,
package: packages,
paySign: paySign,
signType: 'MD5',
tradeId: tradeId,
}
return payParamsObj
}
}
···