简体中文
uni-pay-x
是uni-pay
的uni-app x
版,目前uni-pay-x
仅支持 Android 端,且只支持支付宝支付
本文档适用于客户端为
uni-app x
的版本,需 HBuilderX 4.02 及以上版本。若客户端为uni-app
则请访问:uni-pay 文档
支付,重要的变现手段,但开发复杂。在不同端,对接微信支付、支付宝等渠道,前端后端都要写不少代码。
涉及金额可不是小事,生成业务订单、获取收银台、发起支付、支付状态查询、支付异步回调、失败处理、发起退款、退款状态查询、支付统计...众多环节,代码量多,出错率高。
为什么不能有一个开源的、高质量的项目?即可以避免大家重复开发,又可以安心使用,不担心自己从头写产生Bug。
uni-pay-x
应需而生。
开发者在项目中引入 uni-pay-x
后,微信支付、支付宝支付等功能无需自己再开发。由于源码的开放性和层次结构清晰,有二次开发需求也很方便调整。
线上体验地址
uni-pay-x
的功能包括:
页面
云对象(uni-pay-co)
支付统计(内置于uni-admin的支付统计中)
uni-pay-x云端一体模板,包含前端页面、云对象、云端公共模块、uni-config-center配置、opendb数据表等内容。以及内置于uni-admin的支付统计报表。
uni-pay-x的uni_modules中包含了前端页面、云对象和公共模块,目录结构如下:
├─uni_modules 存放[uni_module](https://uniapp.dcloud.net.cn/plugin/uni_modules.html)规范的插件。
│ ├─其他module
│ └─uni-pay-x
│ ├─uniCloud
│ │ └─cloudfunctions 云函数目录
│ │ ├─common 云端公共模块目录
│ │ └─uni-pay uni-pay公共模块
│ │ └─uni-pay-co 集成调用uni-pay方法的云对象
│ │ ├─common 公用逻辑
│ │ ├─config 配置
│ │ │ └─permission.js 调用接口所需的权限配置
│ │ ├─dao 数据库操作相关API
│ │ ├─lang 国际化目录
│ │ ├─lib 基础功能(不建议修改此目录下文件)
│ │ │ ├─alipay.js 支付宝平台相关API
│ │ │ ├─common.js 一些通用API
│ │ │ ├─crypto.js 跨云函数通信加解密API
│ │ │ ├─qrcode.js 云端生成二维码的插件(来自于npm i qrcode的压缩版)
│ │ │ ├─wxpay.js 微信支付平台相关API
│ │ ├─middleware 中间件
│ │ ├─notify 异步通知逻辑(你自己的异步通知逻辑写在这里)
│ │ ├─service 云对象方法的服务实现
│ │ └─index.obj.js 云对象入口文件
│ ├─components 组件目录
│ │ └─uni-pay
│ │ └─uni-pay.vue uni-pay收银台弹窗组件
│ │ └─uni-pay-popup
│ │ └─uni-pay-popup.vue 弹窗子组件
│ ├─js_sdk js sdk目录
│ │ └─js_sdk.js
│ ├─pages 页面目录
│ │ └─success
│ │ └─success.js 支付成功结果页
│ │ └─pay-desk
│ │ └─pay-desk.js 收银台页面
│ ├─static 静态资源目录
│ ├─changelog.md 更新日志
│ ├─package.json 包管理文件
│ └─readme.md 插件自述文件
支付配置不在插件目录中,统一存放在 uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js
查看支付配置介绍
支付插件需要创建以下表后才能正常运行,可以右键 database
目录,初始化数据库功能来创建表。
在对接自己的项目之前,建议先跑通示例项目,能跑通示例项目,代表你的配置和证书一定是正确的,然后再将uni-pay-x
集成到你自己的项目中。
uni-pay-x
示例项目。前往插件市场uni-pay
配置文件,配置文件地址: uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js
查看支付配置介绍uni-config-center
(右键,上传公共模块,每次修改了支付配置,都需要重新上传此模块才会生效)uni-pay
(右键,上传公共模块)uni-pay-co
(右键,上传部署。当然对uniCloud目录点右键批量上传也可以)注意:测试支付回调必须选择云端云函数环境
开发者在微信和支付宝的支付后台,需要申请开通支付服务,成功后会得到各种凭据,这些凭据要配置在uni-pay的配置中。
这里是微信、支付宝全平台支付配置样例。如果只使用部分支付方式,后续有专门的分渠道章节。
配置文件在 uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js
const fs = require('fs');
const path = require('path')
module.exports = {
// 统一 - 支付回调地址,格式为 "服务空间ID":"URL化地址"
"notifyUrl": {
// 测试环境服务空间-支付回调地址
"mp-b267e273-19a7-4288-99c7-f6f27f9c5b77": "https://fc-mp-b267e273-19a7-4288-99c7-f6f27f9c5b77.next.bspapp.com/uni-pay-co",
// 线上环境服务空间-支付回调地址(如果只有一个服务空间,则只需要配置线上环境服务空间即可)
"mp-499e2a37-0c77-418a-82aa-3e5820ecb057": "https://fc-mp-499e2a37-0c77-418a-82aa-3e5820ecb057.next.bspapp.com/uni-pay-co",
},
"notifyKey":"5FB2CD73C7B53918728417C50762E6D45FB2CD73C7B53918728417C50762E6D4", // 跨云函数通信时的加密密钥,建议手动改下,不要使用默认的密钥,长度保持在64位以上即可
// 微信支付相关
"wxpay": {
"enable": true, // 是否启用微信支付
// 微信 - 小程序支付
"mp": {
"appId": "", // 小程序的appid
"secret": "", // 小程序的secret
"mchId": "", // 商户id
"key": "", // v2的api key
"pfx": fs.readFileSync(__dirname + '/wxpay/apiclient_cert.p12'), // v2需要用到的证书
"v3Key": "", // v3的api key
"appCertPath": path.join(__dirname, 'wxpay/apiclient_cert.pem'), // v3需要用到的证书
"appPrivateKeyPath": path.join(__dirname, 'wxpay/apiclient_key.pem'), // v3需要用到的证书
"version": 2, // 启用支付的版本 2代表v2版本 3 代表v3版本
},
// 微信 - APP支付
"app": {
"appId": "", // app开放平台下的应用的appid
"secret": "", // app开放平台下的应用的secret
"mchId": "", // 商户id
"key": "", // v2的api key
"pfx": fs.readFileSync(__dirname + '/wxpay/apiclient_cert.p12'), // v2需要用到的证书
"v3Key": "", // v3的api key
"appCertPath": path.join(__dirname, 'wxpay/apiclient_cert.pem'), // v3需要用到的证书
"appPrivateKeyPath": path.join(__dirname, 'wxpay/apiclient_key.pem'), // v3需要用到的证书
"version": 2, // 启用支付的版本 2代表v2版本 3 代表v3版本
},
// 微信 - 扫码支付
"native": {
"appId": "", // 可以是小程序或公众号或app开放平台下的应用的任意一个appid
"secret": "", // secret
"mchId": "", // 商户id
"key": "", // v2的api key
"pfx": fs.readFileSync(__dirname + '/wxpay/apiclient_cert.p12'), // v2需要用到的证书
"v3Key": "", // v3的api key
"appCertPath": path.join(__dirname, 'wxpay/apiclient_cert.pem'), // v3需要用到的证书
"appPrivateKeyPath": path.join(__dirname, 'wxpay/apiclient_key.pem'), // v3需要用到的证书
"version": 2, // 启用支付的版本 2代表v2版本 3 代表v3版本
},
// 微信 - 公众号支付
"jsapi": {
"appId": "", // 公众号的appid
"secret": "", // 公众号的secret
"mchId": "", // 商户id
"key": "", // v2的api key
"pfx": fs.readFileSync(__dirname + '/wxpay/apiclient_cert.p12'), // v2需要用到的证书
"v3Key": "", // v3的api key
"appCertPath": path.join(__dirname, 'wxpay/apiclient_cert.pem'), // v3需要用到的证书
"appPrivateKeyPath": path.join(__dirname, 'wxpay/apiclient_key.pem'), // v3需要用到的证书
"version": 2, // 启用支付的版本 2代表v2版本 3 代表v3版本
},
// 微信 - 手机外部浏览器H5支付
"mweb": {
"appId": "", // 可以是小程序或公众号或app开放平台下的应用的任意一个appid
"secret": "", // secret
"mchId": "", // 商户id
"key": "", // v2的api key
"pfx": fs.readFileSync(__dirname + '/wxpay/apiclient_cert.p12'), // v2需要用到的证书
"v3Key": "", // v3的api key
"appCertPath": path.join(__dirname, 'wxpay/apiclient_cert.pem'), // v3需要用到的证书
"appPrivateKeyPath": path.join(__dirname, 'wxpay/apiclient_key.pem'), // v3需要用到的证书
"version": 2, // 启用支付的版本 2代表v2版本 3 代表v3版本
// 场景信息,必填
"sceneInfo": {
"h5_info": {
"type": "Wap", // 此值固定Wap
"wap_url": "", // 你的H5首页地址,必须和你发起支付的页面的域名一致。
"wap_name": "", // 你的H5网站名称
}
}
},
},
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - 小程序支付配置
"mp": {
"appId": "", // 支付宝小程序appid
"privateKey": "", // 支付宝商户PKCS8格式的私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
},
// 支付宝 - APP支付配置
"app": {
"appId": "", // 支付宝开放平台下应用的appid
"privateKey": "", // 支付宝商户PKCS8格式的私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
},
// 支付宝 - H5支付配置(包含:网站二维码、手机H5,需申请支付宝当面付接口权限)
"native": {
"appId": "", // 支付宝开放平台下应用的appid
"privateKey": "", // 支付宝商户PKCS8格式的私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
}
},
// 苹果虚拟支付相关
"appleiap": {
// 苹果虚拟支付支付,参数获取地址:https://appstoreconnect.apple.com/access/integrations/api/subs
"app": {
"appId": "", // 密钥ID
"issuerId": "", // Issuer ID
"bundleId": "", // 正式包名(如果dev包名和正式包名一致,则只填bundleId即可)
"devBundleId": "", // dev包名(如果dev包名和正式包名一致,则devBundleId可不填)
"appCertPath": path.join(__dirname, 'appleiap/apiclient_cert.p8'), // 证书路径
"sandbox": true, // 是否是沙箱环境
},
},
// 微信虚拟支付
"wxpay-virtual": {
// 微信 - 小程序支付
"mp": {
"appId": "", // 小程序的appid
"secret": "",
"mchId": "", // 商户id
"offerId": "", // 支付应用ID
"appKey": "", // 现网AppKey(正式环境)
"sandboxAppKey": "", // 沙箱AppKey
"rate": 100, // 代币兑换比例,比如1元兑换100代币,那么这里就是100(需要开通虚拟支付的时候也设置成 1 人民币 = 100 代币)
"token": "", // 微信小程序通信的token,在开发 - 开发管理 - 消息推送 - Token(令牌)
"encodingAESKey": "", // 必须43位,微信小程序消息加密密钥,在开发 - 开发管理 - 消息推送 - EncodingAESKey(消息加解密密钥)
"sandbox": false, // 是否是沙箱环境(注意:沙箱环境异步回调可能有延迟,建议直接正式环境测试)
}
}
}
如果你对支付配置中各参数如何获取有疑问,请点击获取支付配置帮助
注意
微信支付同时支持V2版本和V3版本
以微信小程序支付为例
V2版本
"mp": {
"appId": "", // 小程序的appid
"secret": "", // 小程序的secret
"mchId": "", // 商户id
"key": "", // v2的api key
"pfx": fs.readFileSync(__dirname + '/wxpay/apiclient_cert.p12'), // v2需要用到的证书
"version": 2, // 启用支付的版本 2代表v2版本 3 代表v3版本
},
V3版本
"mp": {
"appId": "", // 小程序的appid
"secret": "", // 小程序的secret
"mchId": "", // 商户id
"v3Key": "", // v3的api key
"appCertPath": path.join(__dirname, 'wxpay/apiclient_cert.pem'), // v3需要用到的证书
"appPrivateKeyPath": path.join(__dirname, 'wxpay/apiclient_key.pem'), // v3需要用到的证书
"version": 3, // 启用支付的版本 2代表v2版本 3 代表v3版本
},
当然你也可以全部配置了,这样可以方便自由切换V2和V3
"mp": {
"appId": "", // 小程序的appid
"secret": "", // 小程序的secret
"mchId": "", // 商户id
"key": "", // v2的api key
"pfx": fs.readFileSync(__dirname + '/wxpay/apiclient_cert.p12'), // v2需要用到的证书
"v3Key": "", // v3的api key
"appCertPath": path.join(__dirname, 'wxpay/apiclient_cert.pem'), // v3需要用到的证书
"appPrivateKeyPath": path.join(__dirname, 'wxpay/apiclient_key.pem'), // v3需要用到的证书
"version": 2, // 启用支付的版本 2代表v2版本 3 代表v3版本
},
对应支付配置的节点是 notifyUrl
示例
// 统一 - 支付回调地址,格式为 "服务空间ID":"URL化地址"
"notifyUrl": {
// 测试环境服务空间-支付回调地址
"mp-b267e273-19a7-4288-99c7-f6f27f9c5b77": "https://fc-mp-b267e273-19a7-4288-99c7-f6f27f9c5b77.next.bspapp.com/uni-pay-co",
// 线上环境服务空间-支付回调地址(如果只有一个服务空间,则只需要配置线上环境服务空间即可)
"mp-499e2a37-0c77-418a-82aa-3e5820ecb057": "https://fc-mp-499e2a37-0c77-418a-82aa-3e5820ecb057.next.bspapp.com/uni-pay-co",
},
格式为 "服务空间ID": "URL化地址"
服务空间ID如何获取?
点击此处进入服务空间列表,找到你项目用的服务空间,复制其服务空间ID
URL化地址如何获取?
点击此处进入服务空间列表,找到你项目用的服务空间,点击服务空间名称进入空间详情页,点击左侧菜单【云函数/云对象】- 点击【uni-pay-co】云对象右侧的【详情】按钮
进入详情后,点下面的【复制路径】,复制的内容就是【URL化地址】
上面的配置样例是微信和支付宝全端配置样例。如果只使用一种支付场景,比如微信公众号里的微信支付,可以看下面章节的分渠道支付配置样例。
对应支付配置的节点是 wxpay.app
const fs = require('fs');
const path = require('path')
module.exports = {
// 统一 - 支付回调地址,格式为 "服务空间ID":"URL化地址"
"notifyUrl": {
// 测试环境服务空间-支付回调地址
"mp-b267e273-19a7-4288-99c7-f6f27f9c5b77": "https://fc-mp-b267e273-19a7-4288-99c7-f6f27f9c5b77.next.bspapp.com/uni-pay-co",
// 线上环境服务空间-支付回调地址(如果只有一个服务空间,则只需要配置线上环境服务空间即可)
"mp-499e2a37-0c77-418a-82aa-3e5820ecb057": "https://fc-mp-499e2a37-0c77-418a-82aa-3e5820ecb057.next.bspapp.com/uni-pay-co",
},
// 微信支付相关
"wxpay": {
"enable": true, // 是否启用微信支付
// 微信 - APP支付
"app": {
"appId": "", // app开放平台下的应用的appid
"secret": "", // app开放平台下的应用的secret
"mchId": "", // 商户id
"key": "", // v2的api key
"pfx": fs.readFileSync(__dirname + '/wxpay/apiclient_cert.p12'), // v2需要用到的证书
"v3Key": "", // v3的api key
"appCertPath": path.join(__dirname, 'wxpay/apiclient_cert.pem'), // v3需要用到的证书
"appPrivateKeyPath": path.join(__dirname, 'wxpay/apiclient_key.pem'), // v3需要用到的证书
"version": 2, // 启用支付的版本 2代表v2版本 3 代表v3版本
},
},
}
对应支付配置的节点是 wxpay.mp
const fs = require('fs');
const path = require('path')
module.exports = {
// 统一 - 支付回调地址,格式为 "服务空间ID":"URL化地址"
"notifyUrl": {
// 测试环境服务空间-支付回调地址
"mp-b267e273-19a7-4288-99c7-f6f27f9c5b77": "https://fc-mp-b267e273-19a7-4288-99c7-f6f27f9c5b77.next.bspapp.com/uni-pay-co",
// 线上环境服务空间-支付回调地址(如果只有一个服务空间,则只需要配置线上环境服务空间即可)
"mp-499e2a37-0c77-418a-82aa-3e5820ecb057": "https://fc-mp-499e2a37-0c77-418a-82aa-3e5820ecb057.next.bspapp.com/uni-pay-co",
},
// 微信支付相关
"wxpay": {
"enable": true, // 是否启用微信支付
// 微信 - 小程序支付
"mp": {
"appId": "", // 小程序的appid
"secret": "", // 小程序的secret
"mchId": "", // 商户id
"key": "", // v2的api key
"pfx": fs.readFileSync(__dirname + '/wxpay/apiclient_cert.p12'), // v2需要用到的证书
"v3Key": "", // v3的api key
"appCertPath": path.join(__dirname, 'wxpay/apiclient_cert.pem'), // v3需要用到的证书
"appPrivateKeyPath": path.join(__dirname, 'wxpay/apiclient_key.pem'), // v3需要用到的证书
"version": 2, // 启用支付的版本 2代表v2版本 3 代表v3版本
},
},
}
对应支付配置的节点是 wxpay.jsapi
const fs = require('fs');
const path = require('path')
module.exports = {
// 统一 - 支付回调地址,格式为 "服务空间ID":"URL化地址"
"notifyUrl": {
// 测试环境服务空间-支付回调地址
"mp-b267e273-19a7-4288-99c7-f6f27f9c5b77": "https://fc-mp-b267e273-19a7-4288-99c7-f6f27f9c5b77.next.bspapp.com/uni-pay-co",
// 线上环境服务空间-支付回调地址(如果只有一个服务空间,则只需要配置线上环境服务空间即可)
"mp-499e2a37-0c77-418a-82aa-3e5820ecb057": "https://fc-mp-499e2a37-0c77-418a-82aa-3e5820ecb057.next.bspapp.com/uni-pay-co",
},
// 微信支付相关
"wxpay": {
"enable": true, // 是否启用微信支付
// 微信 - 公众号支付
"jsapi": {
"appId": "", // 公众号的appid
"secret": "", // 公众号的secret
"mchId": "", // 商户id
"key": "", // v2的api key
"pfx": fs.readFileSync(__dirname + '/wxpay/apiclient_cert.p12'), // v2需要用到的证书
"v3Key": "", // v3的api key
"appCertPath": path.join(__dirname, 'wxpay/apiclient_cert.pem'), // v3需要用到的证书
"appPrivateKeyPath": path.join(__dirname, 'wxpay/apiclient_key.pem'), // v3需要用到的证书
"version": 2, // 启用支付的版本 2代表v2版本 3 代表v3版本
},
},
}
对应支付配置的节点是 wxpay.mweb
const fs = require('fs');
const path = require('path')
module.exports = {
// 统一 - 支付回调地址,格式为 "服务空间ID":"URL化地址"
"notifyUrl": {
// 测试环境服务空间-支付回调地址
"mp-b267e273-19a7-4288-99c7-f6f27f9c5b77": "https://fc-mp-b267e273-19a7-4288-99c7-f6f27f9c5b77.next.bspapp.com/uni-pay-co",
// 线上环境服务空间-支付回调地址(如果只有一个服务空间,则只需要配置线上环境服务空间即可)
"mp-499e2a37-0c77-418a-82aa-3e5820ecb057": "https://fc-mp-499e2a37-0c77-418a-82aa-3e5820ecb057.next.bspapp.com/uni-pay-co",
},
// 微信支付相关
"wxpay": {
"enable": true, // 是否启用微信支付
// 微信 - 手机外部浏览器H5支付
"mweb": {
"appId": "", // 可以是小程序或公众号或app开放平台下的应用的任意一个appid
"secret": "", // secret
"mchId": "", // 商户id
"key": "", // v2的api key
"pfx": fs.readFileSync(__dirname + '/wxpay/apiclient_cert.p12'), // v2需要用到的证书
"v3Key": "", // v3的api key
"appCertPath": path.join(__dirname, 'wxpay/apiclient_cert.pem'), // v3需要用到的证书
"appPrivateKeyPath": path.join(__dirname, 'wxpay/apiclient_key.pem'), // v3需要用到的证书
"version": 2, // 启用支付的版本 2代表v2版本 3 代表v3版本
// 场景信息,必填
"sceneInfo": {
"h5_info": {
"type": "Wap", // 此值固定Wap
"wap_url": "", // 你的H5首页地址,必须和你发起支付的页面的域名一致。
"wap_name": "", // 你的H5网站名称
}
}
},
},
}
对应支付配置的节点是 wxpay.native
const fs = require('fs');
const path = require('path')
module.exports = {
// 统一 - 支付回调地址,格式为 "服务空间ID":"URL化地址"
"notifyUrl": {
// 测试环境服务空间-支付回调地址
"mp-b267e273-19a7-4288-99c7-f6f27f9c5b77": "https://fc-mp-b267e273-19a7-4288-99c7-f6f27f9c5b77.next.bspapp.com/uni-pay-co",
// 线上环境服务空间-支付回调地址(如果只有一个服务空间,则只需要配置线上环境服务空间即可)
"mp-499e2a37-0c77-418a-82aa-3e5820ecb057": "https://fc-mp-499e2a37-0c77-418a-82aa-3e5820ecb057.next.bspapp.com/uni-pay-co",
},
// 微信支付相关
"wxpay": {
"enable": true, // 是否启用微信支付
// 微信 - 扫码支付
"native": {
"appId": "", // 可以是小程序或公众号或app开放平台下的应用的任意一个appid
"secret": "", // secret
"mchId": "", // 商户id
"key": "", // v2的api key
"pfx": fs.readFileSync(__dirname + '/wxpay/apiclient_cert.p12'), // v2需要用到的证书
"v3Key": "", // v3的api key
"appCertPath": path.join(__dirname, 'wxpay/apiclient_cert.pem'), // v3需要用到的证书
"appPrivateKeyPath": path.join(__dirname, 'wxpay/apiclient_key.pem'), // v3需要用到的证书
"version": 2, // 启用支付的版本 2代表v2版本 3 代表v3版本
},
},
}
对应支付配置的节点是 alipay.app
const fs = require('fs');
const path = require('path')
module.exports = {
// 统一 - 支付回调地址,格式为 "服务空间ID":"URL化地址"
"notifyUrl": {
// 测试环境服务空间-支付回调地址
"mp-b267e273-19a7-4288-99c7-f6f27f9c5b77": "https://fc-mp-b267e273-19a7-4288-99c7-f6f27f9c5b77.next.bspapp.com/uni-pay-co",
// 线上环境服务空间-支付回调地址(如果只有一个服务空间,则只需要配置线上环境服务空间即可)
"mp-499e2a37-0c77-418a-82aa-3e5820ecb057": "https://fc-mp-499e2a37-0c77-418a-82aa-3e5820ecb057.next.bspapp.com/uni-pay-co",
},
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - APP支付配置
"app": {
"appId": "", // 支付宝开放平台下应用的appid
"privateKey": "", // 支付宝商户PKCS8格式的私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
},
}
}
对应支付配置的节点是 alipay.mp
const fs = require('fs');
const path = require('path')
module.exports = {
// 统一 - 支付回调地址,格式为 "服务空间ID":"URL化地址"
"notifyUrl": {
// 测试环境服务空间-支付回调地址
"mp-b267e273-19a7-4288-99c7-f6f27f9c5b77": "https://fc-mp-b267e273-19a7-4288-99c7-f6f27f9c5b77.next.bspapp.com/uni-pay-co",
// 线上环境服务空间-支付回调地址(如果只有一个服务空间,则只需要配置线上环境服务空间即可)
"mp-499e2a37-0c77-418a-82aa-3e5820ecb057": "https://fc-mp-499e2a37-0c77-418a-82aa-3e5820ecb057.next.bspapp.com/uni-pay-co",
},
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - 小程序支付配置
"mp": {
"appId": "", // 支付宝小程序appid
"privateKey": "", // 支付宝商户PKCS8格式的私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
},
}
}
对应支付配置的节点是 alipay.native
(和PC扫码配置节点一样)
const fs = require('fs');
const path = require('path')
module.exports = {
// 统一 - 支付回调地址,格式为 "服务空间ID":"URL化地址"
"notifyUrl": {
// 测试环境服务空间-支付回调地址
"mp-b267e273-19a7-4288-99c7-f6f27f9c5b77": "https://fc-mp-b267e273-19a7-4288-99c7-f6f27f9c5b77.next.bspapp.com/uni-pay-co",
// 线上环境服务空间-支付回调地址(如果只有一个服务空间,则只需要配置线上环境服务空间即可)
"mp-499e2a37-0c77-418a-82aa-3e5820ecb057": "https://fc-mp-499e2a37-0c77-418a-82aa-3e5820ecb057.next.bspapp.com/uni-pay-co",
},
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - H5支付配置(包含:网站二维码、手机H5,需申请支付宝当面付接口权限)
"native": {
"appId": "", // 支付宝开放平台下应用的appid
"privateKey": "", // 支付宝商户PKCS8格式的私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
}
}
}
对应支付配置的节点是 alipay.native
const fs = require('fs');
const path = require('path')
module.exports = {
// 统一 - 支付回调地址,格式为 "服务空间ID":"URL化地址"
"notifyUrl": {
// 测试环境服务空间-支付回调地址
"mp-b267e273-19a7-4288-99c7-f6f27f9c5b77": "https://fc-mp-b267e273-19a7-4288-99c7-f6f27f9c5b77.next.bspapp.com/uni-pay-co",
// 线上环境服务空间-支付回调地址(如果只有一个服务空间,则只需要配置线上环境服务空间即可)
"mp-499e2a37-0c77-418a-82aa-3e5820ecb057": "https://fc-mp-499e2a37-0c77-418a-82aa-3e5820ecb057.next.bspapp.com/uni-pay-co",
},
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - H5支付配置(包含:网站二维码、手机H5,需申请支付宝当面付接口权限)
"native": {
"appId": "", // 支付宝开放平台下应用的appid
"privateKey": "", // 支付宝商户PKCS8格式的私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
}
}
}
对应支付配置的节点是 appleiap.app
const fs = require('fs');
const path = require('path')
module.exports = {
// 统一 - 支付回调地址,格式为 "服务空间ID":"URL化地址"
"notifyUrl": {
// 测试环境服务空间-支付回调地址
"mp-b267e273-19a7-4288-99c7-f6f27f9c5b77": "https://fc-mp-b267e273-19a7-4288-99c7-f6f27f9c5b77.next.bspapp.com/uni-pay-co",
// 线上环境服务空间-支付回调地址(如果只有一个服务空间,则只需要配置线上环境服务空间即可)
"mp-499e2a37-0c77-418a-82aa-3e5820ecb057": "https://fc-mp-499e2a37-0c77-418a-82aa-3e5820ecb057.next.bspapp.com/uni-pay-co",
},
// 苹果虚拟支付相关
"appleiap": {
// 苹果虚拟支付支付,参数获取地址:https://appstoreconnect.apple.com/access/integrations/api/subs
"app": {
"appId": "", // 密钥ID
"issuerId": "", // Issuer ID
"bundleId": "", // 正式包名(如果dev包名和正式包名一致,则只填bundleId即可)
"devBundleId": "", // dev包名(如果dev包名和正式包名一致,则devBundleId可不填)
"appCertPath": path.join(__dirname, 'appleiap/apiclient_cert.p8'), // 证书路径
"sandbox": true, // 是否是沙箱环境
},
}
}
在对接自己的项目之前,建议先跑通示例项目,能跑通示例项目,代表你的配置和证书一定是正确的,然后再将uni-pay
集成到你自己的项目中。
uni-pay-x
插件到你自己的项目。前往插件市场uni-pay
配置文件,配置文件地址: uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js
到你的项目中 查看支付配置介绍uni-config-center
(右键,上传公共模块,每次修改了支付配置,都需要重新上传此模块才会生效)uni-pay
(右键,上传公共模块)uni-pay-co
(右键,上传部署)pages.json
添加subPackages
分包页面配置(如果页面已自动配置,则可无视此步骤)"pages": [
...你的页面
],
"subPackages": [
{
"root": "uni_modules/uni-pay-x/pages",
"pages": [
{
"path": "success/success",
"style": {
"navigationBarTitleText": "支付成功",
"backgroundColor": "#F8F8F8"
}
},
{
"path": "ad-interactive-webview/ad-interactive-webview",
"style": {
"navigationBarTitleText": "ad",
"backgroundColor": "#F8F8F8"
}
}
]
}
],
打开你需要进行支付的页面,一般是业务订单提交之后的页面来展现收银台。
template
内放一个 uni-pay
组件标签,声明ref,然后调用组件的API。如下注意:vue3下ref不可以等于组件名,因此这里 ref="payRef"
而不能是 ref="uniPay"
<template>
<view>
<button @click="open">唤起收银台支付</button>
<uni-pay ref="payRef" height="900rpx"></uni-pay>
</view>
</template>
非 setup 模式
<template>
<view>
<button @click="open">唤起收银台支付</button>
<uni-pay ref="payRef" height="900rpx"></uni-pay>
</view>
</template>
<script>
export default {
data() {
return {
total_fee: 1, // 支付金额,单位分 100 = 1元
order_no: "", // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: "", // 插件支付单号
description: "测试订单", // 支付描述
type: "test", // 支付回调类型 如 recharge 代表余额充值 goods 代表商品订单(可自定义,任意英文单词都可以,只要你在 uni-pay-co/notify/目录下创建对应的 xxx.js文件进行编写对应的回调逻辑即可)
custom:{
a: "a",
b: 1
} as UTSJSONObject,
}
},
methods: {
/**
* 发起支付(唤起收银台,如果只有一种支付方式,则收银台不会弹出来,会直接使用此支付方式)
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
open() {
this.order_no = `test` + Date.now();
this.out_trade_no = `${this.order_no}-1`;
// 打开支付收银台
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
payInstance.open({
total_fee: this.total_fee, // 支付金额,单位分 100 = 1元
order_no: this.order_no, // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: this.out_trade_no, // 插件支付单号
description: this.description, // 支付描述
type: this.type, // 支付回调类型
custom: this.custom, // 自定义数据
});
}
}
}
</script>
setup 模式
<template>
<view>
<button @click="open">唤起收银台支付</button>
<uni-pay ref="payRef" height="900rpx"></uni-pay>
</view>
</template>
<script setup>
import { ref } from 'vue';
let total_fee = ref(1); // 支付金额,单位分 100 = 1元
let order_no = ref(""); // 业务系统订单号(即你自己业务系统的订单表的订单号)
let out_trade_no = ref(""); // 插件支付单号
let description = ref("测试订单"); // 支付描述
let type = ref("test"); // 支付回调类型 如 recharge 代表余额充值 goods 代表商品订单(可自定义,任意英文单词都可以,只要你在 uni-pay-co/notify/目录下创建对应的 xxx.js文件进行编写对应的回调逻辑即可)
let custom = ref({
a: "a",
b: 1
} as UTSJSONObject);
const payRef : Ref<UniPayComponentPublicInstance | null> = ref(null);
const open = () => {
order_no.value = `test` + Date.now();
out_trade_no.value = `${order_no.value}-1`;
// 打开支付收银台
const payInstance = payRef.value as UniPayComponentPublicInstance;
if (payInstance != null) {
payInstance.open({
total_fee: total_fee.value, // 支付金额,单位分 100 = 1元
order_no: order_no.value, // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: out_trade_no.value, // 插件支付单号
description: description.value, // 支付描述
type: type.value, // 支付回调类型
custom: custom.value, // 自定义数据
});
}
}
</script>
当用户支付成功后,我们要给用户增加余额或者给业务订单标记支付成功,这些通过异步回调通知来实现的。
提示:异步回调通知写在 uni-pay-co/notify
目录下,在此目录新建2个js文件,分别为 recharge.js
、goods.js
文件,同时复制以下代码要你新建的2个js文件里。
代码如下
'use strict';
/**
* 此处建议只改下订单状态,保证能及时返回给第三方支付服务器成功状态
* 限制4秒内必须执行完全部的异步回调逻辑,建议将消息发送、返佣、业绩结算等业务逻辑异步处理(如用定时任务去处理这些异步逻辑)
* 特别注意:因为金额是前端传的,需要再判断下金额和你业务系统订单中的金额是否一致,如果不一致,直接返回 return false;
* 特别注意:因为金额是前端传的,需要再判断下金额和你业务系统订单中的金额是否一致,如果不一致,直接返回 return false;
* 特别注意:因为金额是前端传的,需要再判断下金额和你业务系统订单中的金额是否一致,如果不一致,直接返回 return false;
*/
module.exports = async (obj) => {
let user_order_success = true;
let { data = {} } = obj;
let {
order_no,
out_trade_no,
total_fee
} = data; // uni-pay-orders 表内的数据均可获取到
// 此处写你自己的支付成功逻辑开始-----------------------------------------------------------
// 因为金额total_fee是前端传的,因此有被用户篡改的风险,因此需要判断下total_fee的值是否和你业务订单中的金额一致,如果不一致,直接返回 return false;
// 有三种方式
// 方式一:直接写数据库操作
// 方式二:使用 await uniCloud.callFunction 调用其他云函数
// 方式三:使用 await uniCloud.httpclient.request 调用http接口地址
// 此处写你自己的支付成功逻辑结束-----------------------------------------------------------
// user_order_success = true 代表你自己的逻辑处理成功 返回 false 代表你自己的处理逻辑失败。
return user_order_success;
};
因为金额 total_fee
是前端传的,因此有被用户篡改的风险,因此需要 判断下total_fee的值是否和你业务订单中的金额一致
,如果不一致,直接返回 return false
注意
为什么要你自己创建.js文件,而不是插件默认给你创建好,这是因为后面当插件更新时,你写的代码会被插件更新的代码覆盖(一键合并功能),因此只要插件这里没有文件(而是你自己新建的文件),那么插件更新时,不会覆盖你自己新建的文件内的代码。
其中
recharge.js
内可以写余额充值相关的回调逻辑goods.js
内可以写商品订单付款成功后的回调逻辑最终调用哪个回调逻辑是根据你创建支付订单时,type
参数填的什么,type
如果填 recharge
则支付成功后就会执行 recharge.js
内的代码逻辑。
即前端调用支付时传的 type
参数
// 打开支付收银台
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
payInstance.open({
type: "recharge", // 支付回调类型 recharge 代表余额充值(当然你可以自己自定义)
});
注意:每次修改都需要重新上传云对象uni-pay-co
如果你的业务在uniCloud上,那么可以使用方式一或方式二进行编写自定义回调逻辑。
方式一:直接写数据库操作
适用场景:简单数据库操作场景
以给用户充值余额为例,代码如下
'use strict';
/**
* 此处建议只改下订单状态,保证能及时返回给第三方支付服务器成功状态
* 限制4秒内必须执行完全部的异步回调逻辑,建议将消息发送、返佣、业绩结算等业务逻辑异步处理(如用定时任务去处理这些异步逻辑)
* 特别注意:因为金额是前端传的,需要再判断下金额和你业务系统订单中的金额是否一致,如果不一致,直接返回 return false;
* 特别注意:因为金额是前端传的,需要再判断下金额和你业务系统订单中的金额是否一致,如果不一致,直接返回 return false;
* 特别注意:因为金额是前端传的,需要再判断下金额和你业务系统订单中的金额是否一致,如果不一致,直接返回 return false;
*/
module.exports = async (obj) => {
let user_order_success = true;
let { data = {} } = obj;
let {
order_no,
out_trade_no,
total_fee,
custom = {},
} = data; // uni-pay-orders 表内的数据均可获取到
// 此处写你自己的支付成功逻辑开始-----------------------------------------------------------
// 方式一:直接写数据库操作
// 此处只是简单演示下,实际数据库语句会更复杂一点。
const db = uniCloud.database();
const _ = db.command;
// 获取你的业务订单信息
let orderRes = await db.collection("你的业务订单表").where({ order_no }).get();
let orderInfo = orderRes.data[0];
// 给用户充值余额(此处没有判断total_fee是否和你业务订单的金额一致,正常需要判断下,不过如果是充值余额,则直接按用户付款的金额充值也没问题)
let res = await db.collection("uni-id-users").doc(orderInfo.user_id).update({
balance: _.inc(total_fee)
});
if (res && res.updated) {
user_order_success = true; // 通知插件我的自定义回调逻辑执行成功
} else {
user_order_success = false; // 通知插件我的自定义回调逻辑执行失败
}
// 此处写你自己的支付成功逻辑结束-----------------------------------------------------------
return user_order_success;
};
方式二:直接调用其他云函数或云对象
适用场景:业务较为复杂,需写在其他云函数或云对象里的场景。
调用其他云函数示例代码如下
'use strict';
/**
* 此处建议只改下订单状态,保证能及时返回给第三方支付服务器成功状态
* 限制4秒内必须执行完全部的异步回调逻辑,建议将消息发送、返佣、业绩结算等业务逻辑异步处理(如用定时任务去处理这些异步逻辑)
* 特别注意:因为金额是前端传的,需要再判断下金额和你业务系统订单中的金额是否一致,如果不一致,直接返回 return false;
* 特别注意:因为金额是前端传的,需要再判断下金额和你业务系统订单中的金额是否一致,如果不一致,直接返回 return false;
* 特别注意:因为金额是前端传的,需要再判断下金额和你业务系统订单中的金额是否一致,如果不一致,直接返回 return false;
*/
const payCrypto = require('../libs/crypto.js'); // 获取加密服务
module.exports = async (obj) => {
let user_order_success = true;
let { data = {} } = obj;
let {
order_no,
out_trade_no,
total_fee
} = data; // uni-pay-orders 表内的数据均可获取到
// 此处写你自己的支付成功逻辑开始-----------------------------------------------------------
// 方式二安全模式一(加密)
let encrypted = payCrypto.aes.encrypt({
data: data, // 待加密的原文
});
await uniCloud.callFunction({
name: "你的云函数名称",
data: {
encrypted, // 传输加密数据(通过payCrypto.aes.decrypt解密)
},
});
// 解密示例
// let decrypted = payCrypto.aes.decrypt({
// data: encrypted, // 待解密的原文
// });
/*
// 方式二安全模式二(只传一个订单号 out_trade_no,你自己的回调里查数据库表 uni-pay-orders 判断 status是否等于1来判断是否真的支付了)
await uniCloud.callFunction({
name: "你的云函数名称",
data: {
out_trade_no, // 支付插件订单号
},
});
*/
// 此处写你自己的支付成功逻辑结束-----------------------------------------------------------
// user_order_success = true 代表你自己的逻辑处理成功 返回 false 代表你自己的处理逻辑失败。
return user_order_success;
};
调用其他云对象示例代码如下
'use strict';
/**
* 此处建议只改下订单状态,保证能及时返回给第三方支付服务器成功状态
* 限制4秒内必须执行完全部的异步回调逻辑,建议将消息发送、返佣、业绩结算等业务逻辑异步处理(如用定时任务去处理这些异步逻辑)
* 特别注意:因为金额是前端传的,需要再判断下金额和你业务系统订单中的金额是否一致,如果不一致,直接返回 return false;
* 特别注意:因为金额是前端传的,需要再判断下金额和你业务系统订单中的金额是否一致,如果不一致,直接返回 return false;
* 特别注意:因为金额是前端传的,需要再判断下金额和你业务系统订单中的金额是否一致,如果不一致,直接返回 return false;
*/
const payCrypto = require('../libs/crypto.js'); // 获取加密服务
module.exports = async (obj) => {
let user_order_success = true;
let { data = {} } = obj;
let {
order_no,
out_trade_no,
total_fee
} = data; // uni-pay-orders 表内的数据均可获取到
// 此处写你自己的支付成功逻辑开始-----------------------------------------------------------
// 方式二安全模式一(加密)
let encrypted = payCrypto.aes.encrypt({
data: data, // 待加密的原文
});
const cloudObject = uniCloud.importObject('你的云对象名称');
await cloudObject.rechargeBalance(encrypted); // 传输加密数据(通过payCrypto.aes.decrypt解密)
// 解密示例
// let decrypted = payCrypto.aes.decrypt({
// data: encrypted, // 待解密的原文
// });
/*
// 方式二安全模式二(只传一个订单号 out_trade_no,你自己的回调里查数据库表 uni-pay-orders 判断 status是否等于1来判断是否真的支付了)
const cloudObject = uniCloud.importObject('你的云对象名称');
await cloudObject.rechargeBalance(out_trade_no);
*/
// 此处写你自己的支付成功逻辑结束-----------------------------------------------------------
// user_order_success = true 代表你自己的逻辑处理成功 返回 false 代表你自己的处理逻辑失败。
return user_order_success;
};
如果你的业务不在uniCloud上,如java或php写的后端服务,uni-pay也可以满足你的支付需求,你只需要使用回调方式三的http接口形式调用你自己系统的回调接口即可。
方式三:使用 await uniCloud.httpclient.request 调用外部http接口
适用场景:业务不在uniCloud上。
示例代码如下
'use strict';
/**
* 此处建议只改下订单状态,保证能及时返回给第三方支付服务器成功状态
* 限制4秒内必须执行完全部的异步回调逻辑,建议将消息发送、返佣、业绩结算等业务逻辑异步处理(如用定时任务去处理这些异步逻辑)
* 特别注意:因为金额是前端传的,需要再判断下金额和你业务系统订单中的金额是否一致,如果不一致,直接返回 return false;
* 特别注意:因为金额是前端传的,需要再判断下金额和你业务系统订单中的金额是否一致,如果不一致,直接返回 return false;
* 特别注意:因为金额是前端传的,需要再判断下金额和你业务系统订单中的金额是否一致,如果不一致,直接返回 return false;
*/
const payCrypto = require('../libs/crypto.js'); // 获取加密服务
module.exports = async (obj) => {
let user_order_success = true;
let { data = {} } = obj;
let {
order_no,
out_trade_no,
total_fee
} = data; // uni-pay-orders 表内的数据均可获取到
// 此处写你自己的支付成功逻辑开始-----------------------------------------------------------
// 有三种方式
// 方式三:使用 await uniCloud.httpclient.request 调用http接口地址
// 方式三安全模式一(加密)uni-pay的版本需 >= 2.1.0
let encrypted = payCrypto.aes.encrypt({
mode: "aes-256-ecb",
data: data, // 待加密的原文
});
await uniCloud.httpclient.request("你的服务器接口请求地址", {
method: "POST",
data: {
encrypted, // 传输加密数据(服务端你再自己解密)
},
});
/*
// 方式三安全模式二(只传一个订单号 out_trade_no,你自己的回调里执行url请求来请求 uni-pay-co 云对象的 getOrder 接口来判断订单是否真的支付了)
await uniCloud.httpclient.request("你的服务器接口请求地址", {
method: "POST",
data: {
out_trade_no, // 支付插件订单号
},
});
*/
// 此处写你自己的支付成功逻辑结束-----------------------------------------------------------
// user_order_success = true 代表你自己的逻辑处理成功 返回 false 代表你自己的处理逻辑失败。
return user_order_success;
};
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class CryptoUtil {
// 调用示例
public static void main(String[] args) {
try {
String encrypted = "es2aF7DWr169X4fvMnlKNg=="; // 待解密的密文
String key = "12345678901234561234567890123456"; // 必须是固定的32位(只支持数字、英文)
// 解密
String decrypted = decrypt(encrypted, key);
System.out.println("decrypted: " + decrypted);
} catch (Exception e) {
e.printStackTrace();
}
}
// 解密函数
private static String decrypt(String encryptedData, String key) throws Exception {
if (key.length() > 32) {
key = key.substring(0, 32);
}
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
// 加密函数
private static String encrypt(String data, String key) throws Exception {
if (key.length() > 32) {
key = key.substring(0, 32);
}
byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedBytes = cipher.doFinal(dataBytes);
return Base64.getEncoder().encodeToString(encryptedBytes);
}
}
<?php
$key = '12345678901234561234567890123456'; // 必须是固定的32位(只支持数字、英文)
$encrypt = "es2aF7DWr169X4fvMnlKNg=="; // 待解密的内容
// 解密
$decrypt = openssl_decrypt(base64_decode($encrypt), 'aes-256-ecb', substr($key, 0, 32), OPENSSL_RAW_DATA);
echo $decrypt;
?>
运行你的项目,进行支付的体验和测试。
属性名 | 说明 | 类型 | 默认值 | 可选值 |
---|---|---|---|---|
adpid | uni-ad的广告位ID,若填写,则会在支付成功结果页展示广告(可以增加开发者广告收益) | string | - | - |
returnUrl | 支付成功后,用户点击【查看订单】按钮时跳转的页面地址,如果不填写此属性,则没有【查看订单】按钮 | string | - | - |
mode | 收银台模式,插件会自动识别,也可手动传参,mobile 手机模式 pc 电脑模式 | string | 自动识别 | mobile、pc |
logo | 当mode为PC时,展示的logo | string | /static/logo.png | - |
height | 收银台高度 | string | 900rpx | - |
事件名 | 说明 | 参数 |
---|---|---|
success | 支付成功的回调 | res |
cancel | 支付取消的回调 | res |
fail | 支付失败的回调 | res |
create | 创建支付订单时的回调(此时用户还未支付) | res |
mounted | 组件挂载到template时 | res |
通过
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
await payInstance.xxx();
方式调用,详情调用方式参考下方的【前端完整示例代码】
方法名 | 说明 |
---|---|
open | 发起支付 - 打开支付收银台弹窗 查看详情 |
createOrder | 直接发起支付(无收银台) 查看详情 |
getOrder | 查询订单 查看详情 |
refund | 发起退款(此接口需要权限才可以访问) 查看详情 |
getRefund | 查询退款 查看详情 |
closeOrder | 关闭订单 查看详情 |
getPayProviderFromCloud | 获取支持的支付供应商 查看详情 |
getProviderAppId | 获取支付配置内的appid(主要用于获取微信公众号的appid,用以获取code) 查看详情 |
getOpenid | 根据code获取openid (主要用于微信公众号code换取openid) 查看详情 |
前端完整示例代码
<template>
<view class="app">
<view>
<text style="color: red;">注意:uni-app x支持的支付方式详见uni.RequestPayment</text>
</view>
<view>
<view class="label">支付单号:</view>
<view><input class="input" v-model="out_trade_no" /></view>
</view>
<view>
<view class="label">支付金额(单位分,100=1元):</view>
<view><input class="input" v-model.number="total_fee" /></view>
</view>
<button class="button" @click="open()">打开收银台(弹窗模式)</button>
<!-- #ifdef APP || H5 -->
<view v-if="!isPcCom">
<button class="button" @click="toPayDesk">打开收银台(新页面模式)</button>
</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN || H5 || APP -->
<button class="button" @click="createOrder('wxpay')">发起支付(微信)</button>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY || H5 || APP -->
<button class="button" @click="createOrder('alipay')">发起支付(支付宝)</button>
<!-- #endif -->
<!-- #ifdef APP -->
<button class="button" @click="createQRcode('alipay')">APP扫码支付(支付宝)</button>
<!-- #endif -->
<button class="button" @click="getOrderPopup(true)">查询支付状态</button>
<button class="button" @click="pageTo('/uni_modules/uni-pay-x/pages/success/success?out_trade_no=test2024030501-1&order_no=test2024030501&total_fee=1&adpid=1000000001&return_url=/pages/order-detail/order-detail')">支付成功页面示例</button>
<!-- 查询支付的弹窗 -->
<uni-pay-popup ref="getOrderPopupRef" type="bottom">
<scroll-view direction="vertical" class="get-order-popup">
<view class="label">插件支付单号:</view>
<view class="mt20">
<input class="input pd2030" v-model="out_trade_no" placeholder="请输入" />
<view><text class="tips">插件支付单号和第三方交易单号2选1填即可</text> </view>
</view>
<view class="label">第三方交易单号:</view>
<view class="mt20">
<input class="input pd2030" v-model="transaction_id" placeholder="请输入" />
<view class="tips"><text class="tips">可从支付宝账单(订单号)、微信账单(交易单号)中复制</text></view>
</view>
<view class="mt20">
<button class="button" @click="getOrder">查询支付状态</button>
</view>
<view class="mt20" v-if="getOrderRes['transaction_id']">
<view class="table">
<view class="table-tr">
<view class="table-td label"><text class="text align-left">订单描述</text></view>
<view class="table-td"><text class="text align-right">{{ getOrderRes['description'] }}</text></view>
</view>
<view class="table-tr">
<view class="table-td label"><text class="text align-left">支付金额</text></view>
<view class="table-td"><text class="text align-right">{{ amountFormat(getOrderRes.getNumber('total_fee')) }}</text></view>
</view>
<view class="table-tr">
<view class="table-td label"><text class="text align-left">支付方式</text></view>
<view class="table-td"><text class="text align-right">{{ providerFormat(getOrderRes['provider'] as string) }}</text></view>
</view>
<view class="table-tr">
<view class="table-td label"><text class="text align-left">第三方交易单号</text></view>
<view class="table-td"><text class="text align-right">{{ getOrderRes['transaction_id'] }}</text></view>
</view>
<view class="table-tr">
<view class="table-td label"><text class="text align-left">插件支付单号</text></view>
<view class="table-td"><text class="text align-right">{{ getOrderRes['out_trade_no'] }}</text></view>
</view>
<view class="table-tr">
<view class="table-td label"><text class="text align-left">回调状态</text></view>
<view class="table-td"><text
class="text align-right">{{ getOrderRes.getBoolean('user_order_success') != null && getOrderRes.getBoolean('user_order_success') == true ? "成功" : "异常" }}</text>
</view>
</view>
</view>
</view>
</scroll-view>
</uni-pay-popup>
<!-- #ifdef APP -->
<!-- <button class="button" v-if="isIosAppCom" @click="pageTo('/pages/iosiap/iosiap')">苹果内购示例</button> -->
<!-- #endif -->
<!-- <button class="button" @click="refund">发起退款</button>
<view><text class="tips">发起退款需要admin权限,本示例未对接登录功能</text></view>
<button class="button" @click="getRefund">查询退款状态</button>
<button class="button" @click="closeOrder">关闭订单</button> -->
<!-- #ifdef H5 -->
<button class="button" v-if="h5Env === 'h5-weixin'" @click="getWeiXinJsCode('snsapi_base')">公众号获取openid示例</button>
<!-- #endif -->
<!-- 统一支付组件,注意:vue3下ref不可以等于组件名,因此这里ref="pay" 而不能是 ref="uniPay" -->
<uni-pay ref="payRef" :adpid="adpid" height="900rpx" return-url="/pages/order-detail/order-detail" logo="/static/logo.png" @success="onSuccess" @create="onCreate"
@fail="onFail" @cancel="onCancel"></uni-pay>
</view>
</template>
<script>
export default {
data() {
return {
total_fee: 1, // 支付金额,单位分 100 = 1元
order_no: "", // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: "", // 插件支付单号
description: "测试订单", // 支付描述
type: "test", // 支付回调类型 如 recharge 代表余额充值 goods 代表商品订单(可自定义,任意英文单词都可以,只要你在 uni-pay-co/notify/目录下创建对应的 xxx.js文件进行编写对应的回调逻辑即可)
openid: "", // 微信公众号需要
custom: {
a: "a",
b: 1
} as UTSJSONObject,
adpid: "1000000001", // uni-ad的广告位id
transaction_id: "", // 查询订单接口的查询条件
getOrderRes: {} as UTSJSONObject, // 查询订单支付成功后的返回值
}
},
onLoad(options) {
console.log('onLoad: ', options)
// #ifdef H5
// 微信公众号特殊逻辑开始-----------------------------------------------------------
// 以下代码仅为获取openid,正常你自己项目应该是登录后才能支付,登录后已经拿到openid,无需编写下面的代码
if (this.h5Env == 'h5-weixin') {
let openid = uni.getStorageSync("uni-pay-weixin-h5-openid");
let oldCode = uni.getStorageSync("uni-pay-weixin-h5-code");
if (openid != null && openid != "") {
this.openid = openid;
}
let code = options['code'] as string;
let state = options['state'] as string;
// 如果code和state有值,且此code没有被使用过,则执行获取微信公众号的openid
if (code != null && code != "" && state != null && state != "" && code != oldCode) {
// 获取微信公众号的openid
setTimeout(() => {
this.getOpenid({
provider: "wxpay",
code
});
}, 300);
} else if (!openid){
// 如果openid为空,则执行微信公众号的网页授权登录逻辑
setTimeout(() => {
this.getWeiXinJsCode('snsapi_base');
}, 300);
}
}
// 微信公众号特殊逻辑结束-----------------------------------------------------------
// #endif
},
methods: {
/**
* 发起支付(唤起收银台,如果只有一种支付方式,则收银台不会弹出来,会直接使用此支付方式)
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
open() {
this.order_no = `test` + Date.now();
this.out_trade_no = `${this.order_no}-1`;
// 打开支付收银台
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
payInstance.open({
total_fee: this.total_fee, // 支付金额,单位分 100 = 1元
order_no: this.order_no, // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: this.out_trade_no, // 插件支付单号
description: this.description, // 支付描述
type: this.type, // 支付回调类型
openid: this.openid, // 微信公众号需要
custom: this.custom, // 自定义数据
});
},
/**
* 发起支付(不唤起收银台,手动指定支付方式)
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
createOrder(provider : string) {
this.order_no = `test` + Date.now();
this.out_trade_no = `${this.order_no}-1`;
// 发起支付
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
payInstance.createOrder({
provider: provider, // 支付供应商
total_fee: this.total_fee, // 支付金额,单位分 100 = 1元
order_no: this.order_no, // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: this.out_trade_no, // 插件支付单号
description: this.description, // 支付描述
type: this.type, // 支付回调类型
openid: this.openid, // 微信公众号需要
custom: this.custom, // 自定义数据
});
},
/**
* 生成支付独立二维码(只返回支付二维码)
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
createQRcode(provider : string) {
this.order_no = `test` + Date.now();
this.out_trade_no = `${this.order_no}-1`;
// 发起支付
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
payInstance.createOrder({
provider: provider, // 支付供应商
total_fee: this.total_fee, // 支付金额,单位分 100 = 1元
order_no: this.order_no, // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: this.out_trade_no, // 插件支付单号
description: this.description, // 支付描述
type: this.type, // 支付回调类型
qr_code: true, // 强制扫码支付
openid: this.openid, // 微信公众号需要
custom: this.custom, // 自定义数据
});
},
/**
* 前往自定义收银台页面
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
toPayDesk() {
this.order_no = `test` + Date.now();
this.out_trade_no = `${this.order_no}-1`;
let options = {
total_fee: this.total_fee, // 支付金额,单位分 100 = 1元
order_no: this.order_no, // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: this.out_trade_no, // 插件支付单号
description: this.description, // 支付描述
type: this.type, // 支付回调类型
openid: this.openid, // 微信公众号需要
custom: this.custom, // 自定义数据
};
let optionsStr = encodeURI(JSON.stringify(options));
uni.navigateTo({
url: `/uni_modules/uni-pay-x/pages/pay-desk/pay-desk?options=${optionsStr}`
});
},
// 打开查询订单的弹窗
getOrderPopup(key : boolean) {
const getOrderPopupInstance = this.$refs["getOrderPopupRef"] as UniPayPopupComponentPublicInstance;
if (key) {
getOrderPopupInstance.open();
} else {
getOrderPopupInstance.close();
}
},
// 查询支付状态
async getOrder() : Promise<void> {
this.getOrderRes = {} as UTSJSONObject;
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
let getOrderData = {
await_notify: true
} as UTSJSONObject;
if (this.transaction_id != "") {
getOrderData['transaction_id'] = this.transaction_id;
} else if (this.out_trade_no != "") {
getOrderData['out_trade_no'] = this.out_trade_no;
}
let res = await payInstance.getOrder(getOrderData);
if (res != null && res['errCode'] == 0) {
this.getOrderRes = res.getJSON('pay_order') as UTSJSONObject;
let obj = {
"-1": "已关闭",
"1": "已支付",
"0": "未支付",
"2": "已部分退款",
"3": "已全额退款"
} as UTSJSONObject;
let status = res['status'] as number;
let statusStr = status + "";
let title = obj[statusStr] as string;
uni.showToast({
title: title,
icon: "none"
});
}
},
// 发起退款
async refund() : Promise<void> {
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
let res = await payInstance.refund({
out_trade_no: this.out_trade_no, // 插件支付单号
});
if (res != null && res['errCode'] == 0) {
uni.showToast({
title: res['errMsg'] as string,
icon: "none"
});
}
},
// 查询退款状态
async getRefund() : Promise<void> {
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
let res = await payInstance.getRefund({
out_trade_no: this.out_trade_no, // 插件支付单号
});
if (res != null && res['errCode'] == 0) {
uni.showModal({
content: res['errMsg'] as string,
showCancel: false
});
}
},
// 关闭订单
async closeOrder() : Promise<void> {
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
let res = await payInstance.closeOrder({
out_trade_no: this.out_trade_no, // 插件支付单号
});
if (res != null && res['errCode'] == 0) {
uni.showModal({
content: res['errMsg'] as string,
showCancel: false
});
}
},
// #ifdef H5
// 获取公众号code
async getWeiXinJsCode(scope = "snsapi_base") : Promise<void> {
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
let res = await payInstance.getProviderAppId({
provider: "wxpay",
provider_pay_type: "jsapi"
});
if (res != null && res['appid'] != null && res['appid'] != "") {
let appid = res['appid'] as string;
let redirect_uri = window.location.href.split("?")[0];
let url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=${scope}&state=STATE#wechat_redirect`;
window.location.href = url;
}
},
// 获取公众号openid
async getOpenid(data:UTSJSONObject) : Promise<void> {
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
let res = await payInstance.getOpenid(data);
if (res != null && res['openid'] != null && res['openid'] != "") {
let openid = res['openid'] as string;
let code = data['code'] as string;
this.openid = openid;
console.log('openid: ', openid)
// 将openid缓存到本地
uni.setStorageSync("uni-pay-weixin-h5-openid", openid);
uni.setStorageSync("uni-pay-weixin-h5-code", code);
uni.showToast({
title: "已获取到openid,可以开始支付",
icon: "none"
});
}
},
// #endif
// 监听事件 - 支付订单创建成功(此时用户还未支付)
onCreate(res : UTSJSONObject) {
console.log('create: ', res);
// 如果只是想生成支付二维码,不需要组件自带的弹窗,则在这里可以获取到支付二维码 qr_code_image
},
// 监听事件 - 支付成功
onSuccess(res : UTSJSONObject) {
console.log('success: ', res);
let user_order_success = res.getBoolean('user_order_success');
if (user_order_success != null && user_order_success != true) {
// 代表用户已付款,且你自己写的回调成功并正确执行了
} else {
// 代表用户已付款,但你自己写的回调执行失败(通常是因为你的回调代码有问题)
}
},
// 监听事件 - 支付失败
onFail(err : RequestPaymentFail) {
console.log('fail: ', err)
},
// 监听事件 - 取消支付
onCancel(err : RequestPaymentFail) {
console.log('cancel: ', err)
},
// 页面跳转
pageTo(url : string) {
uni.navigateTo({
url
});
},
// provider格式化
providerFormat(provider ?: string) : string {
if (provider == null) {
return "";
}
let providerObj = {
"wxpay": "微信支付",
"alipay": "支付宝支付",
"appleiap": "苹果虚拟"
} as UTSJSONObject;
let providerStr = providerObj[provider] as string;
return providerStr;
},
// amount格式化
amountFormat(totalFee : number | null) : string {
if (totalFee == null) {
return "0";
} else {
return (totalFee / 100).toFixed(2)
}
}
},
computed: {
// 计算当前H5环境
h5Env() : string {
// #ifdef H5
const ua = window.navigator.userAgent.toLowerCase();
const isWeixin = /micromessenger/i.test(ua);
const isAlipay = /alipay/i.test(ua);
const isMiniProgram = /miniprogram/i.test(ua);
if (isWeixin) {
if (isMiniProgram) {
return "mp-weixin";
} else {
return "h5-weixin";
}
} else if (isAlipay) {
if (isMiniProgram) {
return "mp-alipay";
} else {
return "h5-alipay";
}
}
return "h5";
// #endif
return "";
},
// 计算当前是否是ios app
isIosAppCom() : boolean {
let info = uni.getSystemInfoSync();
return info.uniPlatform === 'app' && info.osName === 'ios' ? true : false;
},
// 计算当前是否是PC环境
isPcCom() : boolean {
let isPC = false;
// #ifdef H5
let info = uni.getSystemInfoSync();
isPC = info.deviceType === 'pc' ? true : false;
// #endif
return isPC;
}
},
}
</script>
<style lang="scss" scoped>
.app {
padding: 30rpx;
}
.input {
border: 1px solid #f3f3f3;
padding: 10rpx;
width: 100%;
box-sizing: border-box;
height: 80rpx;
}
.button {
margin-top: 20rpx;
}
.label {
margin: 10rpx 0;
}
.tips {
margin: 20rpx 0;
font-size: 24rpx;
color: #565656;
}
.get-order-popup {
background-color: #ffffff;
padding: 30rpx;
height: 900rpx;
border-radius: 20rpx;
width: 690rpx;
}
.mt20 {
margin-top: 20rpx;
}
.pd2030 {
padding: 20rpx 30rpx;
}
.table {
.table-tr {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 10rpx 0;
}
.table-td {
flex: 1;
}
.align-left {
text-align: left;
}
.align-right {
text-align: right;
}
.label {
width: 180rpx;
}
.text {
font-size: 24rpx;
}
}
</style>
├─common 公用逻辑
├─config 配置
│ └─permission.js 调用接口所需的权限配置
├─dao 数据库相关API
├─lang 国际化目录
├─lib 基础功能,不建议修改此目录下文件
│ ├─alipay.js 支付宝平台相关API
│ ├─common.js 一些通用API
│ ├─qrcode.js 云端生成二维码的插件(来自于npm i qrcode的压缩版)
│ └─wxpay.js 微信支付平台相关API
├─middleware 中间件
├─notify 异步通知逻辑(你自己的异步通知逻辑写在这里)
├─service 分模块存放的云对象方法的服务实现
└─index.obj.js 云对象入口文件
uni-pay-co
所有api返回值均满足uniCloud响应体规范
返回值示例
{
errCode: 0, // 错误码,详见错误码列表
errMsg: '', // 错误信息,uni-pay-co会自动根据客户端语言对错误信息进行国际化
// ...其余参数
}
uni-pay前端组件和uni-pay-co云对象的方法是一样的。通常情况下,前端直接调用uni-pay组件内的方法即可(组件内会自动调用云对象内的API,无需再手动调用云对象内的API)
以下是介绍这些api。
API | 说明 |
---|---|
uniPayCo.createOrder | 创建支付 查看详情 |
uniPayCo.getOrder | 查询订单 查看详情 |
uniPayCo.refund | 发起退款(此接口需要权限才可以访问) 查看详情 |
uniPayCo.getRefund | 查询退款 查看详情 |
uniPayCo.closeOrder | 关闭订单 查看详情 |
uniPayCo.getPayProviderFromCloud | 获取支持的支付供应商 查看详情 |
uniPayCo.getProviderAppId | 获取支付配置内的appid(主要用于获取微信公众号的appid,用以获取code) 查看详情 |
uniPayCo.getOpenid | 根据code获取openid (主要用于微信公众号code换取openid) 查看详情 |
支付组件方法形式(收银台弹窗模式)(推荐)
open
和createOrder
参数是一致的,唯一区别是open
会打开收银台,而createOrder
不带收银台,直接调用支付。
open
如果只有一种支付方式,比如微信小程序内只能用微信支付,则不会弹收银台,而是直接调用支付。
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
payInstance.open({
total_fee: 1, // 支付金额,单位分 100 = 1元
type: "recharge", // 支付回调类型
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
description: "uniCloud个人版包月套餐", // 支付描述
});
直接跳收银台页面模式(推荐)
与弹窗模式的区别是:跳页面模式是通过 uni.navigateTo
直接跳到收银台页面,而弹窗模式是在原页面弹出收银台。
let options = {
total_fee: 1, // 支付金额,单位分 100 = 1元
type: "recharge", // 支付回调类型
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
description: "uniCloud个人版包月套餐", // 支付描述
} as UTSJSONObject;
let optionsStr = encodeURI(JSON.stringify(options));
uni.navigateTo({
url:`/uni_modules/uni-pay-x/pages/pay-desk/pay-desk?options=${optionsStr}`
});
收银台页面源码在 /uni_modules/uni-pay-x/pages/pay-desk/pay-desk
中
如果你想要自定义收银台样式,建议复制该页面到你的项目pages目录,如/pages/pay-desk/pay-desk
,然后在复制的页面上进行修改样式,同时跳转到自定义收银台的代码如下:
let options = {
total_fee: 1, // 支付金额,单位分 100 = 1元
type: "recharge", // 支付回调类型
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
description: "uniCloud个人版包月套餐", // 支付描述
} as UTSJSONObject;
let optionsStr = encodeURI(JSON.stringify(options));
uni.navigateTo({
url:`/pages/pay-desk/pay-desk?options=${optionsStr}`
});
支付组件方法形式(不带收银台)
不带收银台时,provider参数为必传项,代表支付供应商
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
payInstance.createOrder({
provider: "wxpay", // 支付供应商
total_fee: 1, // 支付金额,单位分 100 = 1元
type: "recharge", // 支付回调类型
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
description: "uniCloud个人版包月套餐", // 支付描述
});
云对象接口形式
await uniPayCo.createOrder({
provider: "wxpay", // 支付供应商
total_fee: 1, // 支付金额,单位分 100 = 1元
type: "recharge", // 支付回调类型
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
description: "uniCloud个人版包月套餐", // 支付描述
});
参数说明
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
provider | string | 是 | 支付供应商 如 wxpay alipay |
total_fee | int | 是 | 订单总金额,单位为分,100等于1元(注意:因为是前端传的,此参数可能会被伪造,回调时需要再校验下是否和自己业务订单金额一致) |
type | string | 是 | 订单类型 goods:订单付款 recharge:余额充值付款 vip:vip充值付款 等等,可自定义,主要用于判断走哪个回调逻辑(如商品付款和余额充值的回调逻辑肯定是不一样的) |
order_no | string | 是 | 业务系统订单号 建议控制在20-28位(不可以是24位,24位在阿里云空间可能会有问题)(可重复,代表1个业务订单会有多次付款的情况) |
out_trade_no | string | 否 | 支付插件订单号(需控制唯一,不传则由插件自动生成) |
description | string | 否 | 支付描述,如:uniCloud个人版包月套餐 |
qr_code | boolean | 否 | 若设置为 true 则强制开启二维码支付模式 |
openid | string | 否 | 发起支付的用户openid(微信公众号支付必填,小程序支付等插件会自动获取,无需填写 |
custom | object | 否 | 自定义参数(不会发送给第三方支付服务器)此参数不推荐使用,因为是前端传的,此参数可能会被伪造,建议通过order_no查询自己业务订单表来获取自定义业务数据 |
other | object | 否 | 其他请求参数(会发送给第三方支付服务器) |
返回值
参数名 | 类型 | 说明 |
---|---|---|
order | object | 用于发起支付的订单信息 |
order_no | string | 本次交易的订单号,等于你一开始传的order_no的值 |
out_trade_no | string | 本次交易的支付插件订单号 |
provider | string | 本次交易的支付供应商 |
provider_pay_type | string | 本次交易的支付供应商的支付类型 |
qr_code | boolean | 本次交易的是否是扫码支付模式 |
qr_code_image | string | 如果是扫码支付,会返回此字段,代表二维码的base64值 |
特别注意(一定要看)
在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no
,再把 order_no
当参数传给此api。
整个逻辑是这样的:
以用户购买商品付款为例
order_no
给前端(非本插件功能)order_no
调用插件的创建支付API(type参数的值写 goods
),发起真正的支付功能(本插件功能)goods
回调),同时标记订单为已付款(本插件功能)以用户充值余额为例
order_no
给前端(非本插件功能)order_no
调用插件的创建支付API(type参数的值写 recharge
),发起真正的支付功能(本插件功能)recharge
回调),同时标记订单为已付款(本插件功能)支付组件方法形式(推荐)
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
await payInstance.getOrder({
out_trade_no: "2022102701100010100101001", // 插件支付单号
await_notify: true, // 是否需要等待异步通知执行完成,若为了响应速度,可以设置为false,若需要等待异步回调执行完成,则设置为true
});
云对象接口形式
await uniPayCo.getOrder({
out_trade_no: "2022102701100010100101001", // 插件支付单号
await_notify: true, // 是否需要等待异步通知执行完成,若为了响应速度,可以设置为false,若需要等待异步回调执行完成,则设置为true
});
参数说明
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
out_trade_no | string | out_trade_no、transaction_id 二选一 | 插件订单号 |
transaction_id | string | out_trade_no、transaction_id 二选一 | 第三方支付交易单号 |
await_notify | boolean | 否 | 默认为false,是否需要等待异步通知执行完成,若为了响应速度,可以设置为false,若需要等待异步回调执行完成,则设置为true |
await_notify = true 适合什么场景?
当你下一个页面展示的数据需要依赖支付异步回调内的逻辑执行完成后才可以展示时,需要设置为true。
await_notify = false 适合什么场景?
当你下一个页面展示的数据不需要依赖支付异步回调内的逻辑执行完成后才可以展示时,可以设置为false,设置为false可以加快响应速度。
返回值
参数名 | 类型 | 说明 |
---|---|---|
has_paid | boolean | 标记用户是否已付款成功(此参数只能表示用户确实付款了,但系统的异步回调逻辑可能还未执行完成) |
user_order_success | boolean | 用户异步通知逻辑是否全部执行完成,且无异常(建议前端通过此参数是否为true来判断是否支付成功) |
out_trade_no | string | 支付插件订单号 |
transaction_id | string | 第三方支付交易单号(只有付款成功的才会返回) |
status | int | 当前支付订单状态 -1:已关闭 0:未支付 1:已支付 2:已部分退款 3:已全额退款 |
pay_order | object | 支付订单完整信息 |
注意
发起退款默认需要admin权限(基于uni-id用户体系登录),否则会报权限不足或缺少token。查看uni-id介绍
当然,你也可以修改uni-pay-co/config/permission.js
这个文件内的权限规则。
支付组件方法形式(推荐)
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
await payInstance.refund({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
云对象接口形式
await uniPayCo.refund({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
参数说明
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
out_trade_no | string | out_trade_no、out_refund_no 二选一 | 插件订单号 |
out_refund_no | string | out_trade_no、out_refund_no 二选一 | 插件退款订单号 |
refund_desc | string | 否 | 退款描述 |
refund_fee | int | 否 | 退款金,单位分 100 = 1元 |
返回值
参数名 | 类型 | 说明 |
---|---|---|
result | object | 第三方供应商返回的结果 |
支付组件方法形式(推荐)
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
await payInstance.getRefund({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
云对象接口形式
await uniPayCo.getRefund({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
参数说明
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
out_trade_no | string | 是 | 插件订单号 |
参数说明
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
out_trade_no | string | out_trade_no、out_refund_no 二选一 | 插件订单号 |
out_refund_no | string | out_trade_no、out_refund_no 二选一 | 插件退款订单号 |
refund_desc | string | 否 | 退款描述 |
refund_fee | int | 否 | 退款金,单位分 100 = 1元 |
返回值
参数名 | 类型 | 说明 |
---|---|---|
result | object | 第三方供应商返回的结果 |
pay_order | object | 支付订单信息 |
一般情况下,无需调用此方法去主动关闭订单(订单若未支付,则会在一段时间后自动关闭),但你有需要主动关闭订单的场景时,可以使用此api来主动关闭订单。(只有未支付的订单才可以主动关闭)
注意:
支付组件方法形式(推荐)
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
await payInstance.closeOrder({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
云对象接口形式
await uniPayCo.closeOrder({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
参数说明
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
out_trade_no | string | 是 | 插件订单号 |
返回值
参数名 | 类型 | 说明 |
---|---|---|
result | object | 第三方供应商返回的结果 |
一般情况下,无需调用此api,uni-pay
组件内部已自动调用此api。
支付组件方法形式(推荐)
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
await payInstance.getPayProviderFromCloud();
云对象接口形式
await uniPayCo.getPayProviderFromCloud();
参数说明
该API无参数
返回值
参数名 | 类型 | 说明 |
---|---|---|
wxpay | boolean | 是否支持微信支付 |
alipay | boolean | 是否支持支付宝支付 |
provider | array<string> | 支持哪些支付供应商,如["wxpay","alipay"] |
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
await payInstance.getProviderAppId({
provider: "wxpay",
provider_pay_type: "jsapi",
});
云对象接口形式
await uniPayCo.getProviderAppId({
provider: "wxpay",
provider_pay_type: "jsapi",
});
参数说明
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
provider | string | 是 | 支付供应商 如 wxpay alipay |
provider_pay_type | string | 是 | 支付供应商 如 jsapi |
返回值
参数名 | 类型 | 说明 |
---|---|---|
appid | string | appid |
一般用于微信公众号根据网页授权回调返回的code获取用户openid
注意
小程序不需要调用此方法,组件内部已自动静默获取openid
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
await payInstance.getOpenid({
provider: "wxpay",
code: options.code
});
云对象接口形式
await uniPayCo.getOpenid({
provider: "wxpay",
code: options.code
});
参数说明
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
provider | string | 是 | 支付供应商 如 wxpay alipay |
code | string | 是 | 微信公众号网页授权回调返回的code |
返回值
参数名 | 类型 | 说明 |
---|---|---|
openid | string | openid |
HBuilderX 版本需≥4.26
概述
苹果虚拟支付:In-App Purchase,是指苹果 App Store 的应用内购买,是苹果为 App 内购买虚拟商品或服务提供的一套交易系统。
适用范围:在 App 内需要付费使用的产品功能或虚拟商品/服务,如游戏道具、电子书、音乐、视频、订阅会员、App的高级功能等需要使用 IAP,而在 App 内购买实体商品(如淘宝购买手机)或者不在 App 内使用的虚拟商品(如充话费)或服务(如滴滴叫车)则不适用于 IAP。
简而言之,苹果规定:适用范围内的虚拟商品或服务,必须使用 IAP 进行购买支付,不允许使用支付宝、微信支付等其它第三方支付方式(包括Apple Pay),也不允许以任何方式(包括跳出App、提示文案等)引导用户通过应用外部渠道购买。
示例代码
注意:只能使用uni-pay支付组件发起
// 发起苹果虚拟支付
let buy_quantity = 1; // 购买数量
let goods_price = 1; // 单价(此参数的单位是元)
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
payInstance.createOrder({
provider: "appleiap", // 支付供应商(这里固定未appleiap,代表苹果虚拟支付)
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
type: "appleiap", // 支付回调类型(可自定义,建议填写appleiap)
description: "为DCloud提供的免费软件进行赞助", // 订单描述
total_fee: parseInt((goods_price * 100 * buy_quantity).toFixed(0)), // 插件是以分为单位,故这里需要乘以100
// apple_virtual字段仅苹果虚拟支付生效
apple_virtual: {
product_id: "uniappx.consumable.sponsor_1", // 产品id
goods_price: goods_price, // 单价(此参数的单位是元)
buy_quantity: buy_quantity, // 购买数量
},
custom: {}, // 自定义数据(此参数不推荐使用,因为是前端传的,此参数可能会被伪造,建议通过order_no查询自己业务订单表来获取自定义业务数据)
});
完整苹果虚拟支付示例代码
<template>
<view class="content">
<view class="uni-list">
<radio-group @change="applePriceChange">
<view class="uni-list-cell" v-for="(item, index) in productList" :key="index">
<radio :value="item['product_id']" :checked="product_id == item['product_id']"/>
<view class="price" @click="applePriceClick(item)">{{item['title']}} {{item['goods_price']}}元</view>
</view>
</radio-group>
</view>
<view class="uni-padding-wrap">
<button class="button btn-pay" @click="createOrder" :loading="loading" :disabled="disabled">立即支付</button>
</view>
<!-- 统一支付组件 -->
<uni-pay ref="payRef" :debug="true" :adpid="adpid" return-url="/pages/order-detail/order-detail" @mounted="onMounted" @success="onSuccess" @fail="onFail" @cancel="onCancel"></uni-pay>
</view>
</template>
<script>
export default {
data() {
return {
order_no: "", // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: "", // 插件支付单号
adpid: "1000000001", // uni-ad的广告位id
loading: false, // 支付按钮是否在loading中
disabled: true, // 支付按钮是否禁用
product_id: "", // 用户选择的商品id
// 出售的苹果虚拟商品列表
productList: [
{
"description": "为DCloud提供的免费软件进行赞助",
"goods_price": 1, // 单价(元)
"buy_quantity": 1, // 数量(消耗性类型: 数量默认是1,最大值是10)
"product_id": "uniappx.consumable.sponsor_1",
"title": "消耗性产品:赞助"
},
{
"description": "为DCloud提供的免费软件进行赞助",
"goods_price": 5, // 单价(元)
"buy_quantity": 1, // 数量(消耗性类型: 数量默认是1,最大值是10)
"product_id": "uniappx.consumable.sponsor_50",
"title": "消耗性产品:赞助"
},
{
"description": "为DCloud提供的免费软件进行赞助",
"goods_price": 1, // 单价(元)
"buy_quantity": 1, // 数量(非消耗性: 数量只能是1,且一个该类型产品一个appleId只能购买一次)
"product_id": "uniappx.nonconsumable.sponsorskin_1",
"title": "非消耗性产品: 赞助"
},
{
"description": "为DCloud提供的免费软件进行赞助",
"goods_price": 1, // 单价(元)
"buy_quantity": 1, // 数量(自动续期订阅产品: 数量只能是1)
"product_id": "uniappx.autorenewable.monthly_1",
"title": "自动续期订阅产品:每月定期赞助", // 注意自动续期订阅产品在沙盒模式下,实际周期会缩短到几分钟续期一次(即现实世界几分钟 = 沙盒世界1个月)
},
{
"description": "为DCloud提供的免费软件进行赞助",
"goods_price": 1, // 单价(元)
"buy_quantity": 1, // 数量(非自动续期订阅产品: 数量只能是1)
"product_id": "uniappx.nonrenewable.monthly_1",
"title": "非自动续期订阅产品:月赞助",
},
{
"description": "为DCloud提供的免费软件进行赞助",
"goods_price": 1, // 单价(元)
"buy_quantity": 1, // 数量
"product_id": "uniappx.nonrenewable.none",
"title": "测试不存在的产品"
}
] as Array<UTSJSONObject>,
}
},
onLoad: function() {
},
onShow() {
},
onUnload() {},
methods: {
// 支付组件加载完毕后执行
onMounted(insideData: any){
this.init();
},
// 初始化
init() {
this.product_id = this.productList[0]["product_id"] as string;
this.disabled = false;
let payRef = this.$refs['payRef'] as UniPayComponentPublicInstance;
// 苹果虚拟支付未完成订单检测
payRef.appleiapRestore();
},
/**
* 发起支付
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
createOrder(){
this.order_no = `test`+Date.now();
this.out_trade_no = this.order_no;
let productInfo: UTSJSONObject = this.productList.find((item: UTSJSONObject) : boolean => {
return item['product_id'] == this.product_id;
});
let buy_quantity = productInfo.getNumber('buy_quantity') || 1;
let goods_price = productInfo.getNumber('goods_price');
// 发起支付
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
payInstance.createOrder({
provider: "appleiap", // 支付供应商(这里固定为appleiap,代表苹果虚拟支付)
order_no: this.order_no, // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: this.out_trade_no, // 插件支付单号
type: "appleiap", // 支付回调类型(可自定义,建议填写appleiap)
description: productInfo.description,
total_fee: parseInt((goods_price * 100 * buy_quantity).toFixed(0)), // 插件是以分为单位,故这里需要乘以100
// apple_virtual字段仅苹果虚拟支付生效
apple_virtual: {
product_id: this.product_id, // 产品id
goods_price: goods_price, // 单价
buy_quantity: buy_quantity, // 购买数量
},
// 自定义数据
custom: {}
});
},
// 监听事件 - 支付成功
onSuccess(res){
console.log('success: ', res);
if (res.user_order_success) {
// 代表用户已付款,且你自己写的回调成功并正确执行了
} else {
// 代表用户已付款,但你自己写的回调执行失败(通常是因为你的回调代码有问题)
}
},
onFail(err){
uni.showModal({
content: `${err.errSubject} : ${err.errCode} : ${err.errMsg}`,
showCancel: false,
title: `发起支付失败`,
});
},
onCancel(err){
uni.showToast({
title: "用户取消了支付",
icon: 'none'
});
},
// 监听-多选框选中的值改变
applePriceChange(e) {
this.product_id = e.detail.value;
},
applePriceClick(item: any){
this.product_id = item['product_id'] as string;
}
}
}
</script>
<style>
.content {
padding: 15px;
}
.button {
background-color: #007aff;
color: #ffffff;
}
.uni-list-cell {
display: flex;
flex-direction: row;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
.price {
margin-left: 10px;
}
.btn-pay {
margin-top: 30px;
}
</style>
uni-pay
基于uni统计2.0
新增了支付统计。为您赋能数字化运营。
uni-admin 2.2.0
即以上版本已内置支付统计,菜单位置为uni统计 / 支付统计
如果你当前使用的是旧版uni-admin
,则需要先更新到新版uni-admin
(右键admin项目根目录package.json
,从插件市场更新,注意合并时的文件对比,如果不对比直接合并会覆盖你之前写的代码)
同时新建一个空的json文件,复制下面的内容到新建的json文件中,最后去uniCloud控制台
的opendb-admin-menus
表手动导入json文件
{"menu_id": "uni-stat-pay","name": "支付统计","icon": "uni-icons-circle","url": "","sort": 2122,"parent_id": "uni-stat","permission": [],"enable": true,"create_date": 1667386977981}
{"menu_id": "uni-stat-pay-overview","name": "概况","icon": "","url": "/pages/uni-stat/pay-order/overview/overview","sort": 21221,"parent_id": "uni-stat-pay","permission": [],"enable": true,"create_date": 1667387038602}
{"menu_id": "uni-stat-pay-funnel","name": "漏斗分析","icon": "","url": "/pages/uni-stat/pay-order/funnel/funnel","sort": 21222,"parent_id": "uni-stat-pay","permission": [],"enable": true,"create_date": 1668430092890}
{"menu_id": "uni-stat-pay-ranking","name": "价值用户排行","icon": "","url": "/pages/uni-stat/pay-order/ranking/ranking","sort": 21223,"parent_id": "uni-stat-pay","permission": [],"enable": true,"create_date": 1668430256302}
概况
在概况
栏目中可以直观的看到今日、昨日、前日、本周、本月、本季度、本年度、累计数据。
名词解释:
今日数据
在今日数据
栏目中可以看到更多今日统计数据。
名词解释:
趋势图
在趋势图
栏目中以天维度
、月维度
、季维度
、年维度
进行趋势统计。可以直观的看到收入的增长趋势。
可以为您分析指定时间段的支付转化率,同时展示支付转化率趋势图。
名词解释:
可以为您快速筛选高价值用户,高复购率用户。
可以搜索、查看订单详情
h5的路由模式必须配置为 history
,因为微信公众号登录的回调地址不支持 hash
模式。
同时微信公众号开发调试比较麻烦,麻烦在于网页授权需要添加域名白名单,用localhost或用ip访问本地是无法获取到微信的code的,这样也就无法获取openid,导致无法支付。
操作步骤
当用自定义域名时,还需要在项目根目录添加 vue.config.js
文件,内容如下:
module.exports = {
devServer: {
disableHostCheck: true, // 忽略域名检查
port: 80, // 设置80端口为项目启动端口
}
}
APP支付除了配置uni-pay的支付配置外,还需要打包时添加支付模块,如下图所示。
同时,还需要打自定义基座(包名需要和开放平台下填写的一致),且你在开放平台下的这个应用必须通过审核才可以。(比如微信开放平台下的APP应用显示通过审核才可以)
错误模块 | 错误码 | 说明 |
---|---|---|
uni-pay | 50403 | 当前登录用户的角色权限不足 |
uni-pay | 51001 | 支付单号(out_trade_no)不能为空 |
uni-pay | 51002 | code不能为空 |
uni-pay | 51003 | 订单号(order_no)不能为空 |
uni-pay | 51004 | 回调类型(type)不能为空,如设置为goods代表商品订单 |
uni-pay | 51005 | 支付金额(total_fee)必须为正整数(>0的整数)(注意:100=1元) |
uni-pay | 51006 | 支付描述(description)不能为空 |
uni-pay | 51007 | 支付供应商(provider)不能为空 |
uni-pay | 51008 | 未获取到 clientInfo |
uni-pay | 51009 | 未获取到 cloudInfo |
uni-pay | 52001 | 查询的支付订单不存在 |
uni-pay | 52002 | 未配置正确的异步回调URL |
uni-pay | 53001 | 获取支付信息失败(具体信息以控制台打印的日志为准) |
uni-pay | 53002 | 退款失败(具体信息以控制台打印的日志为准) |
uni-pay | 53003 | 查询退款信息失败(具体信息以控制台打印的日志为准) |
uni-pay | 53004 | 关闭订单失败(具体信息以控制台打印的日志为准) |
uni-pay | 53005 | 证书错误,请检查支付证书 |
返回值示例
{
"errMsg": "支付单号(out_trade_no)不能为空",
"errCode": 51001,
"errSubject": "uni-pay"
}
支付插件需要创建支付相关的表后才能正常运行。查看相关的数据库表
本插件对接的支付渠道是微信和支付宝官方渠道
微信支付
申请地址 https://pay.weixin.qq.com/index.php/apply/applyment_home/guide_normal
申请指引 https://pay.weixin.qq.com/static/applyment_guide/applyment_index.shtml
支付宝
申请指引 https://opendocs.alipay.com/common/02asmu
注意
支付账号申请需要企业资质(个体工商户也可以,但不可以是个人资质,需要有营业执照,银行对公账户)。
.p12
的文件,如果你的.p12
文件不是apiclient_cert.p12
,则将它改名成apiclient_cert.p12
,并复制到 uni-config-center/uni-pay/wxpay/
目录下apiclient_cert.pem
的文件,将它复制到 uni-config-center/uni-pay/wxpay/
目录下apiclient_key.pem
的文件,将它复制到 uni-config-center/uni-pay/wxpay/
目录下appCertPublicKey.crt
的文件,将它复制到 uni-config-center/uni-pay/alipay/
目录下alipayCertPublicKey_RSA2.crt
的文件,将它复制到 uni-config-center/uni-pay/alipay/
目录下alipayRootCert.crt
的文件,将它复制到 uni-config-center/uni-pay/alipay/
目录下这是由于云开发的域名没有添加到微信小程序域名白名单导致的,需要去微信小程序后台,添加以下域名到微信小程序域名白名单
https://api.next.bspapp.com;https://api.bspapp.com;https://tcb-api.tencentcloudapi.com;
添加完域名后,一定要重启微信开发者工具,然后去手机微信里删除最近使用的小程序(这一步很关键),最后重新扫二维码进入小程序。
有两个方案可以解决
方案一:使用微信支付v3版本
方案二:将云函数的node版本切换成node16(支付宝云默认是node18,而node18不再支持微信支付v2证书pfx的加密算法导致的)