本文章适合初学者自主学习研究,欢迎大家互相学习提问。如有书写说明错误请及时联系我加以修正。谢谢~
一、准备工作
产品介绍
1.手机支付宝搜索->产品签约->签约组手->申请并开通面付款功能(无需营业执照)
ps:其他功能的开通需要申请营业执照
官网地址:小程序文档 - 支付宝文档中心
2.电脑网页打开:支付宝开放平台
完成接入并访问后进入“控制台”会看到当面付应用已开启:
3.点开应用完成(秘钥/证书)的设置
获取秘钥
1.官方 秘钥生成工具:小程序文档 - 支付宝文档中心
2. 生成私钥和公钥文件
3. 继续完成前部分步骤3的秘钥配置
沙箱测试环境
支付测试工具
完成沙箱测试环境秘钥的配置并启用(也可以使用系统默认秘钥)
安装沙箱测试支付宝App
账号密码
二、后端支付接口
框架技术:NodeJs + Express + TS + MySql
秘钥文件
创建文件存储“应用私钥”和“支付宝公钥”(在步骤5中获取)
配置SDK
1.安装支付宝支付SDK:npm install alipay-sdk --save
npm文档地址:alipay-sdk - npm
2.初始化SDK
创建alipay.ts文件
目录结构
--------------
-utils
-alipay.ts
---------------
/**
* 对接支付宝 H5 支付
*/
import AlipaySdk from 'alipay-sdk' // 引入SDK
import { APP_PRIVATE_KEY, ALIPAY_PUBLIC_KEY } from '../config/payment-key'
export const alipaySdk = new AlipaySdk({
appId: '9021000123611250',
keyType: 'PKCS8', // node版本需要配置这个,否则会出现支付地址获取不到等问题
signType: 'RSA2', // 签名算法,默认 RSA2
gateway: 'https://openapi-sandbox.dl.alipaydev.com/gateway.do', // 支付宝网关地址 ,沙箱环境下使用时需要修改
alipayPublicKey: ALIPAY_PUBLIC_KEY, // 支付宝公钥
privateKey: APP_PRIVATE_KEY // 应用私钥
})
3.创建路由引入SDK完成支付下单
SDK官方文档:小程序文档 - 支付宝文档中心
创建订单接口
import express from 'express'
import { v4 as uuidv4 } from 'uuid'
import axios from 'axios' // 用于调用支付接口
// 创建注册页面路由
const payment = express.Router()
// 引入支付宝SDK
import { alipaySdk } from '../utils/alipay'
// 发起支付宝的当面付
payment.post('/inPerson', checkParamsIsNull, checkToken, (req, res) => {
const { totalAmount, ... } = req.body
const outTradeNo = uuidv4() // 订单号
// 填写订单基础信息
const bizContent = {
out_trade_no: outTradeNo, // 商户订单号,64个字符以内、可包含字母、数字、下划线,且不能重复
total_amount: totalAmount, // 订单总金额('0.01'),单位为元,精确到小数点后两位[string]类型
subject: '积分充值', // 订单标题
}
// 获取支付宝官方支付链接
const resultUrl = alipaySdk.pageExec('alipay.trade.precreate', {
method: 'GET',
bizContent
})
// 生成订单
axios.get(resultUrl).then(resultData => {
// 给前端发送订单信息
...
res.json({ ...resultData.data, msg: '订单创建成功' })
}).catch(err => {
res.json({ msg: '订单创建失败', err })
})
})
export default payment
解析订单接口
...
// 解析支付订单状态
payment.post('/queryOrder', checkParamsIsNull, checkToken, async (req, res) => {
const { outTradeNo, ... } = req.body
const bizContent = { outTradeNo }
const resultData = alipaySdk.pageExec('alipay.trade.query', {
method: 'GET',
bizContent
})
try {
// 查询订单结果
const queryRes = await axios.get(resultData)
const queryData = queryRes.data.alipay_trade_query_response
const { code, trade_status } = queryData
if (code === '10000') {
switch (trade_status) {
case 'WAIT_BUYER_PAY':
res.json({ msg: '交易创建,等待买家付款', code, status: trade_status })
break
case 'TRADE_CLOSED':
res.json({ msg: '未付款交易超时关闭,或支付完成后全额退款', code, status: trade_status })
break
case 'TRADE_SUCCESS':
// 支付成功的操作
...
res.json({ msg: '交易支付成功', queryData, code, status: trade_status })
break
case 'TRADE_FINISHED':
res.json({ msg: '交易结束,不可退款', code, status: trade_status })
break
default: res.end();
}
} else if (code === '40004') {
res.json({ msg: '等待支付', code })
} else {
res.json({ msg: '请根据文档:https://opendoc.alipay.com/common/02km9f中的code码查阅相关信息!', code })
}
} catch (err) {
res.json({ msg: '查询失败', err })
}
})
...
路由中间件
在真正进入路由前的操作(选填),如参数非空验证、Token验证...
import jwt from 'jsonwebtoken'
import { TOKEN_KEY } from '../config'
/*** 校验为空的参数[中间件] */
export const checkParamsIsNull = (req: any, res: any, next: Function) => {
const method = req.method.toUpperCase()
const params = method === 'GET' ? req.query : req.body
if (JSON.stringify(params) === '{}') return res.status(400).json({ msg: '未传递任何参数,请确认传递的方式是否正确!', code: 0 })
const keys = []
for (let key in params) {
if (!params[key]) keys.push(key)
}
if (keys.length > 0) return res.status(400).json({ msg: `传递的${keys.join('-')}为空`, code: 0 })
next()
}
/*** Token校验[中间件] */
export const checkToken = (req: any, res: any, next: Function) => {
const token = req.headers.authorization.split(' ')[1]
jwt.verify(token, TOKEN_KEY, (err: any, decoded: any) => {
if (err) {
// Token 校验失败
return res.status(401).json({ message: '无效的token', code: 0 })
}
// Token 验证通过
req.userId = decoded.userId // 将解码后的用户ID存储在请求中,以便后续处理使用
next()
})
}
三、前端调用支付接口
以uniapp框架为例
发起请求的API封装
api目录下的 pay.ts 文件
// 创建订单的参数类型
interface createOrderType {
totalAmount: string // 订单金额
...
}
/**
* 创建订单
* @returns Promise
*/
export const createOrderApi = (params: createOrderType) => {
return uni.request({
url: '/api/payment/inPerson', // '/api'前端代理接口(解决跨域)
method: 'POST',
header: { // 请求头设置Token(选填)
'authorization': `Bearer ${uni.getStorageSync('TOKEN')}`
},
data: params
})
}
// 订单查询参数类型
interface queryOrderType {
outTradeNo: string // 订单号
...
}
/**
* 解析订单是否支付成功
* @param outTradeNo 订单号
* ...
* @returns Promise
*/
export const queryPayOrderApi = (params: queryOrderType) => {
return uni.request({
url: '/api/payment/queryOrder',
method: 'POST',
header: { // 请求头设置Token(选填)
'authorization': `Bearer ${uni.getStorageSync('TOKEN')}`
},
data: params
})
}
调用创建支付接口
// 创建订单
const createPayOrde = () => {
...
createOrderApi({
totalAmount: totalAmount.value,
...
}).then((res: any) => {
const { alipay_trade_precreate_response: r } = res.data
if (r.code === '10000') {
const ordeInfo = {
total_amount: totalAmount.value, // 总金额
qr_code: r.qr_code, // 支付二维码
out_trade_no: r.out_trade_no, // 订单号
...
}
...
} else {
uni.hideLoading()
uni.showToast({ title: '订单创建失败', icon: 'error' })
}
}).catch(res => {
console.log(res.data.msg)
})
}
调用查询支付结果接口
const noticeTxt = ref<string>('请扫码完成支付') // 文本提示
const isTimeout = ref<boolean>(false) // 是否支付超时
const payStatus = ref<boolean>(false) // 是否支付完成
let payTimer // 支付定时器
...
// 判断支付是否完成
const paymentListen = () => {
if (payStatus.value) return noticeTxt.value = '该订单已完成'
payTimer = setTimeout(() => {
clearTimeout(payTimer)
if (isTimeout.value) return
queryPayOrderApi({outTradeNo, ...}).then(async (queryRes: any) => {
// console.log(queryRes.data)
const { code, status } = queryRes.data
if (code === '10000') {
switch (status) {
case 'TRADE_SUCCESS': noticeTxt.value = '支付成功!'
// 支付成功的处理程序
...
break
default:
noticeTxt.value = '支付中...'
paymentListen()
break
}
} else {
noticeTxt.value = '请扫描二维码完成支付'
paymentListen()
}
})
}, 1500)
}
效果图
四、生产环境
替换测试环境 alipay.ts 文件中的"appid"和"gateway" 与面付款应用保持一致