提交 956f7aa2 编写于 作者: VK1688's avatar VK1688

docs: 新增微信小程序虚拟支付文档、uni-pay-x文档

上级 85b8c384
...@@ -103,8 +103,10 @@ ...@@ -103,8 +103,10 @@
* [uni-admin](admin.md) * [uni-admin](admin.md)
* [uni-upgrade-center App升级中心](upgrade-center.md) * [uni-upgrade-center App升级中心](upgrade-center.md)
* uni-pay 统一支付```{"collapsable": true}``` * uni-pay 统一支付```{"collapsable": true}```
* * [uni-pay 云端一体页面模板](uni-pay.md) * * [uni-pay 云端一体页面模板 uni-app](uni-pay/uni-app.md)
* * [uni-pay 公共模块](unipay.md) * * [uni-pay 云端一体页面模板 uni-app-x](uni-pay/uni-app-x.md)
* * [uni-pay 公共模块](uni-pay/uni-pay-common.md)
* * [uni-pay 微信小程序虚拟支付](uni-pay/wxpay-virtual.md)
* [uni-cms 内容管理](uni-cms.md) * [uni-cms 内容管理](uni-cms.md)
* [uni-ai-chat ai聊天示例](uni-ai-chat.md) * [uni-ai-chat ai聊天示例](uni-ai-chat.md)
* [uni-im 即时通信](uni-im.md) * [uni-im 即时通信](uni-im.md)
......
# uni-pay 2
> 本文档适用于`uni-pay 2.0.0`及以上版本,需 HBuilderX 3.6.5 及以上版本。旧版本文档请访问:[uni-pay 1.x 文档](unipay.md) 文件已搬家,[前往新文档](./uni-pay/uni-app.md)
## 简介@introduction
支付,重要的变现手段,但开发复杂。在不同端,对接微信支付、支付宝等渠道,前端后端都要写不少代码。
涉及金额可不是小事,生成业务订单、获取收银台、发起支付、支付状态查询、支付异步回调、失败处理、发起退款、退款状态查询、支付统计...众多环节,代码量多,出错率高。
为什么不能有一个开源的、高质量的项目?即可以避免大家重复开发,又可以安心使用,不担心自己从头写产生Bug。
`uni-pay`应需而生。
之前`uni-pay 1.x`版本,仅是一个公共模块,它让开发者无需研究支付宝、微信等支付平台的后端开发、无需为它们编写不同代码,拿来即用,屏蔽差异。
但开发者还是需要自己编写前端页面和云函数,还是有一定的开发难度和工作量的,特别对于新手来说,门槛高、易出错。
`uni-pay 2.0` 起,补充了前端页面和云对象,让开发者开箱即用。
**注意:`uni-pay 2` 仍内置了uni-pay公共模块,向下兼容`uni-pay 1.x`,即从`uni-pay 1.x`可以一键升级到`uni-pay 2.x`,且不会对你的老项目造成影响。**
开发者在项目中引入 `uni-pay` 后,微信支付、支付宝支付等功能无需自己再开发。由于源码的开放性和层次结构清晰,有二次开发需求也很方便调整。
> 插件市场地址:[https://ext.dcloud.net.cn/plugin?name=uni-pay](https://ext.dcloud.net.cn/plugin?name=uni-pay)
> 代码仓库地址:[https://gitcode.net/dcloud/uni-pay.git](https://gitcode.net/dcloud/uni-pay.git)
**线上体验地址**
注意:线上体验地址用的是阿里云免费版,免费版请求次数有限,如请求失败为正常现象,可直接导入示例项目绑定自己的空间体验。
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/https___hellounipay.dcloud.net.cn_.png)
`uni-pay` 的功能包括:
- 页面
+ 支付收银台组件(让用户选择付款渠道) [组件详情](#uni-pay-component)
+ 支付成功结果页(可配置uni-ad广告,增加开发者收益)[uni-AD 广告联盟](https://uniad.dcloud.net.cn/login)
- 云对象([uni-pay-co](#uni-pay-co)
+ 微信支付
+ 微信APP支付
+ 微信小程序支付
+ 微信公众号支付
+ 微信手机外部浏览器H5支付
+ 微信PC扫码支付
+ 支付宝支付
+ 支付宝APP支付
+ 支付宝小程序支付
+ 支付宝手机外部浏览器H5支付(支持在微信APP的H5页面中使用支付宝支付)
+ 支付宝PC扫码支付
+ 通用接口
+ 支付异步回调
+ 查询订单
+ 发起退款
+ 查询退款
+ 关闭订单
+ 获取当前支持的支付方式
+ 获取当前支付用户的openid
+ ios内购支付
- 支付统计(内置于uni-admin的支付统计中)
+ 收款趋势
+ 转换漏斗分析
+ 价值用户排行
+ 订单明细
## uni-pay组成@catalogue
uni-pay云端一体模板,包含前端页面、云对象、云端公共模块、uni-config-center配置、opendb数据表等内容。以及内置于uni-admin的支付统计报表。
### uni-pay的uni_modules
uni-pay的[uni_modules](https://uniapp.dcloud.net.cn/plugin/uni_modules.html)中包含了前端页面、云对象和公共模块,目录结构如下:
```
├─uni_modules 存放[uni_module](https://uniapp.dcloud.net.cn/plugin/uni_modules.html)规范的插件。
│ ├─其他module
│ └─uni-pay
│ ├─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 云对象方法的服务实现
│ ├─components 组件目录
│ │ └─uni-pay uni-pay收银台弹窗组件
│ │ └─uni-pay.vue
│ ├─js_sdk js sdk目录
│ │ └─js_sdk.js
│ ├─pages 页面目录
│ │ └─success
│ │ └─success.js 支付成功结果页
│ ├─static 静态资源目录
│ ├─changelog.md 更新日志
│ ├─package.json 包管理文件
│ └─readme.md 插件自述文件
```
完整的uni-app项目目录结构[另见](https://uniapp.dcloud.net.cn/frame?id=%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84)
### uni-pay的uni-config-center配置
支付配置不在插件目录中,统一存放在 `uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js` [查看支付配置介绍](#config)
### uni-pay的opendb数据表@database
支付插件需要创建以下表后才能正常运行,可以右键 `database` 目录,初始化数据库功能来创建表。
- 支付订单表 [uni-pay-orders](https://gitee.com/dcloud/opendb/blob/master/collection/uni-pay-orders/collection.json)
## 示例项目运行教程@rundemo
在对接自己的项目之前,建议先跑通示例项目,能跑通示例项目,代表你的配置和证书一定是正确的,然后再将`uni-pay`集成到你自己的项目中。
1. 从插件市场导入`uni-pay`示例项目。[前往插件市场](https://ext.dcloud.net.cn/plugin?name=uni-pay)
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-9.png)
2. 打开`uni-pay`配置文件,配置文件地址: `uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js` [查看支付配置介绍](#config)
3. 上传公共模块 `uni-config-center`(右键,上传公共模块,每次修改了支付配置,都需要重新上传此模块才会生效)
4. 上传公共模块 `uni-pay`(右键,上传公共模块)
5. 上传云对象 `uni-pay-co`(右键,上传部署。当然对uniCloud目录点右键批量上传也可以)
6. 数据库初始化
![](https://web-ext-storage.dcloud.net.cn/unicloud/uni-pay/462.png)
![](https://web-ext-storage.dcloud.net.cn/unicloud/uni-pay/463.png)
7. 运行启动项目,**在HBuilderX的运行控制台里选择使用云端云函数环境**
**注意:测试支付回调必须选择云端云函数环境**
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-29.png)
8. 前端页面里点击唤起收银台支付,如果可以正常支付,代表示例项目运行成功,可以开始对接自己的项目了。 [对接自己项目](#install)
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-30.png)
## uni-pay的config-center配置@config
开发者在微信和支付宝的支付后台,需要申请开通支付服务,成功后会得到各种凭据,这些凭据要配置在uni-pay的配置中。
配置文件在 `uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js`
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-3.png)
### 完整支付配置示例@config-demo
这里是微信、支付宝全平台支付配置样例。如果只使用部分支付方式,后续有专门的分渠道章节。
```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": "", // 支付宝商户私钥
"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": "", // 支付宝商户私钥
"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": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
}
},
// ios内购相关
"appleiap" :{
// ios内购支付
"app": {
"password": "", // App 专用共享密钥,App 专用共享密钥是用于接收此 App 自动续期订阅收据的唯一代码。如果您要将此 App 转让给其他开发者或不想公开主共享密钥,建议使用 App 专用共享密钥。非自动续订场景不需要此参数
"timeout": 10000, // 请求超时时间,单位:毫秒
"sandbox": false, // 是否是沙箱环境(本地调试ios走的是沙箱环境,故要设置为true,正式发布后,需要设置为false)
}
}
}
```
如果你对支付配置中各参数如何获取有疑问,请点击[获取支付配置帮助](#get-config-help)
**注意**
微信支付同时支持V2版本和V3版本
以微信小程序支付为例
**V2版本**
```js
"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版本**
```js
"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
```js
"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版本
},
```
### 支付回调配置@config-notify
对应支付配置的节点是 `notifyUrl`
**示例**
```js
// 统一 - 支付回调地址,格式为 "服务空间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如何获取?**
[点击此处进入服务空间列表](https://unicloud.dcloud.net.cn/home),找到你项目用的服务空间,复制其服务空间ID
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-4.png)
**URL化地址如何获取?**
[点击此处进入服务空间列表](https://unicloud.dcloud.net.cn/home),找到你项目用的服务空间,点击服务空间名称进入空间详情页,点击左侧菜单【云函数/云对象】- 点击【uni-pay-co】云对象右侧的【详情】按钮
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-5.png"/>
</div>
进入详情后,点下面的【复制路径】,复制的内容就是【URL化地址】
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-6.png"/>
</div>
### 分渠道支付配置示例@config-part
上面的配置样例是微信和支付宝全端配置样例。如果只使用一种支付场景,比如微信公众号里的微信支付,可以看下面章节的分渠道支付配置样例。
#### 微信APP支付@config-wxpay-app
对应支付配置的节点是 `wxpay.app`
```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",
},
// 微信支付相关
"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版本
},
},
}
```
#### 微信小程序支付@config-wxpay-mp
对应支付配置的节点是 `wxpay.mp`
```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",
},
// 微信支付相关
"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版本
},
},
}
```
#### 微信公众号支付@config-wxpay-jsapi
对应支付配置的节点是 `wxpay.jsapi`
```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",
},
// 微信支付相关
"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版本
},
},
}
```
#### 微信手机外部浏览器H5支付@config-wxpay-mweb
对应支付配置的节点是 `wxpay.mweb`
```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",
},
// 微信支付相关
"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网站名称
}
}
},
},
}
```
#### 微信PC扫码支付@config-wxpay-native
对应支付配置的节点是 `wxpay.native`
```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",
},
// 微信支付相关
"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版本
},
},
}
```
#### 支付宝APP支付@config-alipay-app
对应支付配置的节点是 `alipay.app`
```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",
},
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - APP支付配置
"app": {
"appId": "", // 支付宝开放平台下应用的appid
"privateKey": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
},
}
}
```
#### 支付宝小程序支付@config-alipay-mp
对应支付配置的节点是 `alipay.mp`
```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",
},
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - 小程序支付配置
"mp": {
"appId": "", // 支付宝小程序appid
"privateKey": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
},
}
}
```
#### 支付宝手机外部浏览器H5支付@config-alipay-native-h5
对应支付配置的节点是 `alipay.native`(和PC扫码配置节点一样)
```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",
},
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - H5支付配置(包含:网站二维码、手机H5,需申请支付宝当面付接口权限)
"native": {
"appId": "", // 支付宝开放平台下应用的appid
"privateKey": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
}
}
}
```
#### 支付宝PC扫码支付@config-alipay-native-pc
对应支付配置的节点是 `alipay.native`
```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",
},
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - H5支付配置(包含:网站二维码、手机H5,需申请支付宝当面付接口权限)
"native": {
"appId": "", // 支付宝开放平台下应用的appid
"privateKey": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
}
}
}
```
#### ios内购支付@config-appleiap-app
对应支付配置的节点是 `appleiap.app`
```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",
},
// ios内购相关
"appleiap" :{
// ios内购支付
"app": {
"password": "", // App 专用共享密钥,App 专用共享密钥是用于接收此 App 自动续期订阅收据的唯一代码。如果您要将此 App 转让给其他开发者或不想公开主共享密钥,建议使用 App 专用共享密钥。非自动续订场景不需要此参数
"timeout": 10000, // 请求超时时间,单位:毫秒
"sandbox": false, // 是否是沙箱环境(本地调试ios走的是沙箱环境,故要设置为true,正式发布后,需要设置为false)
}
}
}
```
## 集成到自己项目的教程@install
在对接自己的项目之前,建议先[跑通示例项目](#rundemo),能跑通示例项目,代表你的配置和证书一定是正确的,然后再将`uni-pay`集成到你自己的项目中。
### 安装插件
1. 从插件市场导入`uni-pay`插件到你自己的项目。[前往插件市场](https://ext.dcloud.net.cn/plugin?name=uni-pay)
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-10.png)
2. 复制你刚运行的示例项目中的`uni-pay`配置文件,配置文件地址: `uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js`到你的项目中 [查看支付配置介绍](#config)
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-3.png)
3. 上传公共模块 `uni-config-center`(右键,上传公共模块,每次修改了支付配置,都需要重新上传此模块才会生效)
4. 上传公共模块 `uni-pay`(右键,上传公共模块)
5. 上传云对象 `uni-pay-co`(右键,上传部署)
6. 数据库初始化
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-28.png)
7. 项目根目录`pages.json`添加`subPackages`分包页面配置(如果页面已自动配置,则可无视此步骤)
```js
"pages": [
...你的页面
],
"subPackages": [
{
"root": "uni_modules/uni-pay/pages",
"pages": [
{
"path": "success/success",
"style": {
"navigationBarTitleText": "支付成功",
"backgroundColor": "#F8F8F8"
}
},
{
"path": "ad-interactive-webview/ad-interactive-webview",
"style": {
"navigationBarTitleText": "ad",
"backgroundColor": "#F8F8F8"
}
}
]
}
],
```
8. 安装完成
### 前端页面集成@quickly-pages
打开你需要进行支付的页面,一般是业务订单提交之后的页面来展现收银台。
1. 该页面在 `template` 内放一个 `uni-pay` 组件标签,声明ref,然后调用组件的API。如下
注意:vue3下ref不可以等于组件名,因此这里 `ref="pay"` 而不能是 `ref="uniPay"`
```html
<template>
<view>
<button @click="open">唤起收银台支付</button>
<uni-pay ref="pay"></uni-pay>
</view>
</template>
```
2. 在script中编写代码,点击付款时执行方法:
```html
<script> <script>
export default { export default {
data() { data() {
return { 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
}
}
},
methods: {
/**
* 发起支付(唤起收银台,如果只有一种支付方式,则收银台不会弹出来,会直接使用此支付方式)
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
open() {
this.order_no = `test`+Date.now(); // 模拟生成订单号
this.out_trade_no = `${this.order_no}-1`; // 模拟生成插件支付单号
// 打开支付收银台
this.$refs.pay.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, // 自定义数据(此参数不推荐使用,因为是前端传的,此参数可能会被伪造,建议通过order_no查询自己业务订单表来获取自定义业务数据)
});
}
}
}
</script>
```
### 云端支付回调集成@notify
当用户支付成功后,我们要给用户增加余额或者给业务订单标记支付成功,这些通过异步回调通知来实现的。
**提示:异步回调通知写在 `uni-pay-co/notify` 目录下,在此目录新建2个js文件,分别为 `recharge.js`、`goods.js` 文件,同时复制以下代码要你新建的2个js文件里。**
代码如下
```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` 参数
```js
// 打开支付收银台
this.$refs.pay.open({
type: "recharge", // 支付回调类型 recharge 代表余额充值(当然你可以自己自定义)
});
```
**注意:每次修改都需要重新上传云对象`uni-pay-co`**
#### 业务在uniCloud上@service-inside
如果你的业务在uniCloud上,那么可以使用方式一或方式二进行编写自定义回调逻辑。
**方式一:直接写数据库操作**
适用场景:简单数据库操作场景
以给用户充值余额为例,代码如下
```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,
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;
};
```
**方式二:直接调用其他云函数或云对象**
适用场景:业务较为复杂,需写在其他云函数或云对象里的场景。
调用其他云函数示例代码如下
```js
'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解密)
}, },
}); created(){
// 解密示例 this.init();
// 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;
};
```
调用其他云对象示例代码如下
```js
'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上@service-outside
如果你的业务不在uniCloud上,如java或php写的后端服务,uni-pay也可以满足你的支付需求,你只需要使用回调方式三的http接口形式调用你自己系统的回调接口即可。
**方式三:使用 await uniCloud.httpclient.request 调用外部http接口**
适用场景:业务不在uniCloud上。
示例代码如下
```js
'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, // 传输加密数据(服务端你再自己解密)
}, },
}); mounted() {
/*
// 方式三安全模式二(只传一个订单号 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;
};
```
#### java解密示例代码
```java
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解密示例代码
```php
<?php
$key = '12345678901234561234567890123456'; // 必须是固定的32位(只支持数字、英文)
$encrypt = "es2aF7DWr169X4fvMnlKNg=="; // 待解密的内容
// 解密
$decrypt = openssl_decrypt(base64_decode($encrypt), 'aes-256-ecb', substr($key, 0, 32), OPENSSL_RAW_DATA);
echo $decrypt;
?>
```
### 运行启动
运行你的项目,进行支付的体验和测试。
## uni-pay组件介绍@uni-pay-component
#### 组件属性
| 属性名 | 说明 | 类型 | 默认值 | 可选值 |
|-----------------|-------------------------------|---------|--------|-------|
| adpid | uni-ad的广告位ID,若填写,则会在支付成功结果页展示广告(可以增加开发者广告收益) | string | - | - |
| returnUrl | 支付成功后,用户点击【查看订单】按钮时跳转的页面地址,如果不填写此属性,则没有【查看订单】按钮 | string | - | - |
| mainColor | 支付结果页主色调,默认支付宝小程序为#108ee9,其他端均为#01be6e | string | #01be6e | 见下 |
| mode | 收银台模式,插件会自动识别,也可手动传参,mobile 手机模式 pc 电脑模式 | string | 自动识别 | mobile、pc |
| logo | 当mode为PC时,展示的logo | string | /static/logo.png | - |
| height | 收银台高度 | string | 70vh | - |
**mainColor值参考:**
- 绿色系 #01be6e
- 蓝色系 #108ee9
- 咖啡色 #816a4e
- 粉红 #fe4070
- 橙黄 #ffac0c
- 橘黄 #ff7100
- 其他 可自定义
#### 组件事件
| 事件名 | 说明 | 参数 |
|-------------|---------------------|--------|
| success | 支付成功的回调 | res |
| cancel | 支付取消的回调 | res |
| fail | 支付失败的回调 | res |
| create | 创建支付订单时的回调(此时用户还未支付) | res |
#### 组件方法
通过 `let res = await this.$refs.pay.xxx();` 方式调用,详情调用方式参考下方的【前端完整示例代码】
| 方法名 | 说明 |
|---------------------------|---------------------|
| open | 发起支付 - 打开支付收银台弹窗 [查看详情](#create-order) |
| createOrder | 直接发起支付(无收银台) [查看详情](#create-order) |
| getOrder | 查询订单 [查看详情](#get-order) |
| refund | 发起退款(此接口需要权限才可以访问) [查看详情](#refund) |
| getRefund | 查询退款 [查看详情](#get-refund) |
| closeOrder | 关闭订单 [查看详情](#close-order) |
| getPayProviderFromCloud | 获取支持的支付供应商 [查看详情](#get-pay-provider-from-cloud) |
| getProviderAppId | 获取支付配置内的appid(主要用于获取微信公众号的appid,用以获取code) [查看详情](#get-provider-appid) |
| getOpenid | 根据code获取openid (主要用于微信公众号code换取openid) [查看详情](#get-openid) |
**前端完整示例代码**
```html
<template>
<view class="app">
<view>
<view class="label">支付单号:</view>
<view><input v-model="out_trade_no" /></view>
</view>
<view>
<view class="label">支付金额(单位分,100=1元):</view>
<view><input v-model.number="total_fee" /></view>
</view>
<button @click="open">唤起收银台支付</button>
<view class="tips">支付前,让用户自己选择微信还是支付宝</view>
<!-- #ifdef MP-WEIXIN || H5 || APP -->
<button @click="createOrder('wxpay')">直接发起微信支付</button>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY || H5 || APP -->
<button @click="createOrder('alipay')">直接发起支付宝支付</button>
<!-- #endif -->
<button @click="createQRcode('wxpay')">生成独立支付二维码</button>
<view class="tips">用于把生成的二维码放到自己写的页面中(组件不会弹窗,请从日志中查看二维码base64值)</view>
<button @click="getOrder">查询支付状态</button>
<!--
<button @click="refund">发起退款</button>
<view class="tips">发起退款需要admin权限,本示例未对接登录功能</view>
<button @click="getRefund">查询退款状态</button>
<button @click="closeOrder">关闭订单</button>
-->
<!-- #ifdef H5 -->
<button v-if="h5Env === 'h5-weixin'" @click="getWeiXinJsCode('snsapi_base')">公众号获取openid示例</button>
<!-- #endif -->
<!-- 统一支付组件,注意:vue3下ref不可以等于组件名,因此这里ref="pay" 而不能是 ref="uniPay" -->
<uni-pay ref="pay" :adpid="adpid" return-url="/pages/order-detail/order-detail" logo="/static/logo.png" @success="onSuccess" @create="onCreate"></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文件进行编写对应的回调逻辑即可)
//qr_code: true, // 是否强制使用扫码支付
openid:"", // 微信公众号需要
custom:{
a: "a",
b: 1
},
adpid: "1000000001", // uni-ad的广告位id
}
},
onLoad(options={}) {
if (options.code && options.state) {
// 获取微信公众号的openid
setTimeout(() => {
this.getOpenid({
provider: "wxpay",
code: options.code
});
}, 300);
}
}, },
methods: { methods: {
/** // 页面数据初始化函数
* 发起支付(唤起收银台,如果只有一种支付方式,则收银台不会弹出来,会直接使用此支付方式) init(options = {}){
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no let url = window.location.href;
*/ let newUrl = url.replace("uni-pay.html", "uni-pay/uni-app.html");
open() { window.location.href = newUrl;
this.order_no = `test`+Date.now();
this.out_trade_no = `${this.order_no}-1`;
// 打开支付收银台
this.$refs.pay.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, // 支付回调类型
qr_code: this.qr_code, // 是否强制使用扫码支付
openid: this.openid, // 微信公众号需要
custom: this.custom, // 自定义数据(此参数不推荐使用,因为是前端传的,此参数可能会被伪造,建议通过order_no查询自己业务订单表来获取自定义业务数据)
});
},
/**
* 发起支付(不唤起收银台,手动指定支付方式)
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
createOrder(provider){
this.order_no = `test`+Date.now();
this.out_trade_no = `${this.order_no}-1`;
// 发起支付
this.$refs.pay.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: this.qr_code, // 是否强制使用扫码支付
openid: this.openid, // 微信公众号需要
custom: this.custom, // 自定义数据(此参数不推荐使用,因为是前端传的,此参数可能会被伪造,建议通过order_no查询自己业务订单表来获取自定义业务数据)
});
},
/**
* 生成支付独立二维码(只返回支付二维码)
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
createQRcode(provider){
this.order_no = `test`+Date.now();
this.out_trade_no = `${this.order_no}-1`;
// 发起支付
this.$refs.pay.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, // 是否强制使用扫码支付
cancel_popup: true, // 配合qr_code:true使用,是否只生成支付二维码,没有二维码弹窗
openid: this.openid, // 微信公众号需要
custom: this.custom, // 自定义数据(此参数不推荐使用,因为是前端传的,此参数可能会被伪造,建议通过order_no查询自己业务订单表来获取自定义业务数据)
});
},
// 查询支付状态
async getOrder() {
let res = await this.$refs.pay.getOrder({
out_trade_no: this.out_trade_no, // 插件支付单号
await_notify: true
});
if (res) {
let obj = {
"-1": "已关闭",
"1": "已支付",
"0": "未支付",
"2": "已部分退款",
"3": "已全额退款"
};
uni.showToast({
title: obj[res.status] || res.errMsg,
icon: "none"
});
}
},
// 发起退款
async refund() {
let res = await this.$refs.pay.refund({
out_trade_no: this.out_trade_no, // 插件支付单号
});
if (res) {
uni.showToast({
title: res.errMsg,
icon: "none"
});
}
},
// 查询退款状态
async getRefund() {
let res = await this.$refs.pay.getRefund({
out_trade_no: this.out_trade_no, // 插件支付单号
});
if (res) {
uni.showModal({
content: res.errMsg,
showCancel: false
});
}
},
// 关闭订单
async closeOrder() {
let res = await this.$refs.pay.closeOrder({
out_trade_no: this.out_trade_no, // 插件支付单号
});
if (res) {
uni.showModal({
content: res.errMsg,
showCancel: false
});
}
},
// 获取公众号code
async getWeiXinJsCode(scope="snsapi_base") {
let res = await this.$refs.pay.getProviderAppId({
provider: "wxpay",
provider_pay_type: "jsapi"
});
if (res.appid) {
let appid = res.appid;
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) {
let res = await this.$refs.pay.getOpenid(data);
if (res) {
this.openid = res.openid;
uni.showToast({
title: "已获取到openid,可以开始支付",
icon: "none"
});
}
},
// 监听事件 - 支付订单创建成功(此时用户还未支付)
onCreate(res){
console.log('create: ', res);
// 如果只是想生成支付二维码,不需要组件自带的弹窗,则在这里可以获取到支付二维码 qr_code_image
},
// 监听事件 - 支付成功
onSuccess(res){
console.log('success: ', res);
if (res.user_order_success) {
// 代表用户已付款,且你自己写的回调成功并正确执行了
} else {
// 代表用户已付款,但你自己写的回调执行成功(通常是因为你的回调代码有问题)
}
}
},
computed: {
h5Env(){
// #ifdef H5
let ua = window.navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) == 'micromessenger' && (ua.match(/miniprogram/i) == 'miniprogram')) {
// 微信小程序
return "mp-weixin";
}
if (ua.match(/MicroMessenger/i) == 'micromessenger') {
// 微信公众号
return "h5-weixin";
}
if (ua.match(/alipay/i) == 'alipay' && ua.match(/miniprogram/i) == 'miniprogram') {
return "mp-alipay";
}
if (ua.match(/alipay/i) == 'alipay') {
return "h5-alipay";
}
// 外部 H5
return "h5";
// #endif
}
},
}
</script>
<style lang="scss" scoped>
.app{
padding: 30rpx;
}
input {
border: 1px solid #f3f3f3;
padding: 10rpx;
}
button {
margin-top: 20rpx;
}
.label{
margin: 10rpx 0;
}
.tips{
margin-top: 20rpx;
font-size: 24rpx;
color: #565656;
}
</style>
```
## 云对象(uni-pay-co)介绍@uni-pay-co
### 目录结构@cloudobject-catalogue
```
├─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 分模块存放的云对象方法的服务实现
```
### 公共响应参数@co-public-response
`uni-pay-co` 所有api返回值均满足[uniCloud响应体规范](https://uniapp.dcloud.net.cn/uniCloud/cf-functions.html#resformat)
返回值示例
```js
{
errCode: 0, // 错误码,详见错误码列表
errMsg: '', // 错误信息,uni-pay-co会自动根据客户端语言对错误信息进行国际化
// ...其余参数
}
```
## API列表@api
uni-pay前端组件和uni-pay-co云对象的方法是一样的。通常情况下,前端直接调用uni-pay组件内的方法即可(组件内会自动调用云对象内的API,无需再手动调用云对象内的API)
以下是介绍这些api。
| API | 说明 |
|-----------------|---------------------|
| uniPayCo.createOrder | 创建支付 [查看详情](#create-order) |
| uniPayCo.getOrder | 查询订单 [查看详情](#get-order) |
| uniPayCo.refund | 发起退款(此接口需要权限才可以访问) [查看详情](#refund) |
| uniPayCo.getRefund | 查询退款 [查看详情](#get-refund)|
| uniPayCo.closeOrder | 关闭订单 [查看详情](#close-order) |
| uniPayCo.getPayProviderFromCloud | 获取支持的支付供应商 [查看详情](#get-pay-provider-from-cloud) |
| uniPayCo.getProviderAppId | 获取支付配置内的appid(主要用于获取微信公众号的appid,用以获取code) [查看详情](#get-provider-appid) |
| uniPayCo.getOpenid | 根据code获取openid (主要用于微信公众号code换取openid) [查看详情](#get-openid) |
### 创建支付@create-order
**支付组件方法形式(收银台弹窗模式)(推荐)**
`open``createOrder`参数是一致的,唯一区别是`open`会打开收银台,而`createOrder`不带收银台,直接调用支付。
`open`如果只有一种支付方式,比如微信小程序内只能用微信支付,则不会弹收银台,而是直接调用支付。
```js
this.$refs.pay.open({
total_fee: 1, // 支付金额,单位分 100 = 1元
type: "recharge", // 支付回调类型
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
description: "uniCloud个人版包月套餐", // 支付描述
});
```
**直接跳收银台页面模式(推荐)**
与弹窗模式的区别是:跳页面模式是通过 `uni.navigateTo` 直接跳到收银台页面,而弹窗模式是在原页面弹出收银台。
```js
let options = {
total_fee: 1, // 支付金额,单位分 100 = 1元
type: "recharge", // 支付回调类型
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
description: "uniCloud个人版包月套餐", // 支付描述
};
let optionsStr = encodeURI(JSON.stringify(options));
uni.navigateTo({
url:`/uni_modules/uni-pay/pages/pay-desk/pay-desk?options=${optionsStr}`
});
```
收银台页面源码在 `/uni_modules/uni-pay/pages/pay-desk/pay-desk`
如果你想要自定义收银台样式,建议复制该页面到你的项目pages目录,如`/pages/pay-desk/pay-desk`,然后在复制的页面上进行修改样式,同时跳转到自定义收银台的代码如下:
```js
let options = {
total_fee: 1, // 支付金额,单位分 100 = 1元
type: "recharge", // 支付回调类型
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
description: "uniCloud个人版包月套餐", // 支付描述
};
let optionsStr = encodeURI(JSON.stringify(options));
uni.navigateTo({
url:`/pages/pay-desk/pay-desk?options=${optionsStr}`
});
```
**支付组件方法形式(不带收银台)**
不带收银台时,provider参数为必传项,代表支付供应商
```js
this.$refs.pay.createOrder({
provider: "wxpay", // 支付供应商
total_fee: 1, // 支付金额,单位分 100 = 1元
type: "recharge", // 支付回调类型
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
description: "uniCloud个人版包月套餐", // 支付描述
});
```
**云对象接口形式**
```js
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。
整个逻辑是这样的:
**以用户购买商品付款为例**
- 1、前端用户登录(非本插件功能)
- 2、前端用户购买商品并下单,云端生成你自己写的业务系统商品订单信息,并返回订单号 `order_no` 给前端(非本插件功能)
- 3、用上一步云端返回的 `order_no` 调用插件的[创建支付](#create-order)API(type参数的值写 `goods`),发起真正的支付功能(本插件功能)
- 4、用户支付成功后,云端接收第三方支付发过来的异步回调请求,云端校验请求合法性后,执行商品付款成功异步回调逻辑(即执行 `goods` 回调),同时标记订单为已付款(本插件功能)
- 5、前端监听到付款成功事件,跳转到支付成功页,并展示广告(本插件功能)
- 6、用户点击查看订单,跳转到你自己写的业务系统商品订单详情页(本插件功能)
- 7、完成
**以用户充值余额为例**
- 1、前端用户登录(非本插件功能)
- 2、前端用户提交充值余额的数量,云端生成你自己写的业务系统充值订单信息,并返回订单号 `order_no` 给前端(非本插件功能)
- 3、用上一步云端返回的 `order_no` 调用插件的[创建支付](#create-order)API(type参数的值写 `recharge`),发起真正的支付功能(本插件功能)
- 4、用户支付成功后,云端接收第三方支付发过来的异步回调请求,云端校验请求合法性后,执行余额充值付款成功异步回调逻辑(即执行 `recharge` 回调),同时标记订单为已付款(本插件功能)
- 5、前端监听到付款成功事件,跳转到支付成功页,并展示广告(本插件功能)
- 6、用户点击查看订单,跳转到你自己写的业务系统充值订单详情页(本插件功能)
- 7、完成
### 查询订单@get-order
**支付组件方法形式(推荐)**
```js
await this.$refs.pay.getOrder({
out_trade_no: "2022102701100010100101001", // 插件支付单号
await_notify: true, // 是否需要等待异步通知执行完成,若为了响应速度,可以设置为false,若需要等待异步回调执行完成,则设置为true
});
```
**云对象接口形式**
```js
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 | 支付订单完整信息 |
### 发起退款@refund
**注意**
发起退款默认需要admin权限(基于uni-id用户体系登录),否则会报权限不足或缺少token。[查看uni-id介绍](https://uniapp.dcloud.net.cn/uniCloud/uni-id/summary.html)
当然,你也可以修改`uni-pay-co/config/permission.js`这个文件内的权限规则。
**支付组件方法形式(推荐)**
```js
await this.$refs.pay.refund({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
```
**云对象接口形式**
```js
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 | 第三方供应商返回的结果 |
### 查询退款@get-refund
**支付组件方法形式(推荐)**
```js
await this.$refs.pay.getRefund({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
```
**云对象接口形式**
```js
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 | 支付订单信息 |
### 关闭订单@close-order
一般情况下,无需调用此方法去主动关闭订单(订单若未支付,则会在一段时间后自动关闭),但你有需要主动关闭订单的场景时,可以使用此api来主动关闭订单。(只有未支付的订单才可以主动关闭)
注意:
1. 微信支付订单生成后不能马上调用关单接口,最短调用时间间隔为5分钟
2. 支付宝订单生成后需用户进入过输入密码的页面,才能调用关单接口(无需间隔5分钟)
**支付组件方法形式(推荐)**
```js
await this.$refs.pay.closeOrder({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
```
**云对象接口形式**
```js
await uniPayCo.closeOrder({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
```
**参数说明**
| 参数名 | 类型 | 必填 | 说明 |
|-----------------|---------|------|---------------------------|
| out_trade_no | string | 是 | 插件订单号 |
**返回值**
| 参数名 | 类型 | 说明 |
|-----------------|---------|---------------------------|
| result | object | 第三方供应商返回的结果 |
### 获取支持的支付供应商@get-pay-provider-from-cloud
一般情况下,无需调用此api,`uni-pay` 组件内部已自动调用此api。
**支付组件方法形式(推荐)**
```js
await this.$refs.pay.getPayProviderFromCloud();
```
**云对象接口形式**
```js
await uniPayCo.getPayProviderFromCloud();
```
**参数说明**
该API无参数
**返回值**
| 参数名 | 类型 | 说明 |
|-----------------|---------|---------------------------|
| wxpay | boolean | 是否支持微信支付 |
| alipay | boolean | 是否支持支付宝支付 |
| provider | array&lt;string&gt; | 支持哪些支付供应商,如["wxpay","alipay"] |
### 获取支付配置内的appid@get-provider-appid
```js
await this.$refs.pay.getProviderAppId({
provider: "wxpay",
provider_pay_type: "jsapi",
});
```
**云对象接口形式**
```js
await uniPayCo.getProviderAppId({
provider: "wxpay",
provider_pay_type: "jsapi",
});
```
**参数说明**
| 参数名 | 类型 | 必填 | 说明 |
|-----------------|---------|------|---------------------------|
| provider | string | 是 | 支付供应商 如 wxpay alipay |
| provider_pay_type | string | 是 | 支付供应商 如 jsapi |
**返回值**
| 参数名 | 类型 | 说明 |
|-----------------|---------|---------------------------|
| appid | string | appid |
### 根据code获取openid@get-openid
一般用于微信公众号根据网页授权回调返回的code获取用户openid
**注意**
小程序不需要调用此方法,组件内部已自动静默获取openid
```js
await this.$refs.pay.getOpenid({
provider: "wxpay",
code: options.code
});
```
**云对象接口形式**
```js
await uniPayCo.getOpenid({
provider: "wxpay",
code: options.code
});
```
**参数说明**
| 参数名 | 类型 | 必填 | 说明 |
|-----------------|---------|------|---------------------------|
| provider | string | 是 | 支付供应商 如 wxpay alipay |
| code | string | 是 | 微信公众号网页授权回调返回的code |
**返回值**
| 参数名 | 类型 | 说明 |
|-----------------|---------|---------------------------|
| openid | string | openid |
### ios内购支付@appleiap
**概述**
IAP 全称:In-App Purchase,是指苹果 App Store 的应用内购买,是苹果为 App 内购买虚拟商品或服务提供的一套交易系统。
适用范围:在 App 内需要付费使用的产品功能或虚拟商品/服务,如游戏道具、电子书、音乐、视频、订阅会员、App的高级功能等需要使用 IAP,而在 App 内购买实体商品(如淘宝购买手机)或者不在 App 内使用的虚拟商品(如充话费)或服务(如滴滴叫车)则不适用于 IAP。
简而言之,苹果规定:适用范围内的虚拟商品或服务,必须使用 IAP 进行购买支付,不允许使用支付宝、微信支付等其它第三方支付方式(包括Apple Pay),也不允许以任何方式(包括跳出App、提示文案等)引导用户通过应用外部渠道购买。
**示例代码**
注意:只能使用uni-pay支付组件发起
```js
// 发起ios内购支付
this.$refs.pay.createOrder({
provider: "appleiap", // 支付供应商(这里固定未appleiap,代表ios内购支付)
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
type: "appleiap", // 支付回调类型(可自定义,建议填写appleiap)
productid: "io_dcloud_hellouniapp_pay_like6", // ios内购产品id(仅ios内购生效)
custom: {}, // 自定义数据(此参数不推荐使用,因为是前端传的,此参数可能会被伪造,建议通过order_no查询自己业务订单表来获取自定义业务数据)
});
```
[点击查看ios内购注意事项](#tips-appleiap)
完整ios内购支付示例代码
```html
<template>
<view class="content">
<view class="uni-list">
<radio-group @change="applePriceChange">
<label class="uni-list-cell" v-for="(item, index) in productList" :key="index">
<radio :value="item.productid" :checked="item.checked" />
<view class="price">{{item.title}} {{item.price}}元</view>
</label>
</radio-group>
</view>
<view class="uni-padding-wrap">
<button class="btn-pay" @click="createOrder" :loading="loading" :disabled="disabled">立即支付</button>
</view>
<!-- 统一支付组件,注意:vue3下ref不可以等于组件名,因此这里ref="pay" 而不能是 ref="uniPay" -->
<uni-pay ref="pay" :debug="true" :adpid="adpid" return-url="/pages/order-detail/order-detail" @mounted="onMounted" @success="onSuccess"></uni-pay>
</view>
</template>
<script>
export default {
data() {
return {
order_no: "", // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: "", // 插件支付单号
adpid: "1000000001", // uni-ad的广告位id
loading: false, // 支付按钮是否在loading中
disabled: true, // 支付按钮是否禁用
productid: "", // 用户选择的商品id
// 出售的ios内购商品列表
productList: [
{
"description": "为DCloud提供的免费软件进行赞助",
"price": 1,
"productid": "io_dcloud_hellouniapp_pay_like1",
"title": "赞赏"
},
{
"description": "为DCloud提供的免费软件进行赞助",
"price": 6,
"productid": "io_dcloud_hellouniapp_pay_like6",
"title": "赞赏"
}
],
} }
},
onLoad: function() {
},
onShow() {
if (this.$refs.pay && this.$refs.pay.appleiapRestore) {
// ios内购支付漏单重试
this.$refs.pay.appleiapRestore();
}
},
onUnload() {},
methods: {
// 支付组件加载完毕后执行
onMounted(insideData){
this.init();
},
// 初始化
async init() {
this.productList[0].checked = true;
this.productid = this.productList[0].productid;
this.disabled = false;
if (this.$refs.pay && this.$refs.pay.appleiapRestore) {
// ios内购支付漏单重试
this.$refs.pay.appleiapRestore();
}
},
/**
* 发起支付
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
createOrder(){
this.order_no = `test`+Date.now();
this.out_trade_no = this.order_no;
// 发起支付
this.$refs.pay.createOrder({
provider: "appleiap", // 支付供应商(这里固定未appleiap,代表ios内购支付)
order_no: this.order_no, // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: this.out_trade_no, // 插件支付单号
type: "appleiap", // 支付回调类型(可自定义,建议填写appleiap)
productid: this.productid, // ios内购产品id(仅ios内购生效)
custom: {}, // 自定义数据(此参数不推荐使用,因为是前端传的,此参数可能会被伪造,建议通过order_no查询自己业务订单表来获取自定义业务数据)
});
},
// 监听事件 - 支付成功
onSuccess(res){
console.log('success: ', res);
if (res.user_order_success) {
// 代表用户已付款,且你自己写的回调成功并正确执行了
} else {
// 代表用户已付款,但你自己写的回调执行失败(通常是因为你的回调代码有问题)
}
},
// 监听-多选框选中的值改变
applePriceChange(e) {
this.productid = e.detail.value;
},
} }
} };
</script> </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>
```
## 支付统计@pay-stat
`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文件
```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}
```
### 收款趋势
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A101.png"/>
</div>
**概况**
`概况`栏目中可以直观的看到今日、昨日、前日、本周、本月、本季度、本年度、累计数据。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A101-01.png"/>
</div>
**名词解释:**
- 下单金额(GMV):统计时间内,下单金额(包含未支付订单和退款订单)。
- 收款金额(GPV):统计时间内,成功支付的订单金额(包含退款订单)。
- 退款金额:统计时间内,发生退款的金额。
- 实收金额:实收金额=收款金额-退款金额
**今日数据**
`今日数据`栏目中可以看到更多今日统计数据。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A101-02.png"/>
</div>
**名词解释:**
- 订单金额:
+ 下单:今日下单金额(包含未支付订单和退款订单)。
+ 收款:今日成功支付的订单金额(包含退款订单)。
+ 退款:今日发生退款的金额。
- 订单数量:
+ 下单:今日成功下单的订单笔数(包含未支付订单和退款订单)。
+ 收款:今日成功支付的订单数(包含退款订单)。
+ 退款:今日发生退款的订单数。
- 用户数量:
+ 下单:今日成功下单的客户数(包含未支付订单和退款订单)。
+ 收款:今日成功支付的用户数(包含退款订单)。
+ 退款:今日发生退款的用户数。
- 设备数量:
+ 下单:今日成功下单的设备数(包含未支付订单和退款订单)。
+ 收款:今日成功支付的设备数(包含退款订单)。
+ 退款:今日发生退款的设备数。
**趋势图**
`趋势图`栏目中以`天维度``月维度``季维度``年维度`进行趋势统计。可以直观的看到收入的增长趋势。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A101-03.png"/>
</div>
### 转换漏斗分析
可以为您分析指定时间段的支付转化率,同时展示支付转化率趋势图。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A102.png"/>
</div>
**名词解释:**
- 活跃设备数:包含未登录和已登录的用户数量
- 活跃用户数:登录用户的数量
- 支付用户数:至少有一笔成功支付订单的用户
- 用户转化率:用户转化率=活跃用户数/活跃设备数
- 支付转化率:支付转化率=支付用户数/活跃用户数
### 价值用户排行
可以为您快速筛选高价值用户,高复购率用户。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A103.png"/>
</div>
### 订单明细
可以搜索、查看订单详情
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A104.png"/>
</div>
## 注意事项@tips
### 微信公众号@tips-wxpay-jsapi
h5的路由模式必须配置为 `history`,因为微信公众号登录的回调地址不支持 `hash` 模式。
同时微信公众号开发调试比较麻烦,麻烦在于网页授权需要添加域名白名单,用localhost或用ip访问本地是无法获取到微信的code的,这样也就无法获取openid,导致无法支付。
操作步骤
- 1、手机和电脑连接在同一个局域网(路由器WiFi下)
- 2、查看自己电脑的局域网ip地址,比如为192.168.1.8
- 3、假设你的线上域名是(必须要有自己的域名)www.abc.com 则设置 test.abc.com 先解析到你的前端托管域名上(为了让微信验证域名通过,因为验证域名时,需要上传微信指定的文件到你的前端托管)。
- 4、进入公众号后台,设置与开发 -> 公众号设置 -> 设置网页授权域名,添加 test.abc.com
- 5、成功添加后,再重新设置 test.abc.com 解析到你电脑的局域网ip,如192.168.1.8
- 6、过一段时间(大概20分钟后,更换域名解析生效需要时间,这20分钟内千万不要再去访问http://test.abc.com)
- 7、20分钟后,访问 http://test.abc.com 此时就等于访问了 http://192.168.1.8,这样你的手机就用 http://test.abc.com 来访问你的项目
- 8、可以正常获取到openid了,就可以正常进行本地微信公众号支付测试了(不然每次都要上传到服务器测试)。
当用自定义域名时,还需要在项目根目录添加 `vue.config.js` 文件,内容如下:
```js
module.exports = {
devServer: {
disableHostCheck: true, // 忽略域名检查
port: 80, // 设置80端口为项目启动端口
}
}
```
### 微信小程序@tips-wxpay-mp
微信小程序支付除了配置uni-pay的支付配置外,还需要配置 `manifest.json` 内的 微信小程序appid,如下图所示。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-7.png"/>
</div>
如果报如下错误,请点[这里](#question-mp-weixin-domain)
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-32.png)
### APP支付@tips-app
APP支付除了配置uni-pay的支付配置外,还需要打包时添加支付模块,如下图所示。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-8.png"/>
</div>
同时,还需要打自定义基座(包名需要和开放平台下填写的一致),且你在开放平台下的这个应用必须通过审核才可以。(比如微信开放平台下的APP应用显示通过审核才可以)
### ios内购支付@tips-appleiap
1. ios内购支付需勾选App模块配置中的Apple应用内支付
2. 需要打ios自定义基座
3. 需要注册ios开发者账号,且交了年费(688元/年)
4. 需要在ios开发者平台添加内购商品,并获得商品id
5. ios沙箱测试时,需要先在ios开发者平台添加沙箱测试账号,同时你的测试手机上需要登录ios沙箱账号
6. 目前hbx版本热刷新会导致ios支付无法正常调用,因此每次修改完代码保存后,需要先关闭手机App,然后hbx重启项目,再打开手机app。(后面HBX会修复此问题)
## 全局错误码@errorcode
| 错误模块 | 错误码 | 说明 |
|---------|-------------|---------------------------|
| 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 | 证书错误,请检查支付证书 |
返回值示例
```json
{
"errMsg": "支付单号(out_trade_no)不能为空",
"errCode": 51001,
"errSubject": "uni-pay"
}
```
## 常见问题@question
### 老项目如何升级到uni-pay 2
`uni-pay 2` 仍内置了uni-pay公共模块,向下兼容`uni-pay 1.x`,即从`uni-pay 1.x`可以一键升级到`uni-pay 2.x`,且不会对你的老项目造成影响。
### 发起支付时报数据库表不存在
支付插件需要创建支付相关的表后才能正常运行。[查看相关的数据库表](#database)
### 支付账号如何申请
本插件对接的支付渠道是微信和支付宝官方渠道
**微信支付**
申请地址 [https://pay.weixin.qq.com/index.php/apply/applyment_home/guide_normal](https://pay.weixin.qq.com/index.php/apply/applyment_home/guide_normal)
申请指引 [https://pay.weixin.qq.com/static/applyment_guide/applyment_index.shtml](https://pay.weixin.qq.com/static/applyment_guide/applyment_index.shtml)
**支付宝**
申请地址 [https://open.alipay.com](https://open.alipay.com)
申请指引 [https://opendocs.alipay.com/common/02asmu](https://opendocs.alipay.com/common/02asmu)
**注意**
支付账号申请需要企业资质(个体工商户也可以,但不可以是个人资质,需要有营业执照,银行对公账户)。
### 如何获得插件需要的密钥参数@get-config-help
**微信支付**
[微信支付参数和证书生成教程](https://docs.qq.com/doc/DWUpGTW1kSUdpZGF5)
- pfx:微信支付v2需要用到的证书,是一个后缀名为`.p12`的文件,如果你的`.p12`文件不是`apiclient_cert.p12`,则将它改名成`apiclient_cert.p12`,并复制到 `uni-config-center/uni-pay/wxpay/` 目录下
- appCertPath:微信支付v3需要用到的证书,是一个名为`apiclient_cert.pem`的文件,将它复制到 `uni-config-center/uni-pay/wxpay/` 目录下
- appPrivateKeyPath:微信支付v3需要用到的证书,是一个名为`apiclient_key.pem`的文件,将它复制到 `uni-config-center/uni-pay/wxpay/` 目录下
**支付宝**
[支付宝支付证书生成教程](https://docs.qq.com/doc/DWVBlVkZ1Z21SZFpS)
- privateKey:支付宝商户私钥
- appCertPath:支付宝商户公钥路径,是一个后缀名为`appCertPublicKey.crt`的文件,将它复制到 `uni-config-center/uni-pay/alipay/` 目录下
- alipayPublicCertPath:支付宝商户公钥路径,是一个后缀名为`alipayCertPublicKey_RSA2.crt`的文件,将它复制到 `uni-config-center/uni-pay/alipay/` 目录下
- alipayRootCertPath:支付宝根证书路径,是一个后缀名为`alipayRootCert.crt`的文件,将它复制到 `uni-config-center/uni-pay/alipay/` 目录下
### 微信小程序真机报fail url not in domain list错误@question-mp-weixin-domain
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-32.png)
这是由于云开发的域名没有添加到微信小程序域名白名单导致的,需要去微信小程序后台,添加以下域名到微信小程序域名白名单
```
https://api.next.bspapp.com;https://api.bspapp.com;https://tcb-api.tencentcloudapi.com;
```
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-31.png"/>
</div>
**添加完域名后,一定要重启微信开发者工具,然后去手机微信里删除最近使用的小程序(这一步很关键),最后重新扫二维码进入小程序。**
### 支付宝小程序云执行微信v2退款接口失败,报Error: unsupported, POST https://api.mch.weixin.qq.com/secapi/pay/refund -1@question-alipay-weixin-v2-refund
有两个方案可以解决
方案一:使用微信支付v3版本
方案二:将云函数的node版本切换成node16(支付宝小程序云默认是node18,而node18不再支持微信支付v2证书pfx的加密算法导致的)
# uni-pay-x
> `uni-pay-x` 是 `uni-pay` 的 `uni-app x` 版,目前 `uni-pay-x` 仅支持 Android 端,且只支持支付宝支付
> 本文档适用于客户端为 `uni-app x` 的版本,需 HBuilderX 4.02 及以上版本。若客户端为 `uni-app` 则请访问:[uni-pay 文档](./uni-app.md)
## 简介@introduction
支付,重要的变现手段,但开发复杂。在不同端,对接微信支付(暂不支持)、支付宝等渠道,前端后端都要写不少代码。
涉及金额可不是小事,生成业务订单、获取收银台、发起支付、支付状态查询、支付异步回调、失败处理、发起退款、退款状态查询、支付统计...众多环节,代码量多,出错率高。
为什么不能有一个开源的、高质量的项目?即可以避免大家重复开发,又可以安心使用,不担心自己从头写产生Bug。
`uni-pay-x`应需而生。
开发者在项目中引入 `uni-pay-x` 后,微信支付(暂不支持)、支付宝支付等功能无需自己再开发。由于源码的开放性和层次结构清晰,有二次开发需求也很方便调整。(暂只支持支付宝支付)
> 插件市场地址:[https://ext.dcloud.net.cn/plugin?name=uni-pay-x](https://ext.dcloud.net.cn/plugin?name=uni-pay-x)
**线上体验地址**
![](https://web-ext-storage.dcloud.net.cn/unicloud/uni-pay-x/518.png)
`uni-pay-x` 的功能包括:
- 页面
+ 支付收银台组件(让用户选择付款渠道) [组件详情](#uni-pay-component)
- 云对象([uni-pay-co](#uni-pay-co)
+ 支付宝支付
+ 支付宝APP支付
+ 支付宝小程序支付
+ 支付宝手机外部浏览器H5支付(支持在微信APP的H5页面中使用支付宝支付)
+ 支付宝PC扫码支付
+ 通用接口
+ 支付异步回调
+ 查询订单
+ 发起退款
+ 查询退款
+ 关闭订单
+ 获取当前支持的支付方式
+ 获取当前支付用户的openid
- 支付统计(内置于uni-admin的支付统计中)
+ 收款趋势
+ 转换漏斗分析
+ 价值用户排行
+ 订单明细
## uni-pay-x组成@catalogue
uni-pay-x云端一体模板,包含前端页面、云对象、云端公共模块、uni-config-center配置、opendb数据表等内容。以及内置于uni-admin的支付统计报表。
### uni-pay-x的uni_modules
uni-pay-x的[uni_modules](https://uniapp.dcloud.net.cn/plugin/uni_modules.html)中包含了前端页面、云对象和公共模块,目录结构如下:
```
├─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 插件自述文件
```
### uni-pay-x的uni-config-center配置
支付配置不在插件目录中,统一存放在 `uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js` [查看支付配置介绍](#config)
### uni-pay的opendb数据表@database
支付插件需要创建以下表后才能正常运行,可以右键 `database` 目录,初始化数据库功能来创建表。
- 支付订单表 [uni-pay-orders](https://gitee.com/dcloud/opendb/blob/master/collection/uni-pay-orders/collection.json)
## 示例项目运行教程@rundemo
在对接自己的项目之前,建议先跑通示例项目,能跑通示例项目,代表你的配置和证书一定是正确的,然后再将`uni-pay-x`集成到你自己的项目中。
1. 从插件市场导入`uni-pay-x`示例项目。[前往插件市场](https://ext.dcloud.net.cn/plugin?name=uni-pay-x)
2. 打开`uni-pay`配置文件,配置文件地址: `uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js` [查看支付配置介绍](#config)
3. 上传公共模块 `uni-config-center`(右键,上传公共模块,每次修改了支付配置,都需要重新上传此模块才会生效)
4. 上传公共模块 `uni-pay`(右键,上传公共模块)
5. 上传云对象 `uni-pay-co`(右键,上传部署。当然对uniCloud目录点右键批量上传也可以)
6. 数据库初始化
![](https://web-ext-storage.dcloud.net.cn/unicloud/uni-pay/462.png)
![](https://web-ext-storage.dcloud.net.cn/unicloud/uni-pay/463.png)
7. 运行启动项目,**在HBuilderX的运行控制台里选择使用云端云函数环境**
**注意:测试支付回调必须选择云端云函数环境**
![](https://web-ext-storage.dcloud.net.cn/unicloud/uni-pay-x/520.png)
8. 前端页面里点击唤起收银台支付,如果可以正常支付,代表示例项目运行成功,可以开始对接自己的项目了。 [对接自己项目](#install)
![](https://web-ext-storage.dcloud.net.cn/unicloud/uni-pay-x/521.png)
## uni-pay的config-center配置@config
开发者在微信和支付宝的支付后台,需要申请开通支付服务,成功后会得到各种凭据,这些凭据要配置在uni-pay的配置中。
配置文件在 `uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js`
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-3.png)
### 完整支付配置示例@config-demo
这里是支付宝全平台支付配置样例。如果只使用部分支付方式,后续有专门的分渠道章节。
```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位以上即可
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - 小程序支付配置
"mp": {
"appId": "", // 支付宝小程序appid
"privateKey": "", // 支付宝商户私钥
"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": "", // 支付宝商户私钥
"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": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
}
}
}
```
如果你对支付配置中各参数如何获取有疑问,请点击[获取支付配置帮助](#get-config-help)
### 支付回调配置@config-notify
对应支付配置的节点是 `notifyUrl`
**示例**
```js
// 统一 - 支付回调地址,格式为 "服务空间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如何获取?**
[点击此处进入服务空间列表](https://unicloud.dcloud.net.cn/home),找到你项目用的服务空间,复制其服务空间ID
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-4.png)
**URL化地址如何获取?**
[点击此处进入服务空间列表](https://unicloud.dcloud.net.cn/home),找到你项目用的服务空间,点击服务空间名称进入空间详情页,点击左侧菜单【云函数/云对象】- 点击【uni-pay-co】云对象右侧的【详情】按钮
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-5.png"/>
</div>
进入详情后,点下面的【复制路径】,复制的内容就是【URL化地址】
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-6.png"/>
</div>
### 分渠道支付配置示例@config-part
上面的配置样例是微信和支付宝全端配置样例。如果只使用一种支付场景,比如微信公众号里的微信支付,可以看下面章节的分渠道支付配置样例。
#### 支付宝APP支付@config-alipay-app
对应支付配置的节点是 `alipay.app`
```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",
},
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - APP支付配置
"app": {
"appId": "", // 支付宝开放平台下应用的appid
"privateKey": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
},
}
}
```
#### 支付宝小程序支付@config-alipay-mp
对应支付配置的节点是 `alipay.mp`
```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",
},
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - 小程序支付配置
"mp": {
"appId": "", // 支付宝小程序appid
"privateKey": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
},
}
}
```
#### 支付宝手机外部浏览器H5支付@config-alipay-native-h5
对应支付配置的节点是 `alipay.native`(和PC扫码配置节点一样)
```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",
},
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - H5支付配置(包含:网站二维码、手机H5,需申请支付宝当面付接口权限)
"native": {
"appId": "", // 支付宝开放平台下应用的appid
"privateKey": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
}
}
}
```
#### 支付宝PC扫码支付@config-alipay-native-pc
对应支付配置的节点是 `alipay.native`
```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",
},
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - H5支付配置(包含:网站二维码、手机H5,需申请支付宝当面付接口权限)
"native": {
"appId": "", // 支付宝开放平台下应用的appid
"privateKey": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
}
}
}
```
## 集成到自己项目的教程@install
在对接自己的项目之前,建议先[跑通示例项目](#rundemo),能跑通示例项目,代表你的配置和证书一定是正确的,然后再将`uni-pay`集成到你自己的项目中。
### 安装插件
1. 从插件市场导入`uni-pay-x`插件到你自己的项目。[前往插件市场](https://ext.dcloud.net.cn/plugin?name=uni-pay-x)
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-10.png)
2. 复制你刚运行的示例项目中的`uni-pay`配置文件,配置文件地址: `uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js`到你的项目中 [查看支付配置介绍](#config)
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-3.png)
3. 上传公共模块 `uni-config-center`(右键,上传公共模块,每次修改了支付配置,都需要重新上传此模块才会生效)
4. 上传公共模块 `uni-pay`(右键,上传公共模块)
5. 上传云对象 `uni-pay-co`(右键,上传部署)
6. 数据库初始化
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-28.png)
7. 项目根目录`pages.json`添加`subPackages`分包页面配置(如果页面已自动配置,则可无视此步骤)
```js
"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"
}
}
]
}
],
```
8. 安装完成
### 前端页面集成@quickly-pages
打开你需要进行支付的页面,一般是业务订单提交之后的页面来展现收银台。
1. 该页面在 `template` 内放一个 `uni-pay` 组件标签,声明ref,然后调用组件的API。如下
注意:vue3下ref不可以等于组件名,因此这里 `ref="payRef"` 而不能是 `ref="uniPay"`
```html
<template>
<view>
<button @click="open">唤起收银台支付</button>
<uni-pay ref="payRef" height="900rpx"></uni-pay>
</view>
</template>
```
2. 在script中编写代码,点击付款时执行方法:
非 setup 模式
```vue
<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 模式
```vue
<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>
```
### 云端支付回调集成@notify
当用户支付成功后,我们要给用户增加余额或者给业务订单标记支付成功,这些通过异步回调通知来实现的。
**提示:异步回调通知写在 `uni-pay-co/notify` 目录下,在此目录新建2个js文件,分别为 `recharge.js`、`goods.js` 文件,同时复制以下代码要你新建的2个js文件里。**
代码如下
```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` 参数
```js
// 打开支付收银台
this.$refs.payRef.open({
type: "recharge", // 支付回调类型 recharge 代表余额充值(当然你可以自己自定义)
});
```
**注意:每次修改都需要重新上传云对象`uni-pay-co`**
#### 业务在uniCloud上@service-inside
如果你的业务在uniCloud上,那么可以使用方式一或方式二进行编写自定义回调逻辑。
**方式一:直接写数据库操作**
适用场景:简单数据库操作场景
以给用户充值余额为例,代码如下
```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,
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;
};
```
**方式二:直接调用其他云函数或云对象**
适用场景:业务较为复杂,需写在其他云函数或云对象里的场景。
调用其他云函数示例代码如下
```js
'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;
};
```
调用其他云对象示例代码如下
```js
'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上@service-outside
如果你的业务不在uniCloud上,如java或php写的后端服务,uni-pay也可以满足你的支付需求,你只需要使用回调方式三的http接口形式调用你自己系统的回调接口即可。
**方式三:使用 await uniCloud.httpclient.request 调用外部http接口**
适用场景:业务不在uniCloud上。
示例代码如下
```js
'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;
};
```
#### java解密示例代码
```java
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解密示例代码
```php
<?php
$key = '12345678901234561234567890123456'; // 必须是固定的32位(只支持数字、英文)
$encrypt = "es2aF7DWr169X4fvMnlKNg=="; // 待解密的内容
// 解密
$decrypt = openssl_decrypt(base64_decode($encrypt), 'aes-256-ecb', substr($key, 0, 32), OPENSSL_RAW_DATA);
echo $decrypt;
?>
```
### 运行启动
运行你的项目,进行支付的体验和测试。
## uni-pay组件介绍@uni-pay-component
#### 组件属性
| 属性名 | 说明 | 类型 | 默认值 | 可选值 |
|-----------------|-------------------------------|---------|--------|-------|
| 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 |
#### 组件方法
通过
```js
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
await payInstance.xxx();
```
方式调用,详情调用方式参考下方的【前端完整示例代码】
| 方法名 | 说明 |
|---------------------------|---------------------|
| open | 发起支付 - 打开支付收银台弹窗 [查看详情](#create-order) |
| createOrder | 直接发起支付(无收银台) [查看详情](#create-order) |
| getOrder | 查询订单 [查看详情](#get-order) |
| refund | 发起退款(此接口需要权限才可以访问) [查看详情](#refund) |
| getRefund | 查询退款 [查看详情](#get-refund) |
| closeOrder | 关闭订单 [查看详情](#close-order) |
| getPayProviderFromCloud | 获取支持的支付供应商 [查看详情](#get-pay-provider-from-cloud) |
| getProviderAppId | 获取支付配置内的appid(主要用于获取微信公众号的appid,用以获取code) [查看详情](#get-provider-appid) |
| getOpenid | 根据code获取openid (主要用于微信公众号code换取openid) [查看详情](#get-openid) |
**前端完整示例代码**
```html
<template>
<view class="app">
<view>
<text style="color: red;">注意:uni-app x暂不支持微信支付</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": "ios内购"
} 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>
```
## 云对象(uni-pay-co)介绍@uni-pay-co
### 目录结构@cloudobject-catalogue
```
├─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 云对象入口文件
```
### 公共响应参数@co-public-response
`uni-pay-co` 所有api返回值均满足[uniCloud响应体规范](https://uniapp.dcloud.net.cn/uniCloud/cf-functions.html#resformat)
返回值示例
```js
{
errCode: 0, // 错误码,详见错误码列表
errMsg: '', // 错误信息,uni-pay-co会自动根据客户端语言对错误信息进行国际化
// ...其余参数
}
```
## API列表@api
uni-pay前端组件和uni-pay-co云对象的方法是一样的。通常情况下,前端直接调用uni-pay组件内的方法即可(组件内会自动调用云对象内的API,无需再手动调用云对象内的API)
以下是介绍这些api。
| API | 说明 |
|-----------------|---------------------|
| uniPayCo.createOrder | 创建支付 [查看详情](#create-order) |
| uniPayCo.getOrder | 查询订单 [查看详情](#get-order) |
| uniPayCo.refund | 发起退款(此接口需要权限才可以访问) [查看详情](#refund) |
| uniPayCo.getRefund | 查询退款 [查看详情](#get-refund)|
| uniPayCo.closeOrder | 关闭订单 [查看详情](#close-order) |
| uniPayCo.getPayProviderFromCloud | 获取支持的支付供应商 [查看详情](#get-pay-provider-from-cloud) |
| uniPayCo.getProviderAppId | 获取支付配置内的appid(主要用于获取微信公众号的appid,用以获取code) [查看详情](#get-provider-appid) |
| uniPayCo.getOpenid | 根据code获取openid (主要用于微信公众号code换取openid) [查看详情](#get-openid) |
### 创建支付@create-order
**支付组件方法形式(收银台弹窗模式)(推荐)**
`open``createOrder`参数是一致的,唯一区别是`open`会打开收银台,而`createOrder`不带收银台,直接调用支付。
`open`如果只有一种支付方式,比如微信小程序内只能用微信支付,则不会弹收银台,而是直接调用支付。
```js
this.$refs.payRef.open({
total_fee: 1, // 支付金额,单位分 100 = 1元
type: "recharge", // 支付回调类型
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
description: "uniCloud个人版包月套餐", // 支付描述
});
```
**直接跳收银台页面模式(推荐)**
与弹窗模式的区别是:跳页面模式是通过 `uni.navigateTo` 直接跳到收银台页面,而弹窗模式是在原页面弹出收银台。
```js
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`,然后在复制的页面上进行修改样式,同时跳转到自定义收银台的代码如下:
```js
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参数为必传项,代表支付供应商
```js
this.$refs.payRef.createOrder({
provider: "wxpay", // 支付供应商
total_fee: 1, // 支付金额,单位分 100 = 1元
type: "recharge", // 支付回调类型
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
description: "uniCloud个人版包月套餐", // 支付描述
});
```
**云对象接口形式**
```js
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。
整个逻辑是这样的:
**以用户购买商品付款为例**
- 1、前端用户登录(非本插件功能)
- 2、前端用户购买商品并下单,云端生成你自己写的业务系统商品订单信息,并返回订单号 `order_no` 给前端(非本插件功能)
- 3、用上一步云端返回的 `order_no` 调用插件的[创建支付](#create-order)API(type参数的值写 `goods`),发起真正的支付功能(本插件功能)
- 4、用户支付成功后,云端接收第三方支付发过来的异步回调请求,云端校验请求合法性后,执行商品付款成功异步回调逻辑(即执行 `goods` 回调),同时标记订单为已付款(本插件功能)
- 5、前端监听到付款成功事件,跳转到支付成功页,并展示广告(本插件功能)
- 6、用户点击查看订单,跳转到你自己写的业务系统商品订单详情页(本插件功能)
- 7、完成
**以用户充值余额为例**
- 1、前端用户登录(非本插件功能)
- 2、前端用户提交充值余额的数量,云端生成你自己写的业务系统充值订单信息,并返回订单号 `order_no` 给前端(非本插件功能)
- 3、用上一步云端返回的 `order_no` 调用插件的[创建支付](#create-order)API(type参数的值写 `recharge`),发起真正的支付功能(本插件功能)
- 4、用户支付成功后,云端接收第三方支付发过来的异步回调请求,云端校验请求合法性后,执行余额充值付款成功异步回调逻辑(即执行 `recharge` 回调),同时标记订单为已付款(本插件功能)
- 5、前端监听到付款成功事件,跳转到支付成功页,并展示广告(本插件功能)
- 6、用户点击查看订单,跳转到你自己写的业务系统充值订单详情页(本插件功能)
- 7、完成
### 查询订单@get-order
**支付组件方法形式(推荐)**
```js
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
await payInstance.getOrder({
out_trade_no: "2022102701100010100101001", // 插件支付单号
await_notify: true, // 是否需要等待异步通知执行完成,若为了响应速度,可以设置为false,若需要等待异步回调执行完成,则设置为true
});
```
**云对象接口形式**
```js
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 | 支付订单完整信息 |
### 发起退款@refund
**注意**
发起退款默认需要admin权限(基于uni-id用户体系登录),否则会报权限不足或缺少token。[查看uni-id介绍](https://uniapp.dcloud.net.cn/uniCloud/uni-id/summary.html)
当然,你也可以修改`uni-pay-co/config/permission.js`这个文件内的权限规则。
**支付组件方法形式(推荐)**
```js
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
await payInstance.refund({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
```
**云对象接口形式**
```js
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 | 第三方供应商返回的结果 |
### 查询退款@get-refund
**支付组件方法形式(推荐)**
```js
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
await payInstance.getRefund({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
```
**云对象接口形式**
```js
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 | 支付订单信息 |
### 关闭订单@close-order
一般情况下,无需调用此方法去主动关闭订单(订单若未支付,则会在一段时间后自动关闭),但你有需要主动关闭订单的场景时,可以使用此api来主动关闭订单。(只有未支付的订单才可以主动关闭)
注意:
1. 微信支付订单生成后不能马上调用关单接口,最短调用时间间隔为5分钟
2. 支付宝订单生成后需用户进入过输入密码的页面,才能调用关单接口(无需间隔5分钟)
**支付组件方法形式(推荐)**
```js
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
await payInstance.closeOrder({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
```
**云对象接口形式**
```js
await uniPayCo.closeOrder({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
```
**参数说明**
| 参数名 | 类型 | 必填 | 说明 |
|-----------------|---------|------|---------------------------|
| out_trade_no | string | 是 | 插件订单号 |
**返回值**
| 参数名 | 类型 | 说明 |
|-----------------|---------|---------------------------|
| result | object | 第三方供应商返回的结果 |
### 获取支持的支付供应商@get-pay-provider-from-cloud
一般情况下,无需调用此api,`uni-pay` 组件内部已自动调用此api。
**支付组件方法形式(推荐)**
```js
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
await payInstance.getPayProviderFromCloud();
```
**云对象接口形式**
```js
await uniPayCo.getPayProviderFromCloud();
```
**参数说明**
该API无参数
**返回值**
| 参数名 | 类型 | 说明 |
|-----------------|---------|---------------------------|
| wxpay | boolean | 是否支持微信支付 |
| alipay | boolean | 是否支持支付宝支付 |
| provider | array&lt;string&gt; | 支持哪些支付供应商,如["wxpay","alipay"] |
### 获取支付配置内的appid@get-provider-appid
```js
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
await payInstance.getProviderAppId({
provider: "wxpay",
provider_pay_type: "jsapi",
});
```
**云对象接口形式**
```js
await uniPayCo.getProviderAppId({
provider: "wxpay",
provider_pay_type: "jsapi",
});
```
**参数说明**
| 参数名 | 类型 | 必填 | 说明 |
|-----------------|---------|------|---------------------------|
| provider | string | 是 | 支付供应商 如 wxpay alipay |
| provider_pay_type | string | 是 | 支付供应商 如 jsapi |
**返回值**
| 参数名 | 类型 | 说明 |
|-----------------|---------|---------------------------|
| appid | string | appid |
### 根据code获取openid@get-openid
一般用于微信公众号根据网页授权回调返回的code获取用户openid
**注意**
小程序不需要调用此方法,组件内部已自动静默获取openid
```js
const payInstance = this.$refs["payRef"] as UniPayComponentPublicInstance;
await payInstance.getOpenid({
provider: "wxpay",
code: options.code
});
```
**云对象接口形式**
```js
await uniPayCo.getOpenid({
provider: "wxpay",
code: options.code
});
```
**参数说明**
| 参数名 | 类型 | 必填 | 说明 |
|-----------------|---------|------|---------------------------|
| provider | string | 是 | 支付供应商 如 wxpay alipay |
| code | string | 是 | 微信公众号网页授权回调返回的code |
**返回值**
| 参数名 | 类型 | 说明 |
|-----------------|---------|---------------------------|
| openid | string | openid |
## 支付统计@pay-stat
`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文件
```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}
```
### 收款趋势
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A101.png"/>
</div>
**概况**
`概况`栏目中可以直观的看到今日、昨日、前日、本周、本月、本季度、本年度、累计数据。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A101-01.png"/>
</div>
**名词解释:**
- 下单金额(GMV):统计时间内,下单金额(包含未支付订单和退款订单)。
- 收款金额(GPV):统计时间内,成功支付的订单金额(包含退款订单)。
- 退款金额:统计时间内,发生退款的金额。
- 实收金额:实收金额=收款金额-退款金额
**今日数据**
`今日数据`栏目中可以看到更多今日统计数据。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A101-02.png"/>
</div>
**名词解释:**
- 订单金额:
+ 下单:今日下单金额(包含未支付订单和退款订单)。
+ 收款:今日成功支付的订单金额(包含退款订单)。
+ 退款:今日发生退款的金额。
- 订单数量:
+ 下单:今日成功下单的订单笔数(包含未支付订单和退款订单)。
+ 收款:今日成功支付的订单数(包含退款订单)。
+ 退款:今日发生退款的订单数。
- 用户数量:
+ 下单:今日成功下单的客户数(包含未支付订单和退款订单)。
+ 收款:今日成功支付的用户数(包含退款订单)。
+ 退款:今日发生退款的用户数。
- 设备数量:
+ 下单:今日成功下单的设备数(包含未支付订单和退款订单)。
+ 收款:今日成功支付的设备数(包含退款订单)。
+ 退款:今日发生退款的设备数。
**趋势图**
`趋势图`栏目中以`天维度``月维度``季维度``年维度`进行趋势统计。可以直观的看到收入的增长趋势。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A101-03.png"/>
</div>
### 转换漏斗分析
可以为您分析指定时间段的支付转化率,同时展示支付转化率趋势图。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A102.png"/>
</div>
**名词解释:**
- 活跃设备数:包含未登录和已登录的用户数量
- 活跃用户数:登录用户的数量
- 支付用户数:至少有一笔成功支付订单的用户
- 用户转化率:用户转化率=活跃用户数/活跃设备数
- 支付转化率:支付转化率=支付用户数/活跃用户数
### 价值用户排行
可以为您快速筛选高价值用户,高复购率用户。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A103.png"/>
</div>
### 订单明细
可以搜索、查看订单详情
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A104.png"/>
</div>
## 注意事项@tips
### 微信公众号@tips-wxpay-jsapi
h5的路由模式必须配置为 `history`,因为微信公众号登录的回调地址不支持 `hash` 模式。
同时微信公众号开发调试比较麻烦,麻烦在于网页授权需要添加域名白名单,用localhost或用ip访问本地是无法获取到微信的code的,这样也就无法获取openid,导致无法支付。
操作步骤
- 1、手机和电脑连接在同一个局域网(路由器WiFi下)
- 2、查看自己电脑的局域网ip地址,比如为192.168.1.8
- 3、假设你的线上域名是(必须要有自己的域名)www.abc.com 则设置 test.abc.com 先解析到你的前端托管域名上(为了让微信验证域名通过,因为验证域名时,需要上传微信指定的文件到你的前端托管)。
- 4、进入公众号后台,设置与开发 -> 公众号设置 -> 设置网页授权域名,添加 test.abc.com
- 5、成功添加后,再重新设置 test.abc.com 解析到你电脑的局域网ip,如192.168.1.8
- 6、过一段时间(大概20分钟后,更换域名解析生效需要时间,这20分钟内千万不要再去访问http://test.abc.com)
- 7、20分钟后,访问 http://test.abc.com 此时就等于访问了 http://192.168.1.8,这样你的手机就用 http://test.abc.com 来访问你的项目
- 8、可以正常获取到openid了,就可以正常进行本地微信公众号支付测试了(不然每次都要上传到服务器测试)。
当用自定义域名时,还需要在项目根目录添加 `vue.config.js` 文件,内容如下:
```js
module.exports = {
devServer: {
disableHostCheck: true, // 忽略域名检查
port: 80, // 设置80端口为项目启动端口
}
}
```
### APP支付@tips-app
APP支付除了配置uni-pay的支付配置外,还需要打包时添加支付模块,如下图所示。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-8.png"/>
</div>
同时,还需要打自定义基座(包名需要和开放平台下填写的一致),且你在开放平台下的这个应用必须通过审核才可以。(比如微信开放平台下的APP应用显示通过审核才可以)
## 全局错误码@errorcode
| 错误模块 | 错误码 | 说明 |
|---------|-------------|---------------------------|
| 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 | 证书错误,请检查支付证书 |
返回值示例
```json
{
"errMsg": "支付单号(out_trade_no)不能为空",
"errCode": 51001,
"errSubject": "uni-pay"
}
```
## 常见问题@question
### 发起支付时报数据库表不存在
支付插件需要创建支付相关的表后才能正常运行。[查看相关的数据库表](#database)
### 支付账号如何申请
本插件对接的支付渠道是微信和支付宝官方渠道
**微信支付**
申请地址 [https://pay.weixin.qq.com/index.php/apply/applyment_home/guide_normal](https://pay.weixin.qq.com/index.php/apply/applyment_home/guide_normal)
申请指引 [https://pay.weixin.qq.com/static/applyment_guide/applyment_index.shtml](https://pay.weixin.qq.com/static/applyment_guide/applyment_index.shtml)
**支付宝**
申请地址 [https://open.alipay.com](https://open.alipay.com)
申请指引 [https://opendocs.alipay.com/common/02asmu](https://opendocs.alipay.com/common/02asmu)
**注意**
支付账号申请需要企业资质(个体工商户也可以,但不可以是个人资质,需要有营业执照,银行对公账户)。
### 如何获得插件需要的密钥参数@get-config-help
**微信支付**
[微信支付参数和证书生成教程](https://docs.qq.com/doc/DWUpGTW1kSUdpZGF5)
- pfx:微信支付v2需要用到的证书,是一个后缀名为`.p12`的文件,如果你的`.p12`文件不是`apiclient_cert.p12`,则将它改名成`apiclient_cert.p12`,并复制到 `uni-config-center/uni-pay/wxpay/` 目录下
- appCertPath:微信支付v3需要用到的证书,是一个名为`apiclient_cert.pem`的文件,将它复制到 `uni-config-center/uni-pay/wxpay/` 目录下
- appPrivateKeyPath:微信支付v3需要用到的证书,是一个名为`apiclient_key.pem`的文件,将它复制到 `uni-config-center/uni-pay/wxpay/` 目录下
**支付宝**
[支付宝支付证书生成教程](https://docs.qq.com/doc/DWVBlVkZ1Z21SZFpS)
- privateKey:支付宝商户私钥
- appCertPath:支付宝商户公钥路径,是一个后缀名为`appCertPublicKey.crt`的文件,将它复制到 `uni-config-center/uni-pay/alipay/` 目录下
- alipayPublicCertPath:支付宝商户公钥路径,是一个后缀名为`alipayCertPublicKey_RSA2.crt`的文件,将它复制到 `uni-config-center/uni-pay/alipay/` 目录下
- alipayRootCertPath:支付宝根证书路径,是一个后缀名为`alipayRootCert.crt`的文件,将它复制到 `uni-config-center/uni-pay/alipay/` 目录下
### 微信小程序真机报fail url not in domain list错误@question-mp-weixin-domain
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-32.png)
这是由于云开发的域名没有添加到微信小程序域名白名单导致的,需要去微信小程序后台,添加以下域名到微信小程序域名白名单
```
https://api.next.bspapp.com;https://api.bspapp.com;https://tcb-api.tencentcloudapi.com;
```
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-31.png"/>
</div>
**添加完域名后,一定要重启微信开发者工具,然后去手机微信里删除最近使用的小程序(这一步很关键),最后重新扫二维码进入小程序。**
### 支付宝小程序云执行微信v2退款接口失败,报Error: unsupported, POST https://api.mch.weixin.qq.com/secapi/pay/refund -1@question-alipay-weixin-v2-refund
有两个方案可以解决
方案一:使用微信支付v3版本
方案二:将云函数的node版本切换成node16(支付宝小程序云默认是node18,而node18不再支持微信支付v2证书pfx的加密算法导致的)
# uni-pay 2
> 本文档适用于客户端为 `uni-app` 且 `uni-pay 2.0.0` 及以上版本,需 HBuilderX 3.6.5 及以上版本。旧版本文档请访问:[uni-pay 1.x 文档](./uni-pay-common.md)
> 若客户端为 `uni-app-x` 则请访问:[uni-pay-x 文档](./uni-app-x.md)
## 简介@introduction
支付,重要的变现手段,但开发复杂。在不同端,对接微信支付、支付宝等渠道,前端后端都要写不少代码。
涉及金额可不是小事,生成业务订单、获取收银台、发起支付、支付状态查询、支付异步回调、失败处理、发起退款、退款状态查询、支付统计...众多环节,代码量多,出错率高。
为什么不能有一个开源的、高质量的项目?即可以避免大家重复开发,又可以安心使用,不担心自己从头写产生Bug。
`uni-pay`应需而生。
之前`uni-pay 1.x`版本,仅是一个公共模块,它让开发者无需研究支付宝、微信等支付平台的后端开发、无需为它们编写不同代码,拿来即用,屏蔽差异。
但开发者还是需要自己编写前端页面和云函数,还是有一定的开发难度和工作量的,特别对于新手来说,门槛高、易出错。
`uni-pay 2.0` 起,补充了前端页面和云对象,让开发者开箱即用。
**注意:`uni-pay 2` 仍内置了uni-pay公共模块,向下兼容`uni-pay 1.x`,即从`uni-pay 1.x`可以一键升级到`uni-pay 2.x`,且不会对你的老项目造成影响。**
开发者在项目中引入 `uni-pay` 后,微信支付、支付宝支付等功能无需自己再开发。由于源码的开放性和层次结构清晰,有二次开发需求也很方便调整。
> 插件市场地址:[https://ext.dcloud.net.cn/plugin?name=uni-pay](https://ext.dcloud.net.cn/plugin?name=uni-pay)
> 代码仓库地址:[https://gitcode.net/dcloud/uni-pay.git](https://gitcode.net/dcloud/uni-pay.git)
**线上体验地址**
注意:线上体验地址用的是阿里云免费版,免费版请求次数有限,如请求失败为正常现象,可直接导入示例项目绑定自己的空间体验。
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/https___hellounipay.dcloud.net.cn_.png)
`uni-pay` 的功能包括:
- 页面
+ 支付收银台组件(让用户选择付款渠道) [组件详情](#uni-pay-component)
+ 支付成功结果页(可配置uni-ad广告,增加开发者收益)[uni-AD 广告联盟](https://uniad.dcloud.net.cn/login)
- 云对象([uni-pay-co](#uni-pay-co)
+ 微信支付
+ 微信APP支付
+ 微信小程序支付
+ 微信公众号支付
+ 微信手机外部浏览器H5支付
+ 微信PC扫码支付
+ 支付宝支付
+ 支付宝APP支付
+ 支付宝小程序支付
+ 支付宝手机外部浏览器H5支付(支持在微信APP的H5页面中使用支付宝支付)
+ 支付宝PC扫码支付
+ 通用接口
+ 支付异步回调
+ 查询订单
+ 发起退款
+ 查询退款
+ 关闭订单
+ 获取当前支持的支付方式
+ 获取当前支付用户的openid
+ ios内购支付
+ 微信小程序虚拟支付
+ 代币充值
+ 道具直购
- 支付统计(内置于uni-admin的支付统计中)
+ 收款趋势
+ 转换漏斗分析
+ 价值用户排行
+ 订单明细
## uni-pay组成@catalogue
uni-pay云端一体模板,包含前端页面、云对象、云端公共模块、uni-config-center配置、opendb数据表等内容。以及内置于uni-admin的支付统计报表。
### uni-pay的uni_modules
uni-pay的[uni_modules](https://uniapp.dcloud.net.cn/plugin/uni_modules.html)中包含了前端页面、云对象和公共模块,目录结构如下:
```
├─uni_modules 存放[uni_module](https://uniapp.dcloud.net.cn/plugin/uni_modules.html)规范的插件。
│ ├─其他module
│ └─uni-pay
│ ├─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收银台弹窗组件
│ │ └─uni-pay.vue
│ ├─js_sdk js sdk目录
│ │ └─js_sdk.js
│ ├─pages 页面目录
│ │ └─success
│ │ └─success.js 支付成功结果页
│ ├─static 静态资源目录
│ ├─changelog.md 更新日志
│ ├─package.json 包管理文件
│ └─readme.md 插件自述文件
```
完整的uni-app项目目录结构[另见](https://uniapp.dcloud.net.cn/frame?id=%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84)
### uni-pay的uni-config-center配置
支付配置不在插件目录中,统一存放在 `uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js` [查看支付配置介绍](#config)
### uni-pay的opendb数据表@database
支付插件需要创建以下表后才能正常运行,可以右键 `database` 目录,初始化数据库功能来创建表。
- 支付订单表 [uni-pay-orders](https://gitee.com/dcloud/opendb/blob/master/collection/uni-pay-orders/collection.json)
## 示例项目运行教程@rundemo
在对接自己的项目之前,建议先跑通示例项目,能跑通示例项目,代表你的配置和证书一定是正确的,然后再将`uni-pay`集成到你自己的项目中。
1. 从插件市场导入`uni-pay`示例项目。[前往插件市场](https://ext.dcloud.net.cn/plugin?name=uni-pay)
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-9.png)
2. 打开`uni-pay`配置文件,配置文件地址: `uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js` [查看支付配置介绍](#config)
3. 上传公共模块 `uni-config-center`(右键,上传公共模块,每次修改了支付配置,都需要重新上传此模块才会生效)
4. 上传公共模块 `uni-pay`(右键,上传公共模块)
5. 上传云对象 `uni-pay-co`(右键,上传部署。当然对uniCloud目录点右键批量上传也可以)
6. 数据库初始化
![](https://web-ext-storage.dcloud.net.cn/unicloud/uni-pay/462.png)
![](https://web-ext-storage.dcloud.net.cn/unicloud/uni-pay/463.png)
7. 运行启动项目,**在HBuilderX的运行控制台里选择使用云端云函数环境**
**注意:测试支付回调必须选择云端云函数环境**
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-29.png)
8. 前端页面里点击唤起收银台支付,如果可以正常支付,代表示例项目运行成功,可以开始对接自己的项目了。 [对接自己项目](#install)
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-30.png)
## uni-pay的config-center配置@config
开发者在微信和支付宝的支付后台,需要申请开通支付服务,成功后会得到各种凭据,这些凭据要配置在uni-pay的配置中。
### 完整支付配置示例@config-demo
这里是微信、支付宝全平台支付配置样例。如果只使用部分支付方式,后续有专门的分渠道章节。
配置文件在 `uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js`
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-3.png)
```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": "", // 支付宝商户私钥
"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": "", // 支付宝商户私钥
"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": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
}
},
// ios内购相关
"appleiap" :{
// ios内购支付
"app": {
"password": "", // App 专用共享密钥,App 专用共享密钥是用于接收此 App 自动续期订阅收据的唯一代码。如果您要将此 App 转让给其他开发者或不想公开主共享密钥,建议使用 App 专用共享密钥。非自动续订场景不需要此参数
"timeout": 10000, // 请求超时时间,单位:毫秒
"sandbox": false, // 是否是沙箱环境(本地调试ios走的是沙箱环境,故要设置为true,正式发布后,需要设置为false)
}
},
// 微信虚拟支付
"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, // 是否是沙箱环境(注意:沙箱环境异步回调可能有延迟,建议直接正式环境测试)
}
}
}
```
如果你对支付配置中各参数如何获取有疑问,请点击[获取支付配置帮助](#get-config-help)
**注意**
微信支付同时支持V2版本和V3版本
以微信小程序支付为例
**V2版本**
```js
"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版本**
```js
"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
```js
"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版本
},
```
### 支付回调配置@config-notify
对应支付配置的节点是 `notifyUrl`
**示例**
```js
// 统一 - 支付回调地址,格式为 "服务空间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如何获取?**
[点击此处进入服务空间列表](https://unicloud.dcloud.net.cn/home),找到你项目用的服务空间,复制其服务空间ID
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-4.png)
**URL化地址如何获取?**
[点击此处进入服务空间列表](https://unicloud.dcloud.net.cn/home),找到你项目用的服务空间,点击服务空间名称进入空间详情页,点击左侧菜单【云函数/云对象】- 点击【uni-pay-co】云对象右侧的【详情】按钮
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-5.png"/>
</div>
进入详情后,点下面的【复制路径】,复制的内容就是【URL化地址】
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-6.png"/>
</div>
### 分渠道支付配置示例@config-part
上面的配置样例是微信和支付宝全端配置样例。如果只使用一种支付场景,比如微信公众号里的微信支付,可以看下面章节的分渠道支付配置样例。
#### 微信APP支付@config-wxpay-app
对应支付配置的节点是 `wxpay.app`
```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",
},
// 微信支付相关
"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版本
},
},
}
```
#### 微信小程序支付@config-wxpay-mp
对应支付配置的节点是 `wxpay.mp`
```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",
},
// 微信支付相关
"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版本
},
},
}
```
#### 微信公众号支付@config-wxpay-jsapi
对应支付配置的节点是 `wxpay.jsapi`
```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",
},
// 微信支付相关
"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版本
},
},
}
```
#### 微信手机外部浏览器H5支付@config-wxpay-mweb
对应支付配置的节点是 `wxpay.mweb`
```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",
},
// 微信支付相关
"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网站名称
}
}
},
},
}
```
#### 微信PC扫码支付@config-wxpay-native
对应支付配置的节点是 `wxpay.native`
```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",
},
// 微信支付相关
"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版本
},
},
}
```
#### 支付宝APP支付@config-alipay-app
对应支付配置的节点是 `alipay.app`
```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",
},
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - APP支付配置
"app": {
"appId": "", // 支付宝开放平台下应用的appid
"privateKey": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
},
}
}
```
#### 支付宝小程序支付@config-alipay-mp
对应支付配置的节点是 `alipay.mp`
```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",
},
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - 小程序支付配置
"mp": {
"appId": "", // 支付宝小程序appid
"privateKey": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
},
}
}
```
#### 支付宝手机外部浏览器H5支付@config-alipay-native-h5
对应支付配置的节点是 `alipay.native`(和PC扫码配置节点一样)
```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",
},
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - H5支付配置(包含:网站二维码、手机H5,需申请支付宝当面付接口权限)
"native": {
"appId": "", // 支付宝开放平台下应用的appid
"privateKey": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
}
}
}
```
#### 支付宝PC扫码支付@config-alipay-native-pc
对应支付配置的节点是 `alipay.native`
```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",
},
// 支付宝相关(加签方式选证书模式,加密算法选RSA2)
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - H5支付配置(包含:网站二维码、手机H5,需申请支付宝当面付接口权限)
"native": {
"appId": "", // 支付宝开放平台下应用的appid
"privateKey": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
}
}
}
```
#### ios内购支付@config-appleiap-app
对应支付配置的节点是 `appleiap.app`
```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",
},
// ios内购相关
"appleiap" :{
// ios内购支付
"app": {
"password": "", // App 专用共享密钥,App 专用共享密钥是用于接收此 App 自动续期订阅收据的唯一代码。如果您要将此 App 转让给其他开发者或不想公开主共享密钥,建议使用 App 专用共享密钥。非自动续订场景不需要此参数
"timeout": 10000, // 请求超时时间,单位:毫秒
"sandbox": false, // 是否是沙箱环境(本地调试ios走的是沙箱环境,故要设置为true,正式发布后,需要设置为false)
}
}
}
```
#### 微信小程序虚拟支付@config-wxpay-virtual-mp
配置文件在 `uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js`
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-3.png)
对应支付配置的节点是 `wxpay-virtual.mp`
```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",
},
// 微信虚拟支付
"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, // 是否是沙箱环境(注意:沙箱环境异步回调可能有延迟,建议直接正式环境测试)
}
}
}
```
## 集成到自己项目的教程@install
在对接自己的项目之前,建议先[跑通示例项目](#rundemo),能跑通示例项目,代表你的配置和证书一定是正确的,然后再将`uni-pay`集成到你自己的项目中。
### 安装插件
1. 从插件市场导入`uni-pay`插件到你自己的项目。[前往插件市场](https://ext.dcloud.net.cn/plugin?name=uni-pay)
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-10.png)
2. 复制你刚运行的示例项目中的`uni-pay`配置文件,配置文件地址: `uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js`到你的项目中 [查看支付配置介绍](#config)
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-3.png)
3. 上传公共模块 `uni-config-center`(右键,上传公共模块,每次修改了支付配置,都需要重新上传此模块才会生效)
4. 上传公共模块 `uni-pay`(右键,上传公共模块)
5. 上传云对象 `uni-pay-co`(右键,上传部署)
6. 数据库初始化
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-28.png)
7. 项目根目录`pages.json`添加`subPackages`分包页面配置(如果页面已自动配置,则可无视此步骤)
```js
"pages": [
...你的页面
],
"subPackages": [
{
"root": "uni_modules/uni-pay/pages",
"pages": [
{
"path": "success/success",
"style": {
"navigationBarTitleText": "支付成功",
"backgroundColor": "#F8F8F8"
}
},
{
"path": "ad-interactive-webview/ad-interactive-webview",
"style": {
"navigationBarTitleText": "ad",
"backgroundColor": "#F8F8F8"
}
}
]
}
],
```
8. 安装完成
### 前端页面集成@quickly-pages
打开你需要进行支付的页面,一般是业务订单提交之后的页面来展现收银台。
1. 该页面在 `template` 内放一个 `uni-pay` 组件标签,声明ref,然后调用组件的API。如下
注意:vue3下ref不可以等于组件名,因此这里 `ref="pay"` 而不能是 `ref="uniPay"`
```html
<template>
<view>
<button @click="open">唤起收银台支付</button>
<uni-pay ref="pay"></uni-pay>
</view>
</template>
```
2. 在script中编写代码,点击付款时执行方法:
```html
<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
}
}
},
methods: {
/**
* 发起支付(唤起收银台,如果只有一种支付方式,则收银台不会弹出来,会直接使用此支付方式)
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
open() {
this.order_no = `test`+Date.now(); // 模拟生成订单号
this.out_trade_no = `${this.order_no}-1`; // 模拟生成插件支付单号
// 打开支付收银台
this.$refs.pay.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, // 自定义数据(此参数不推荐使用,因为是前端传的,此参数可能会被伪造,建议通过order_no查询自己业务订单表来获取自定义业务数据)
});
}
}
}
</script>
```
### 云端支付回调集成@notify
当用户支付成功后,我们要给用户增加余额或者给业务订单标记支付成功,这些通过异步回调通知来实现的。
**提示:异步回调通知写在 `uni-pay-co/notify` 目录下,在此目录新建2个js文件,分别为 `recharge.js`、`goods.js` 文件,同时复制以下代码要你新建的2个js文件里。**
代码如下
```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` 参数
```js
// 打开支付收银台
this.$refs.pay.open({
type: "recharge", // 支付回调类型 recharge 代表余额充值(当然你可以自己自定义)
});
```
**注意:每次修改都需要重新上传云对象`uni-pay-co`**
#### 业务在uniCloud上@service-inside
如果你的业务在uniCloud上,那么可以使用方式一或方式二进行编写自定义回调逻辑。
**方式一:直接写数据库操作**
适用场景:简单数据库操作场景
以给用户充值余额为例,代码如下
```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,
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;
};
```
**方式二:直接调用其他云函数或云对象**
适用场景:业务较为复杂,需写在其他云函数或云对象里的场景。
调用其他云函数示例代码如下
```js
'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;
};
```
调用其他云对象示例代码如下
```js
'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上@service-outside
如果你的业务不在uniCloud上,如java或php写的后端服务,uni-pay也可以满足你的支付需求,你只需要使用回调方式三的http接口形式调用你自己系统的回调接口即可。
**方式三:使用 await uniCloud.httpclient.request 调用外部http接口**
适用场景:业务不在uniCloud上。
示例代码如下
```js
'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;
};
```
#### java解密示例代码
```java
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解密示例代码
```php
<?php
$key = '12345678901234561234567890123456'; // 必须是固定的32位(只支持数字、英文)
$encrypt = "es2aF7DWr169X4fvMnlKNg=="; // 待解密的内容
// 解密
$decrypt = openssl_decrypt(base64_decode($encrypt), 'aes-256-ecb', substr($key, 0, 32), OPENSSL_RAW_DATA);
echo $decrypt;
?>
```
### 运行启动
运行你的项目,进行支付的体验和测试。
## uni-pay组件介绍@uni-pay-component
#### 组件属性
| 属性名 | 说明 | 类型 | 默认值 | 可选值 |
|-----------------|-------------------------------|---------|--------|-------|
| adpid | uni-ad的广告位ID,若填写,则会在支付成功结果页展示广告(可以增加开发者广告收益) | string | - | - |
| returnUrl | 支付成功后,用户点击【查看订单】按钮时跳转的页面地址,如果不填写此属性,则没有【查看订单】按钮 | string | - | - |
| mainColor | 支付结果页主色调,默认支付宝小程序为#108ee9,其他端均为#01be6e | string | #01be6e | 见下 |
| mode | 收银台模式,插件会自动识别,也可手动传参,mobile 手机模式 pc 电脑模式 | string | 自动识别 | mobile、pc |
| logo | 当mode为PC时,展示的logo | string | /static/logo.png | - |
| height | 收银台高度 | string | 70vh | - |
**mainColor值参考:**
- 绿色系 #01be6e
- 蓝色系 #108ee9
- 咖啡色 #816a4e
- 粉红 #fe4070
- 橙黄 #ffac0c
- 橘黄 #ff7100
- 其他 可自定义
#### 组件事件
| 事件名 | 说明 | 参数 |
|-------------|---------------------|--------|
| success | 支付成功的回调 | res |
| cancel | 支付取消的回调 | res |
| fail | 支付失败的回调 | res |
| create | 创建支付订单时的回调(此时用户还未支付) | res |
#### 组件方法
通过 `let res = await this.$refs.pay.xxx();` 方式调用,详情调用方式参考下方的【前端完整示例代码】
| 方法名 | 说明 |
|---------------------------|---------------------|
| open | 发起支付 - 打开支付收银台弹窗 [查看详情](#create-order) |
| createOrder | 直接发起支付(无收银台) [查看详情](#create-order) |
| getOrder | 查询订单 [查看详情](#get-order) |
| refund | 发起退款(此接口需要权限才可以访问) [查看详情](#refund) |
| getRefund | 查询退款 [查看详情](#get-refund) |
| closeOrder | 关闭订单 [查看详情](#close-order) |
| getPayProviderFromCloud | 获取支持的支付供应商 [查看详情](#get-pay-provider-from-cloud) |
| getProviderAppId | 获取支付配置内的appid(主要用于获取微信公众号的appid,用以获取code) [查看详情](#get-provider-appid) |
| getOpenid | 根据code获取openid (主要用于微信公众号code换取openid) [查看详情](#get-openid) |
**前端完整示例代码**
```html
<template>
<view class="app">
<view>
<view class="label">支付单号:</view>
<view><input v-model="out_trade_no" /></view>
</view>
<view>
<view class="label">支付金额(单位分,100=1元):</view>
<view><input v-model.number="total_fee" /></view>
</view>
<button @click="open">唤起收银台支付</button>
<view class="tips">支付前,让用户自己选择微信还是支付宝</view>
<!-- #ifdef MP-WEIXIN || H5 || APP -->
<button @click="createOrder('wxpay')">直接发起微信支付</button>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY || H5 || APP -->
<button @click="createOrder('alipay')">直接发起支付宝支付</button>
<!-- #endif -->
<button @click="createQRcode('wxpay')">生成独立支付二维码</button>
<view class="tips">用于把生成的二维码放到自己写的页面中(组件不会弹窗,请从日志中查看二维码base64值)</view>
<button @click="getOrder">查询支付状态</button>
<!--
<button @click="refund">发起退款</button>
<view class="tips">发起退款需要admin权限,本示例未对接登录功能</view>
<button @click="getRefund">查询退款状态</button>
<button @click="closeOrder">关闭订单</button>
-->
<!-- #ifdef H5 -->
<button v-if="h5Env === 'h5-weixin'" @click="getWeiXinJsCode('snsapi_base')">公众号获取openid示例</button>
<!-- #endif -->
<!-- 统一支付组件,注意:vue3下ref不可以等于组件名,因此这里ref="pay" 而不能是 ref="uniPay" -->
<uni-pay ref="pay" :adpid="adpid" return-url="/pages/order-detail/order-detail" logo="/static/logo.png" @success="onSuccess" @create="onCreate"></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文件进行编写对应的回调逻辑即可)
//qr_code: true, // 是否强制使用扫码支付
openid:"", // 微信公众号需要
custom:{
a: "a",
b: 1
},
adpid: "1000000001", // uni-ad的广告位id
}
},
onLoad(options={}) {
if (options.code && options.state) {
// 获取微信公众号的openid
setTimeout(() => {
this.getOpenid({
provider: "wxpay",
code: options.code
});
}, 300);
}
},
methods: {
/**
* 发起支付(唤起收银台,如果只有一种支付方式,则收银台不会弹出来,会直接使用此支付方式)
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
open() {
this.order_no = `test`+Date.now();
this.out_trade_no = `${this.order_no}-1`;
// 打开支付收银台
this.$refs.pay.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, // 支付回调类型
qr_code: this.qr_code, // 是否强制使用扫码支付
openid: this.openid, // 微信公众号需要
custom: this.custom, // 自定义数据(此参数不推荐使用,因为是前端传的,此参数可能会被伪造,建议通过order_no查询自己业务订单表来获取自定义业务数据)
});
},
/**
* 发起支付(不唤起收银台,手动指定支付方式)
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
createOrder(provider){
this.order_no = `test`+Date.now();
this.out_trade_no = `${this.order_no}-1`;
// 发起支付
this.$refs.pay.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: this.qr_code, // 是否强制使用扫码支付
openid: this.openid, // 微信公众号需要
custom: this.custom, // 自定义数据(此参数不推荐使用,因为是前端传的,此参数可能会被伪造,建议通过order_no查询自己业务订单表来获取自定义业务数据)
});
},
/**
* 生成支付独立二维码(只返回支付二维码)
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
createQRcode(provider){
this.order_no = `test`+Date.now();
this.out_trade_no = `${this.order_no}-1`;
// 发起支付
this.$refs.pay.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, // 是否强制使用扫码支付
cancel_popup: true, // 配合qr_code:true使用,是否只生成支付二维码,没有二维码弹窗
openid: this.openid, // 微信公众号需要
custom: this.custom, // 自定义数据(此参数不推荐使用,因为是前端传的,此参数可能会被伪造,建议通过order_no查询自己业务订单表来获取自定义业务数据)
});
},
// 查询支付状态
async getOrder() {
let res = await this.$refs.pay.getOrder({
out_trade_no: this.out_trade_no, // 插件支付单号
await_notify: true
});
if (res) {
let obj = {
"-1": "已关闭",
"1": "已支付",
"0": "未支付",
"2": "已部分退款",
"3": "已全额退款"
};
uni.showToast({
title: obj[res.status] || res.errMsg,
icon: "none"
});
}
},
// 发起退款
async refund() {
let res = await this.$refs.pay.refund({
out_trade_no: this.out_trade_no, // 插件支付单号
});
if (res) {
uni.showToast({
title: res.errMsg,
icon: "none"
});
}
},
// 查询退款状态
async getRefund() {
let res = await this.$refs.pay.getRefund({
out_trade_no: this.out_trade_no, // 插件支付单号
});
if (res) {
uni.showModal({
content: res.errMsg,
showCancel: false
});
}
},
// 关闭订单
async closeOrder() {
let res = await this.$refs.pay.closeOrder({
out_trade_no: this.out_trade_no, // 插件支付单号
});
if (res) {
uni.showModal({
content: res.errMsg,
showCancel: false
});
}
},
// 获取公众号code
async getWeiXinJsCode(scope="snsapi_base") {
let res = await this.$refs.pay.getProviderAppId({
provider: "wxpay",
provider_pay_type: "jsapi"
});
if (res.appid) {
let appid = res.appid;
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) {
let res = await this.$refs.pay.getOpenid(data);
if (res) {
this.openid = res.openid;
uni.showToast({
title: "已获取到openid,可以开始支付",
icon: "none"
});
}
},
// 监听事件 - 支付订单创建成功(此时用户还未支付)
onCreate(res){
console.log('create: ', res);
// 如果只是想生成支付二维码,不需要组件自带的弹窗,则在这里可以获取到支付二维码 qr_code_image
},
// 监听事件 - 支付成功
onSuccess(res){
console.log('success: ', res);
if (res.user_order_success) {
// 代表用户已付款,且你自己写的回调成功并正确执行了
} else {
// 代表用户已付款,但你自己写的回调执行成功(通常是因为你的回调代码有问题)
}
}
},
computed: {
h5Env(){
// #ifdef H5
let ua = window.navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) == 'micromessenger' && (ua.match(/miniprogram/i) == 'miniprogram')) {
// 微信小程序
return "mp-weixin";
}
if (ua.match(/MicroMessenger/i) == 'micromessenger') {
// 微信公众号
return "h5-weixin";
}
if (ua.match(/alipay/i) == 'alipay' && ua.match(/miniprogram/i) == 'miniprogram') {
return "mp-alipay";
}
if (ua.match(/alipay/i) == 'alipay') {
return "h5-alipay";
}
// 外部 H5
return "h5";
// #endif
}
},
}
</script>
<style lang="scss" scoped>
.app{
padding: 30rpx;
}
input {
border: 1px solid #f3f3f3;
padding: 10rpx;
}
button {
margin-top: 20rpx;
}
.label{
margin: 10rpx 0;
}
.tips{
margin-top: 20rpx;
font-size: 24rpx;
color: #565656;
}
</style>
```
## 云对象(uni-pay-co)介绍@uni-pay-co
### 目录结构@cloudobject-catalogue
```
├─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 云对象入口文件
```
### 公共响应参数@co-public-response
`uni-pay-co` 所有api返回值均满足[uniCloud响应体规范](https://uniapp.dcloud.net.cn/uniCloud/cf-functions.html#resformat)
返回值示例
```js
{
errCode: 0, // 错误码,详见错误码列表
errMsg: '', // 错误信息,uni-pay-co会自动根据客户端语言对错误信息进行国际化
// ...其余参数
}
```
## API列表@api
uni-pay前端组件和uni-pay-co云对象的方法是一样的。通常情况下,前端直接调用uni-pay组件内的方法即可(组件内会自动调用云对象内的API,无需再手动调用云对象内的API)
以下是介绍这些api。
| API | 说明 |
|-----------------|---------------------|
| uniPayCo.createOrder | 创建支付 [查看详情](#create-order) |
| uniPayCo.getOrder | 查询订单 [查看详情](#get-order) |
| uniPayCo.refund | 发起退款(此接口需要权限才可以访问) [查看详情](#refund) |
| uniPayCo.getRefund | 查询退款 [查看详情](#get-refund)|
| uniPayCo.closeOrder | 关闭订单 [查看详情](#close-order) |
| uniPayCo.getPayProviderFromCloud | 获取支持的支付供应商 [查看详情](#get-pay-provider-from-cloud) |
| uniPayCo.getProviderAppId | 获取支付配置内的appid(主要用于获取微信公众号的appid,用以获取code) [查看详情](#get-provider-appid) |
| uniPayCo.getOpenid | 根据code获取openid (主要用于微信公众号code换取openid) [查看详情](#get-openid) |
### 创建支付@create-order
**支付组件方法形式(收银台弹窗模式)(推荐)**
`open``createOrder`参数是一致的,唯一区别是`open`会打开收银台,而`createOrder`不带收银台,直接调用支付。
`open`如果只有一种支付方式,比如微信小程序内只能用微信支付,则不会弹收银台,而是直接调用支付。
```js
this.$refs.pay.open({
total_fee: 1, // 支付金额,单位分 100 = 1元
type: "recharge", // 支付回调类型
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
description: "uniCloud个人版包月套餐", // 支付描述
});
```
**直接跳收银台页面模式(推荐)**
与弹窗模式的区别是:跳页面模式是通过 `uni.navigateTo` 直接跳到收银台页面,而弹窗模式是在原页面弹出收银台。
```js
let options = {
total_fee: 1, // 支付金额,单位分 100 = 1元
type: "recharge", // 支付回调类型
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
description: "uniCloud个人版包月套餐", // 支付描述
};
let optionsStr = encodeURI(JSON.stringify(options));
uni.navigateTo({
url:`/uni_modules/uni-pay/pages/pay-desk/pay-desk?options=${optionsStr}`
});
```
收银台页面源码在 `/uni_modules/uni-pay/pages/pay-desk/pay-desk`
如果你想要自定义收银台样式,建议复制该页面到你的项目pages目录,如`/pages/pay-desk/pay-desk`,然后在复制的页面上进行修改样式,同时跳转到自定义收银台的代码如下:
```js
let options = {
total_fee: 1, // 支付金额,单位分 100 = 1元
type: "recharge", // 支付回调类型
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
description: "uniCloud个人版包月套餐", // 支付描述
};
let optionsStr = encodeURI(JSON.stringify(options));
uni.navigateTo({
url:`/pages/pay-desk/pay-desk?options=${optionsStr}`
});
```
**支付组件方法形式(不带收银台)**
不带收银台时,provider参数为必传项,代表支付供应商
```js
this.$refs.pay.createOrder({
provider: "wxpay", // 支付供应商
total_fee: 1, // 支付金额,单位分 100 = 1元
type: "recharge", // 支付回调类型
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
description: "uniCloud个人版包月套餐", // 支付描述
});
```
**云对象接口形式**
```js
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。
整个逻辑是这样的:
**以用户购买商品付款为例**
- 1、前端用户登录(非本插件功能)
- 2、前端用户购买商品并下单,云端生成你自己写的业务系统商品订单信息,并返回订单号 `order_no` 给前端(非本插件功能)
- 3、用上一步云端返回的 `order_no` 调用插件的[创建支付](#create-order)API(type参数的值写 `goods`),发起真正的支付功能(本插件功能)
- 4、用户支付成功后,云端接收第三方支付发过来的异步回调请求,云端校验请求合法性后,执行商品付款成功异步回调逻辑(即执行 `goods` 回调),同时标记订单为已付款(本插件功能)
- 5、前端监听到付款成功事件,跳转到支付成功页,并展示广告(本插件功能)
- 6、用户点击查看订单,跳转到你自己写的业务系统商品订单详情页(本插件功能)
- 7、完成
**以用户充值余额为例**
- 1、前端用户登录(非本插件功能)
- 2、前端用户提交充值余额的数量,云端生成你自己写的业务系统充值订单信息,并返回订单号 `order_no` 给前端(非本插件功能)
- 3、用上一步云端返回的 `order_no` 调用插件的[创建支付](#create-order)API(type参数的值写 `recharge`),发起真正的支付功能(本插件功能)
- 4、用户支付成功后,云端接收第三方支付发过来的异步回调请求,云端校验请求合法性后,执行余额充值付款成功异步回调逻辑(即执行 `recharge` 回调),同时标记订单为已付款(本插件功能)
- 5、前端监听到付款成功事件,跳转到支付成功页,并展示广告(本插件功能)
- 6、用户点击查看订单,跳转到你自己写的业务系统充值订单详情页(本插件功能)
- 7、完成
### 查询订单@get-order
**支付组件方法形式(推荐)**
```js
await this.$refs.pay.getOrder({
out_trade_no: "2022102701100010100101001", // 插件支付单号
await_notify: true, // 是否需要等待异步通知执行完成,若为了响应速度,可以设置为false,若需要等待异步回调执行完成,则设置为true
});
```
**云对象接口形式**
```js
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 | 支付订单完整信息 |
### 发起退款@refund
**注意**
发起退款默认需要admin权限(基于uni-id用户体系登录),否则会报权限不足或缺少token。[查看uni-id介绍](https://uniapp.dcloud.net.cn/uniCloud/uni-id/summary.html)
当然,你也可以修改`uni-pay-co/config/permission.js`这个文件内的权限规则。
**支付组件方法形式(推荐)**
```js
await this.$refs.pay.refund({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
```
**云对象接口形式**
```js
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 | 第三方供应商返回的结果 |
### 查询退款@get-refund
**支付组件方法形式(推荐)**
```js
await this.$refs.pay.getRefund({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
```
**云对象接口形式**
```js
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 | 支付订单信息 |
### 关闭订单@close-order
一般情况下,无需调用此方法去主动关闭订单(订单若未支付,则会在一段时间后自动关闭),但你有需要主动关闭订单的场景时,可以使用此api来主动关闭订单。(只有未支付的订单才可以主动关闭)
注意:
1. 微信支付订单生成后不能马上调用关单接口,最短调用时间间隔为5分钟
2. 支付宝订单生成后需用户进入过输入密码的页面,才能调用关单接口(无需间隔5分钟)
**支付组件方法形式(推荐)**
```js
await this.$refs.pay.closeOrder({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
```
**云对象接口形式**
```js
await uniPayCo.closeOrder({
out_trade_no: "2022102701100010100101001", // 插件支付单号
});
```
**参数说明**
| 参数名 | 类型 | 必填 | 说明 |
|-----------------|---------|------|---------------------------|
| out_trade_no | string | 是 | 插件订单号 |
**返回值**
| 参数名 | 类型 | 说明 |
|-----------------|---------|---------------------------|
| result | object | 第三方供应商返回的结果 |
### 获取支持的支付供应商@get-pay-provider-from-cloud
一般情况下,无需调用此api,`uni-pay` 组件内部已自动调用此api。
**支付组件方法形式(推荐)**
```js
await this.$refs.pay.getPayProviderFromCloud();
```
**云对象接口形式**
```js
await uniPayCo.getPayProviderFromCloud();
```
**参数说明**
该API无参数
**返回值**
| 参数名 | 类型 | 说明 |
|-----------------|---------|---------------------------|
| wxpay | boolean | 是否支持微信支付 |
| alipay | boolean | 是否支持支付宝支付 |
| provider | array&lt;string&gt; | 支持哪些支付供应商,如["wxpay","alipay"] |
### 获取支付配置内的appid@get-provider-appid
```js
await this.$refs.pay.getProviderAppId({
provider: "wxpay",
provider_pay_type: "jsapi",
});
```
**云对象接口形式**
```js
await uniPayCo.getProviderAppId({
provider: "wxpay",
provider_pay_type: "jsapi",
});
```
**参数说明**
| 参数名 | 类型 | 必填 | 说明 |
|-----------------|---------|------|---------------------------|
| provider | string | 是 | 支付供应商 如 wxpay alipay |
| provider_pay_type | string | 是 | 支付供应商 如 jsapi |
**返回值**
| 参数名 | 类型 | 说明 |
|-----------------|---------|---------------------------|
| appid | string | appid |
### 根据code获取openid@get-openid
一般用于微信公众号根据网页授权回调返回的code获取用户openid
**注意**
小程序不需要调用此方法,组件内部已自动静默获取openid
```js
await this.$refs.pay.getOpenid({
provider: "wxpay",
code: options.code
});
```
**云对象接口形式**
```js
await uniPayCo.getOpenid({
provider: "wxpay",
code: options.code
});
```
**参数说明**
| 参数名 | 类型 | 必填 | 说明 |
|-----------------|---------|------|---------------------------|
| provider | string | 是 | 支付供应商 如 wxpay alipay |
| code | string | 是 | 微信公众号网页授权回调返回的code |
**返回值**
| 参数名 | 类型 | 说明 |
|-----------------|---------|---------------------------|
| openid | string | openid |
### ios内购支付@appleiap
**概述**
IAP 全称:In-App Purchase,是指苹果 App Store 的应用内购买,是苹果为 App 内购买虚拟商品或服务提供的一套交易系统。
适用范围:在 App 内需要付费使用的产品功能或虚拟商品/服务,如游戏道具、电子书、音乐、视频、订阅会员、App的高级功能等需要使用 IAP,而在 App 内购买实体商品(如淘宝购买手机)或者不在 App 内使用的虚拟商品(如充话费)或服务(如滴滴叫车)则不适用于 IAP。
简而言之,苹果规定:适用范围内的虚拟商品或服务,必须使用 IAP 进行购买支付,不允许使用支付宝、微信支付等其它第三方支付方式(包括Apple Pay),也不允许以任何方式(包括跳出App、提示文案等)引导用户通过应用外部渠道购买。
**示例代码**
注意:只能使用uni-pay支付组件发起
```js
// 发起ios内购支付
this.$refs.pay.createOrder({
provider: "appleiap", // 支付供应商(这里固定未appleiap,代表ios内购支付)
order_no: "20221027011000101001010", // 业务系统订单号
out_trade_no: "2022102701100010100101001", // 插件支付单号
type: "appleiap", // 支付回调类型(可自定义,建议填写appleiap)
productid: "io_dcloud_hellouniapp_pay_like6", // ios内购产品id(仅ios内购生效)
custom: {}, // 自定义数据(此参数不推荐使用,因为是前端传的,此参数可能会被伪造,建议通过order_no查询自己业务订单表来获取自定义业务数据)
});
```
[点击查看ios内购注意事项](#tips-appleiap)
完整ios内购支付示例代码
```html
<template>
<view class="content">
<view class="uni-list">
<radio-group @change="applePriceChange">
<label class="uni-list-cell" v-for="(item, index) in productList" :key="index">
<radio :value="item.productid" :checked="item.checked" />
<view class="price">{{item.title}} {{item.price}}元</view>
</label>
</radio-group>
</view>
<view class="uni-padding-wrap">
<button class="btn-pay" @click="createOrder" :loading="loading" :disabled="disabled">立即支付</button>
</view>
<!-- 统一支付组件,注意:vue3下ref不可以等于组件名,因此这里ref="pay" 而不能是 ref="uniPay" -->
<uni-pay ref="pay" :debug="true" :adpid="adpid" return-url="/pages/order-detail/order-detail" @mounted="onMounted" @success="onSuccess"></uni-pay>
</view>
</template>
<script>
export default {
data() {
return {
order_no: "", // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: "", // 插件支付单号
adpid: "1000000001", // uni-ad的广告位id
loading: false, // 支付按钮是否在loading中
disabled: true, // 支付按钮是否禁用
productid: "", // 用户选择的商品id
// 出售的ios内购商品列表
productList: [
{
"description": "为DCloud提供的免费软件进行赞助",
"price": 1,
"productid": "io_dcloud_hellouniapp_pay_like1",
"title": "赞赏"
},
{
"description": "为DCloud提供的免费软件进行赞助",
"price": 6,
"productid": "io_dcloud_hellouniapp_pay_like6",
"title": "赞赏"
}
],
}
},
onLoad: function() {
},
onShow() {
if (this.$refs.pay && this.$refs.pay.appleiapRestore) {
// ios内购支付漏单重试
this.$refs.pay.appleiapRestore();
}
},
onUnload() {},
methods: {
// 支付组件加载完毕后执行
onMounted(insideData){
this.init();
},
// 初始化
async init() {
this.productList[0].checked = true;
this.productid = this.productList[0].productid;
this.disabled = false;
if (this.$refs.pay && this.$refs.pay.appleiapRestore) {
// ios内购支付漏单重试
this.$refs.pay.appleiapRestore();
}
},
/**
* 发起支付
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
createOrder(){
this.order_no = `test`+Date.now();
this.out_trade_no = this.order_no;
// 发起支付
this.$refs.pay.createOrder({
provider: "appleiap", // 支付供应商(这里固定未appleiap,代表ios内购支付)
order_no: this.order_no, // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: this.out_trade_no, // 插件支付单号
type: "appleiap", // 支付回调类型(可自定义,建议填写appleiap)
productid: this.productid, // ios内购产品id(仅ios内购生效)
custom: {}, // 自定义数据(此参数不推荐使用,因为是前端传的,此参数可能会被伪造,建议通过order_no查询自己业务订单表来获取自定义业务数据)
});
},
// 监听事件 - 支付成功
onSuccess(res){
console.log('success: ', res);
if (res.user_order_success) {
// 代表用户已付款,且你自己写的回调成功并正确执行了
} else {
// 代表用户已付款,但你自己写的回调执行失败(通常是因为你的回调代码有问题)
}
},
// 监听-多选框选中的值改变
applePriceChange(e) {
this.productid = e.detail.value;
},
}
}
</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>
```
### 微信小程序虚拟支付@wxpay-virtual
**概述**
微信规定上架**短剧**类目的小程序必须使用微信小程序虚拟支付,不可以使用原先的微信支付
**注意**
1. 微信小程序虚拟支付只有短剧类目的小程序才能开通
2. 微信小程序虚拟支付不支持ios系统
3. 微信小程序虚拟支付有较高的手续费(已知目前为10% ~ 20%),由微信官方收取,非uni-pay收取(手续费多少跟使用uni-pay无关)
**如何开户?**
- [微信虚拟支付官方文档](https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/virtual-payment.html#_1-%E4%BA%A7%E5%93%81%E4%BB%8B%E7%BB%8D)
**示例代码**
注意:只能使用uni-pay支付组件发起
#### 代币充值示例@short-series-coin
```vue
<template>
<view class="app">
<view>充值代币数量(建议设置1 人民币 = 100 代币):</view>
<view><input class="input" v-model.number="buy_quantity" /></view>
<button class="button" @click="createOrder('wxpay-virtual')">发起微信虚拟支付</button>
<uni-pay ref="pay" @success="onSuccess" @fail="onFail"></uni-pay>
</view>
</template>
<script>
export default {
data() {
return {
buy_quantity: 1,
}
},
methods: {
/**
* 发起支付(不唤起收银台,手动指定支付方式)
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
createOrder() {
let order_no = `test` + Date.now();
let out_trade_no = `${order_no}-1`;
// 发起支付
this.$refs.pay.createOrder({
provider: "wxpay-virtual", // 支付供应商 固定为 wxpay-virtual
order_no: order_no, // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: out_trade_no, // 插件支付单号
description: "支付描述", // 支付描述
type: "wxpay-virtual-test", // 支付回调类型
// 微信虚拟支付专属字段
wxpay_virtual: {
mode: "short_series_coin", // 模式 short_series_coin 代币充值 short_series_goods 道具直购
buy_quantity: this.buy_quantity, // 购买代币数量
},
// 自定义数据
custom: {
user_id: "001", // 业务系统用户id
},
});
},
// 监听事件 - 支付成功
onSuccess(res) {
console.log('success: ', res);
if (res.user_order_success) {
// 代表用户已付款,且你自己写的回调成功并正确执行了
} else {
// 代表用户已付款,但你自己写的回调执行失败(通常是因为你的回调代码有问题)
}
},
onFail(err) {
console.log('err: ', err)
let errMsg = err.errMsg;
if (errMsg === "requestVirtualPayment:fail INVALID_PLATFORM") {
errMsg = "苹果手机不支持微信虚拟支付";
} else if (errMsg === "requestVirtualPayment:fail no permission") {
errMsg = "微信基础库 2.19.2 开始才支付微信虚拟支付";
}
uni.showModal({
title: "提示",
content: errMsg,
showCancel: false
});
}
}
}
</script>
<style scoped>
.app {
padding: 30rpx;
}
.input {
border: 1px solid #f3f3f3;
padding: 0 20rpx;
width: 100%;
box-sizing: border-box;
height: 80rpx;
}
.button {
margin-top: 20rpx;
}
</style>
```
#### 道具直购示例@short-series-goods
```vue
<template>
<view class="app">
<view>购买道具数量:</view>
<view><input class="input" v-model.number="buy_quantity" /></view>
<button class="button" @click="createOrder('wxpay-virtual')">发起微信虚拟支付</button>
<uni-pay ref="pay" @success="onSuccess" @fail="onFail"></uni-pay>
</view>
</template>
<script>
export default {
data() {
return {
buy_quantity: 1,
}
},
methods: {
/**
* 发起支付(不唤起收银台,手动指定支付方式)
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
createOrder() {
let order_no = `test` + Date.now();
let out_trade_no = `${order_no}-1`;
// 发起支付
this.$refs.pay.createOrder({
provider: "wxpay-virtual", // 支付供应商 固定为 wxpay-virtual
order_no: order_no, // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: out_trade_no, // 插件支付单号
description: "支付描述", // 支付描述
type: "wxpay-virtual-test", // 支付回调类型
// 微信虚拟支付专属字段
wxpay_virtual: {
mode: "short_series_coin", // 模式 short_series_coin 代币充值 short_series_goods 道具直购
buy_quantity: this.buy_quantity, // 购买道具数量
product_id: "test001", // 道具id,在微信小程序后台 - 功能 - 虚拟支付 - 基本配置 - 道具配置 中配置道具id
goods_price: 1, // 道具价格,需要和配置的价格一致才能正常发起支付
},
// 自定义数据
custom: {
user_id: "001", // 业务系统用户id
},
});
},
// 监听事件 - 支付成功
onSuccess(res) {
console.log('success: ', res);
if (res.user_order_success) {
// 代表用户已付款,且你自己写的回调成功并正确执行了
} else {
// 代表用户已付款,但你自己写的回调执行失败(通常是因为你的回调代码有问题)
}
},
onFail(err) {
console.log('err: ', err)
let errMsg = err.errMsg;
if (errMsg === "requestVirtualPayment:fail INVALID_PLATFORM") {
errMsg = "苹果手机不支持微信虚拟支付";
} else if (errMsg === "requestVirtualPayment:fail no permission") {
errMsg = "微信基础库 2.19.2 开始才支付微信虚拟支付";
}
uni.showModal({
title: "提示",
content: errMsg,
showCancel: false
});
}
}
}
</script>
<style scoped>
.app {
padding: 30rpx;
}
.input {
border: 1px solid #f3f3f3;
padding: 0 20rpx;
width: 100%;
box-sizing: border-box;
height: 80rpx;
}
.button {
margin-top: 20rpx;
}
</style>
```
**完整微信小程序虚拟支付示例代码**
```vue
<template>
<view class="app">
<view class="ios-tips">
注意:苹果手机不支持微信虚拟支付
</view>
<view>
<view class="label">模式:</view>
<radio-group @change="modeChange">
<label class="radio">
<radio value="short_series_coin" checked="true" />代币充值
</label>
<label class="radio ml20">
<radio value="short_series_goods" />道具直购
</label>
</radio-group>
</view>
<view class="mt20">
<view class="label">支付单号:</view>
<view><input v-model="out_trade_no" /></view>
</view>
<template v-if="wxpay_virtual.mode === 'short_series_coin'">
<view>
<view class="label">充值代币数量(建议设置1 人民币 = 100 代币):</view>
<view><input v-model.number="wxpay_virtual.buy_quantity" /></view>
</view>
</template>
<template v-else-if="wxpay_virtual.mode === 'short_series_goods'">
<view>
<view class="label">道具选择:</view>
<radio-group @change="productIdChange">
<label class="radio">
<radio value="test001" checked="true" />道具1
</label>
<label class="radio ml20">
<radio value="test002" />道具2
</label>
</radio-group>
</view>
<view>
<view class="label">购买道具数量:</view>
<view><input v-model.number="wxpay_virtual.buy_quantity" /></view>
</view>
</template>
<button @click="createOrder('wxpay-virtual')">发起微信虚拟支付</button>
<button @click="getOrderPopup(true)">查询支付状态</button>
<template v-if="wxpay_virtual.mode === 'short_series_coin'">
<button @click="queryUserBalance">查询我的代币余额</button>
<button @click="currencyPay">扣减代币</button>
</template>
<!-- 查询支付的弹窗 -->
<uni-popup ref="getOrderPopup" type="bottom" :safe-area="false">
<view class="get-order-popup">
<view class="label">支付单号:</view>
<view class="mt20">
<input v-model="out_trade_no" placeholder="请输入" />
</view>
<view class="mt20">
<button @click="getOrder">查询支付状态</button>
</view>
<view class="mt20" v-if="getOrderRes.transaction_id">
<table class="table">
<tr class="table-tr">
<td class="align-left">订单描述</td>
<td class="align-right">{{ getOrderRes.description }}</td>
</tr>
<tr class="table-tr">
<td class="align-left">支付金额</td>
<td class="align-right">{{ (getOrderRes.total_fee / 100).toFixed(2) }}</td>
</tr>
<tr class="table-tr">
<td class="align-left">付款时间</td>
<td class="align-right">{{ timeFormat(getOrderRes.pay_date,'yyyy-MM-dd hh:mm:ss') }}</td>
</tr>
<tr class="table-tr">
<td class="align-left">支付方式</td>
<td class="align-right">{{ providerFormat(getOrderRes.provider) }}</td>
</tr>
<tr class="table-tr">
<td class="align-left">第三方交易单号</td>
<td class="align-right">{{ getOrderRes.transaction_id }}</td>
</tr>
<tr class="table-tr">
<td class="align-left">插件支付单号</td>
<td class="align-right">{{ getOrderRes.out_trade_no }}</td>
</tr>
<tr class="table-tr">
<td class="align-left">回调状态</td>
<td class="align-right">{{ getOrderRes.user_order_success ? "成功" : "异常" }}</td>
</tr>
</table>
</view>
</view>
</uni-popup>
<!--
<button @click="refund">发起退款</button>
<view class="tips">发起退款需要admin权限,本示例未对接登录功能</view>
<button @click="getRefund">查询退款状态</button>
-->
<!-- 统一支付组件,注意:vue3下ref不可以等于组件名,因此这里ref="pay" 而不能是 ref="uniPay" -->
<uni-pay ref="pay" :adpid="adpid" height="70vh" 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 {
wxpay_virtual: {
mode: "short_series_coin", // 模式 short_series_coin 代币充值 short_series_goods 道具直购
buy_quantity: 1, // 购买代币数量或道具数量
product_id: "test001", // 道具id,在微信小程序后台 - 功能 - 虚拟支付 - 基本配置 - 道具配置 中配置道具id
goods_price: 1, // 道具价格,需要和配置的价格一致才能正常发起支付
},
order_no: "", // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: "", // 插件支付单号
description: "测试订单", // 支付描述
type: "test", // 支付回调类型 如 recharge 代表余额充值 goods 代表商品订单(可自定义,任意英文单词都可以,只要你在 uni-pay-co/notify/目录下创建对应的 xxx.js文件进行编写对应的回调逻辑即可)
openid: "", // 微信小程序的用户openid
adpid: "1000000001", // uni-ad的广告位id
getOrderRes: {}, // 查询订单支付成功后的返回值
}
},
onLoad(options = {}) {
},
methods: {
/**
* 发起支付(不唤起收银台,手动指定支付方式)
* 在调用此api前,你应该先创建自己的业务系统订单,并获得订单号 order_no,把order_no当参数传给此api,而示例中为了简化跟支付插件无关的代码,这里直接已时间戳生成了order_no
*/
createOrder(provider) {
// #ifndef MP-WEIXIN
uni.showModal({
title: "提示",
content: "请在微信小程序中体验",
showCancel: false
});
return;
// #endif
this.order_no = `test` + Date.now();
this.out_trade_no = `${this.order_no}-1`;
// 发起支付
this.$refs.pay.createOrder({
provider: provider, // 支付供应商
order_no: this.order_no, // 业务系统订单号(即你自己业务系统的订单表的订单号)
out_trade_no: this.out_trade_no, // 插件支付单号
description: this.description, // 支付描述
type: this.type, // 支付回调类型
wxpay_virtual: this.wxpay_virtual, // 微信虚拟支付专属字段
// 自定义数据
custom: {
user_id: "001", // 业务系统用户id
},
});
},
// 打开查询订单的弹窗
getOrderPopup(key) {
if (key) {
this.$refs.getOrderPopup.open();
} else {
this.$refs.getOrderPopup.close();
}
},
// 查询支付状态
async getOrder() {
this.getOrderRes = {};
let res = await this.$refs.pay.getOrder({
out_trade_no: this.out_trade_no, // 插件支付单号 两者传1个即可
await_notify: false, // 是否等待异步通知
});
if (res) {
this.getOrderRes = res.pay_order;
if (!res.has_paid) {
uni.showToast({
title: "未付款",
icon: "none"
});
return;
}
if (res.user_order_success === true) {
let obj = {
"-1": "已关闭",
"1": "已支付",
"0": "未支付",
"2": "已部分退款",
"3": "已全额退款"
};
uni.showToast({
title: obj[res.status] || res.errMsg,
icon: "none"
});
} else if (res.user_order_success === false) {
uni.showModal({
content: "付款成功,且已接收到异步回调,但自定义回调逻辑执行失败",
showCancel: false
});
} else if (res.status === 0) {
uni.showModal({
content: "付款成功,但还未收到异步回调",
showCancel: false
});
} else {
uni.showModal({
content: "付款成功,且已接收到异步回调,但自定义回调逻辑还在执行中",
showCancel: false
});
}
}
},
// 发起退款
async refund() {
let res = await this.$refs.pay.refund({
out_trade_no: this.out_trade_no, // 插件支付单号
});
if (res) {
uni.showToast({
title: res.errMsg,
icon: "none"
});
}
},
// 查询退款状态
async getRefund() {
let res = await this.$refs.pay.getRefund({
out_trade_no: this.out_trade_no, // 插件支付单号
});
if (res) {
uni.showModal({
content: res.errMsg,
showCancel: false
});
}
},
// 监听事件 - 支付订单创建成功(此时用户还未支付)
onCreate(res) {
console.log('create: ', res);
},
// 监听事件 - 支付成功
onSuccess(res) {
console.log('success: ', res);
if (res.user_order_success) {
// 代表用户已付款,且你自己写的回调成功并正确执行了
} else {
// 代表用户已付款,但你自己写的回调执行失败(通常是因为你的回调代码有问题)
}
},
onFail(err) {
console.log('err: ', err)
let errData = {
"-5": "开通签约结果未知",
"-15002": "outTradeNo重复使用,请换新单号重试",
"-15003": "系统错误",
"-15005": "支付配置错误",
"-15006": "支付配置错误",
"-15007": "session_key过期,用户需要重新登录",
"-15008": "二级商户进件未完成",
"-15009": "代币未发布",
"-15010": "道具productId未发布",
"-15012": "调用米大师失败导致关单,请换新单号重试",
"-15013": "道具价格错误",
"-15014": "道具/代币发布未生效,禁止下单,大概10分钟后生效",
"-15017": "此商家涉嫌违规,收款功能已被限制,暂无法支付。商家可以登录微信商户平台/微信支付商家助手小程序查看原因和解决方案",
"-15018": "代币或者道具productId审核不通过",
"-15019": "调微信报商户受限,商家可以登录微信商户平台/微信支付商家助手小程序查看原因和解决方案",
"-15020": "操作过快,请稍候再试",
"-15021": "小程序被限频交易",
}
let errMsg = errData[String(err.errCode)] || err.errMsg;
if (errMsg === "requestVirtualPayment:fail INVALID_PLATFORM") {
errMsg = "苹果手机不支持微信虚拟支付";
} else if (errMsg === "requestVirtualPayment:fail no permission") {
errMsg = "微信基础库 2.19.2 开始才支付微信虚拟支付";
}
console.error(errMsg);
uni.showModal({
title: "提示",
content: errMsg,
showCancel: false
});
},
onCancel(err) {
console.log('用户取消了支付: ', err)
},
// 查询用户微信虚拟支付代币余额(微信虚拟支付的代币余额是通过调用微信API查询的)
async queryUserBalance() {
// #ifndef MP-WEIXIN
uni.showModal({
title: "提示",
content: "请在微信小程序中体验",
showCancel: false
});
return;
// #endif
const wxpayVirtualCo = uniCloud.importObject("wxpay-virtual-co");
let queryUserBalanceRes = await wxpayVirtualCo.queryUserBalance({
openid: this.$refs.pay.openid,
});
let { balance, presentBalance } = queryUserBalanceRes;
let content = `我的余额:${balance}`;
if (presentBalance) {
content += `(含赠送余额:${presentBalance})`;
}
uni.showModal({
title: "提示",
content,
showCancel: false
})
},
// 扣减用户代币,扣减用户代币需要保证用户的sessionKey在有效期内(uni-pay组件会自动获取当前微信用户的sessionKey)
async currencyPay() {
// #ifndef MP-WEIXIN
uni.showModal({
title: "提示",
content: "请在微信小程序中体验",
showCancel: false
});
return;
// #endif
const wxpayVirtualCo = uniCloud.importObject("wxpay-virtual-co");
// 从uni-pay组件中获取openid
let { openid } = this.$refs.pay;
let queryUserBalanceRes = await wxpayVirtualCo.currencyPay({
openid
});
uni.showModal({
title: "提示",
content: `成功扣减余额:${queryUserBalanceRes.amount},还剩余额:${queryUserBalanceRes.balance}`,
showCancel: false
})
},
// 监听模式选择
modeChange(e) {
this.wxpay_virtual.mode = e.detail.value;
},
// 监听道具选择
productIdChange(e) {
this.wxpay_virtual.product_id = e.detail.value;
if (e.detail.value === "test002") {
this.wxpay_virtual.goods_price = 2; // 道具价格
} else {
this.wxpay_virtual.goods_price = 1; // 道具价格
}
},
providerFormat(provider) {
let providerObj = {
"wxpay": "微信支付",
"alipay": "支付宝支付",
"appleiap": "ios内购",
"wxpay-virtual": "微信虚拟支付"
};
let providerStr = providerObj[provider] || "未知";
return providerStr;
},
/**
* 日期格式化
* @params {Date || Number} date 需要格式化的时间
* timeFormat(new Date(),"yyyy-MM-dd hh:mm:ss");
*/
timeFormat(time, fmt = 'yyyy-MM-dd hh:mm:ss', targetTimezone = 8) {
try {
if (!time) {
return "";
}
if (typeof time === "string" && !isNaN(time)) time = Number(time);
// 其他更多是格式化有如下:
// yyyy-MM-dd hh:mm:ss|yyyy年MM月dd日 hh时MM分等,可自定义组合
let date;
if (typeof time === "number") {
if (time.toString().length == 10) time *= 1000;
date = new Date(time);
} else {
date = time;
}
const dif = date.getTimezoneOffset();
const timeDif = dif * 60 * 1000 + (targetTimezone * 60 * 60 * 1000);
const east8time = date.getTime() + timeDif;
date = new Date(east8time);
let opt = {
"M+": date.getMonth() + 1, //月份
"d+": date.getDate(), //日
"h+": date.getHours(), //小时
"m+": date.getMinutes(), //分
"s+": date.getSeconds(), //秒
"q+": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
}
for (let k in opt) {
if (new RegExp("(" + k + ")").test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (opt[k]) : (("00" + opt[k]).substr(("" + opt[k]).length)));
}
}
return fmt;
} catch (err) {
// 若格式错误,则原值显示
return time;
}
},
},
computed: {
},
}
</script>
<style lang="scss" scoped>
.app {
padding: 30rpx;
}
input {
border: 1px solid #f3f3f3;
padding: 0 20rpx;
width: 100%;
box-sizing: border-box;
height: 80rpx;
}
button {
margin-top: 20rpx;
}
.label {
margin: 10rpx 0;
}
.tips {
margin-top: 20rpx;
font-size: 24rpx;
color: #565656;
}
.get-order-popup {
background-color: #ffffff;
padding: 30rpx;
height: 60vh;
border-radius: 30rpx 30rpx 0 0;
overflow: hidden;
}
.ml20 {
margin-left: 20rpx;
}
.mt20 {
margin-top: 20rpx;
}
.table {
font-size: 24rpx;
width: 100%;
}
.table-tr {
display: flex;
margin-top: 20rpx;
}
.align-left {
text-align: left;
width: 50%;
}
.align-right {
text-align: right;
width: 50%;
}
.ios-tips {
color: #e43d33;
font-size: 28rpx;
}
</style>
```
#### 其他云端API
微信虚拟支付除了支付API以外,还涉及到查询用户代币余额、扣减用户代币、赠送用户代币等API
但这些API并不适合在前端直接调用,否则会有很大的安全隐患,因此API不在 uni-pay 组件和 uni-pay-co 这个云对象中,而是在 uni-pay 公共模块中,以此方便在任何云函数或云对象内调用
点击 [查看微信虚拟支付云端API](./wxpay-virtual.md)
## 支付统计@pay-stat
`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文件
```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}
```
### 收款趋势
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A101.png"/>
</div>
**概况**
`概况`栏目中可以直观的看到今日、昨日、前日、本周、本月、本季度、本年度、累计数据。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A101-01.png"/>
</div>
**名词解释:**
- 下单金额(GMV):统计时间内,下单金额(包含未支付订单和退款订单)。
- 收款金额(GPV):统计时间内,成功支付的订单金额(包含退款订单)。
- 退款金额:统计时间内,发生退款的金额。
- 实收金额:实收金额=收款金额-退款金额
**今日数据**
`今日数据`栏目中可以看到更多今日统计数据。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A101-02.png"/>
</div>
**名词解释:**
- 订单金额:
+ 下单:今日下单金额(包含未支付订单和退款订单)。
+ 收款:今日成功支付的订单金额(包含退款订单)。
+ 退款:今日发生退款的金额。
- 订单数量:
+ 下单:今日成功下单的订单笔数(包含未支付订单和退款订单)。
+ 收款:今日成功支付的订单数(包含退款订单)。
+ 退款:今日发生退款的订单数。
- 用户数量:
+ 下单:今日成功下单的客户数(包含未支付订单和退款订单)。
+ 收款:今日成功支付的用户数(包含退款订单)。
+ 退款:今日发生退款的用户数。
- 设备数量:
+ 下单:今日成功下单的设备数(包含未支付订单和退款订单)。
+ 收款:今日成功支付的设备数(包含退款订单)。
+ 退款:今日发生退款的设备数。
**趋势图**
`趋势图`栏目中以`天维度``月维度``季维度``年维度`进行趋势统计。可以直观的看到收入的增长趋势。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A101-03.png"/>
</div>
### 转换漏斗分析
可以为您分析指定时间段的支付转化率,同时展示支付转化率趋势图。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A102.png"/>
</div>
**名词解释:**
- 活跃设备数:包含未登录和已登录的用户数量
- 活跃用户数:登录用户的数量
- 支付用户数:至少有一笔成功支付订单的用户
- 用户转化率:用户转化率=活跃用户数/活跃设备数
- 支付转化率:支付转化率=支付用户数/活跃用户数
### 价值用户排行
可以为您快速筛选高价值用户,高复购率用户。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A103.png"/>
</div>
### 订单明细
可以搜索、查看订单详情
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/%E6%94%AF%E4%BB%98%E7%BB%9F%E8%AE%A104.png"/>
</div>
## 注意事项@tips
### 微信公众号@tips-wxpay-jsapi
h5的路由模式必须配置为 `history`,因为微信公众号登录的回调地址不支持 `hash` 模式。
同时微信公众号开发调试比较麻烦,麻烦在于网页授权需要添加域名白名单,用localhost或用ip访问本地是无法获取到微信的code的,这样也就无法获取openid,导致无法支付。
操作步骤
- 1、手机和电脑连接在同一个局域网(路由器WiFi下)
- 2、查看自己电脑的局域网ip地址,比如为192.168.1.8
- 3、假设你的线上域名是(必须要有自己的域名)www.abc.com 则设置 test.abc.com 先解析到你的前端托管域名上(为了让微信验证域名通过,因为验证域名时,需要上传微信指定的文件到你的前端托管)。
- 4、进入公众号后台,设置与开发 -> 公众号设置 -> 设置网页授权域名,添加 test.abc.com
- 5、成功添加后,再重新设置 test.abc.com 解析到你电脑的局域网ip,如192.168.1.8
- 6、过一段时间(大概20分钟后,更换域名解析生效需要时间,这20分钟内千万不要再去访问http://test.abc.com)
- 7、20分钟后,访问 http://test.abc.com 此时就等于访问了 http://192.168.1.8,这样你的手机就用 http://test.abc.com 来访问你的项目
- 8、可以正常获取到openid了,就可以正常进行本地微信公众号支付测试了(不然每次都要上传到服务器测试)。
当用自定义域名时,还需要在项目根目录添加 `vue.config.js` 文件,内容如下:
```js
module.exports = {
devServer: {
disableHostCheck: true, // 忽略域名检查
port: 80, // 设置80端口为项目启动端口
}
}
```
### 微信小程序@tips-wxpay-mp
微信小程序支付除了配置uni-pay的支付配置外,还需要配置 `manifest.json` 内的 微信小程序appid,如下图所示。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-7.png"/>
</div>
如果报如下错误,请点[这里](#question-mp-weixin-domain)
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-32.png)
### APP支付@tips-app
APP支付除了配置uni-pay的支付配置外,还需要打包时添加支付模块,如下图所示。
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-8.png"/>
</div>
同时,还需要打自定义基座(包名需要和开放平台下填写的一致),且你在开放平台下的这个应用必须通过审核才可以。(比如微信开放平台下的APP应用显示通过审核才可以)
### ios内购支付@tips-appleiap
1. ios内购支付需勾选App模块配置中的Apple应用内支付
2. 需要打ios自定义基座
3. 需要注册ios开发者账号,且交了年费(688元/年)
4. 需要在ios开发者平台添加内购商品,并获得商品id
5. ios沙箱测试时,需要先在ios开发者平台添加沙箱测试账号,同时你的测试手机上需要登录ios沙箱账号
6. 目前hbx版本热刷新会导致ios支付无法正常调用,因此每次修改完代码保存后,需要先关闭手机App,然后hbx重启项目,再打开手机app。(后面HBX会修复此问题)
## 全局错误码@errorcode
| 错误模块 | 错误码 | 说明 |
|---------|-------------|---------------------------|
| 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 | 证书错误,请检查支付证书 |
返回值示例
```json
{
"errMsg": "支付单号(out_trade_no)不能为空",
"errCode": 51001,
"errSubject": "uni-pay"
}
```
## 常见问题@question
### 老项目如何升级到uni-pay 2
`uni-pay 2` 仍内置了uni-pay公共模块,向下兼容`uni-pay 1.x`,即从`uni-pay 1.x`可以一键升级到`uni-pay 2.x`,且不会对你的老项目造成影响。
### 发起支付时报数据库表不存在
支付插件需要创建支付相关的表后才能正常运行。[查看相关的数据库表](#database)
### 支付账号如何申请
本插件对接的支付渠道是微信和支付宝官方渠道
**微信支付**
申请地址 [https://pay.weixin.qq.com/index.php/apply/applyment_home/guide_normal](https://pay.weixin.qq.com/index.php/apply/applyment_home/guide_normal)
申请指引 [https://pay.weixin.qq.com/static/applyment_guide/applyment_index.shtml](https://pay.weixin.qq.com/static/applyment_guide/applyment_index.shtml)
**支付宝**
申请地址 [https://open.alipay.com](https://open.alipay.com)
申请指引 [https://opendocs.alipay.com/common/02asmu](https://opendocs.alipay.com/common/02asmu)
**注意**
支付账号申请需要企业资质(个体工商户也可以,但不可以是个人资质,需要有营业执照,银行对公账户)。
### 如何获得插件需要的密钥参数@get-config-help
**微信支付**
[微信支付参数和证书生成教程](https://docs.qq.com/doc/DWUpGTW1kSUdpZGF5)
- pfx:微信支付v2需要用到的证书,是一个后缀名为`.p12`的文件,如果你的`.p12`文件不是`apiclient_cert.p12`,则将它改名成`apiclient_cert.p12`,并复制到 `uni-config-center/uni-pay/wxpay/` 目录下
- appCertPath:微信支付v3需要用到的证书,是一个名为`apiclient_cert.pem`的文件,将它复制到 `uni-config-center/uni-pay/wxpay/` 目录下
- appPrivateKeyPath:微信支付v3需要用到的证书,是一个名为`apiclient_key.pem`的文件,将它复制到 `uni-config-center/uni-pay/wxpay/` 目录下
**支付宝**
[支付宝支付证书生成教程](https://docs.qq.com/doc/DWVBlVkZ1Z21SZFpS)
- privateKey:支付宝商户私钥
- appCertPath:支付宝商户公钥路径,是一个后缀名为`appCertPublicKey.crt`的文件,将它复制到 `uni-config-center/uni-pay/alipay/` 目录下
- alipayPublicCertPath:支付宝商户公钥路径,是一个后缀名为`alipayCertPublicKey_RSA2.crt`的文件,将它复制到 `uni-config-center/uni-pay/alipay/` 目录下
- alipayRootCertPath:支付宝根证书路径,是一个后缀名为`alipayRootCert.crt`的文件,将它复制到 `uni-config-center/uni-pay/alipay/` 目录下
### 微信小程序真机报fail url not in domain list错误@question-mp-weixin-domain
![](https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-32.png)
这是由于云开发的域名没有添加到微信小程序域名白名单导致的,需要去微信小程序后台,添加以下域名到微信小程序域名白名单
```
https://api.next.bspapp.com;https://api.bspapp.com;https://tcb-api.tencentcloudapi.com;
```
<div align=center>
<img class="zooming" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni-pay-31.png"/>
</div>
**添加完域名后,一定要重启微信开发者工具,然后去手机微信里删除最近使用的小程序(这一步很关键),最后重新扫二维码进入小程序。**
### 支付宝小程序云执行微信v2退款接口失败,报Error: unsupported, POST https://api.mch.weixin.qq.com/secapi/pay/refund -1@question-alipay-weixin-v2-refund
有两个方案可以解决
方案一:使用微信支付v3版本
方案二:将云函数的node版本切换成node16(支付宝小程序云默认是node18,而node18不再支持微信支付v2证书pfx的加密算法导致的)
> 本文档为`uni-pay 1.x`版本文档。适用于老项目。
> 新项目请另行查阅 [uni-pay 2.x 版本文档](uni-pay.md)。
## 简介
`unipay``uniCloud`开发者提供了简单、易用、统一的支付能力封装。让开发者无需研究支付宝、微信等支付平台的后端开发、无需为它们编写不同代码,拿来即用,屏蔽差异。
`uni-app`前端已经封装的全端支付 api `uni.requestPayment`,现在服务端也封装好了`unipay for uniCloud`,从此开发者可以极快的完成前后一体的支付业务。
目前已封装 App 端(微信支付和支付宝支付)、微信小程序、支付宝小程序的支付能力。
`unipay`是开源 sdk,可放心使用。本插件还包含示例工程,配置自己在微信和支付宝申请的相关配置后即可运行。
为了更好的体验支付流程可以在插件市场导入`unipay`的示例项目快速体验,[插件市场 unipay](https://ext.dcloud.net.cn/plugin?id=1835)[gitee仓库 unipay公共模块](https://gitee.com/dcloud/uniPay.git)
插件市场还有基于uniPay再次封装的模板,前端支付、管理端订单管理均已写好,拿去就用,见:[BaseCloud - 统一下单支付业务模块](https://ext.dcloud.net.cn/plugin?id=2668)
**须知**
- unipay 对入参和返回值均做了驼峰转化,开发者在对照微信支付或者支付宝支付对应的文档时需要注意。
- 特殊参数`appId``mchId`需注意大小写
- 所有金额被统一为以分为单位(避免浮点误差)
- 为避免无关参数干扰此文档仅列举必填参数,其余参数请参照[微信支付-小程序](https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1)[微信支付-App](https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1)[支付宝支付-小程序](https://opendocs.alipay.com/apis/api_1/alipay.trade.create)[支付宝支付-App](https://opendocs.alipay.com/apis/api_1/alipay.trade.app.pay)
- 微信支付沙箱环境不支持小程序支付,另外此沙箱环境只可以跑微信提供的测试用例不可以随意测试
- 无论是微信还是支付宝,沙箱环境都不确保稳定,如果使用沙箱的过程中遇到疑难问题建议切换成正式环境测试
**问题排查指引**
- [支付宝支付蚂蚁技术支持](https://openclub.alipay.com/club/history/read/7695)
## 引入 unipay
开发者可以自行选择是从插件市场导入还是从 npm 安装,引入方式略有不同,请看下面示例
```js
// 插件市场导入
const unipay = require('uni-pay')
// npm安装
const unipay = require('@dcloudio/unipay')
```
**注意**
- 插件市场导入的用法请参考[云函数公用模块](https://uniapp.dcloud.net.cn/uniCloud/cf-common)
## 初始化@init
进行初始化操作返回 unipay 实例
### 微信支付V3
> 新增于 ```uni-pay 1.1.0```
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|:----------------------:| :-----: |:--:|:----------------:|:----------------------------:|
| appId | String | 是 | - | 当前应用在对应支付平台的 appId |
| mchId | String | 是 | - | 商户号 |
| v3Key | String | 是 | - | API v3 密钥 |
| appCertPath | String | 是 | - | 商户 API 证书文件路径(文件路径与字符串二选一) |
| appCertContent | String | 是 | - | 商户 API 证书字符串(文件路径与字符串二选一) |
| appPrivateKeyPath | String | 是 | - | 商户 API 私钥文件路径(文件路径与字符串二选一) |
| appPrivateKeyContent | String | 是 | - | 商户 API 私钥字符串(文件路径与字符串二选一) |
| timeout | Number | 否 | 5000 | 请求超时时间,单位:毫秒 |
```js
const path = require('path'); // 引入内置的path模块
const unipayIns = unipay.initWeixinV3({
appId: 'your appId',
mchId: 'your mchId',
v3Key: 'you parterner key',
appCertPath: path.resolve(__dirname, 'your appCertPath'),
// appCertContent: "",
appPrivateKeyPath: path.resolve(__dirname, 'your appPrivateKeyPath'),
// appPrivateKeyContent: "",
})
```
**说明**
证书字符串与私钥字符串格式要求:
- 证书字符串不应包含`-----BEGIN CERTIFICATE-----``-----END CERTIFICATE-----`,并且证书内容不能换行
- 私钥字符串不应包含`-----BEGIN PRIVATE KEY-----``-----END PRIVATE KEY-----`,并且私钥内容不能换行
例:
```
// 证书内容
-----BEGIN CERTIFICATE-----
your certificate content...
-----END CERTIFICATE-----
// 证书字符串
appCertContent = 'your certificate content...'
```
### 微信支付v2
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|:---------:| :-----: | :----:|:-----------------:|:----------------------------:|
| appId | String | 是 | - | 当前应用在对应支付平台的 appId |
| mchId | String | 是 | - | 商户号 |
| subAppId | String | 否 | - | 子商户appId |
| subMchId | String | 否 | - | 子商户号 |
| key | String | 是 | - | 支付商户 key (API密钥) |
| pfx | String&#124;Buffer| 使用退款功能必填| - | 微信支付商户 API 证书,主要用于退款 |
| timeout | Number | 否 | 5000 | 请求超时时间,单位:毫秒 |
| signType | String | 否 | MD5 | 签名类型 |
| sandbox | Boolean | 否 | false | 是否启用沙箱环境 |
```js
const unipayIns = unipay.initWeixin({
appId: 'your appId',
mchId: 'your mchId',
key: 'you parterner key',
pfx: fs.readFileSync('/path/to/your/pfxfile'), // 建议以p12文件绝对路径进行读取,使用微信退款时需要
})
// 以证书放在云函数index.js同级的cert目录下为例,index.js内可以按照下面这个写
const fs = require('fs');
const path = require('path');
const unipayIns = unipay.initWeixin({
appId: 'your appId',
mchId: 'your mchId',
key: 'you parterner key',
pfx: fs.readFileSync(path.resolve(__dirname, 'cert/xxx.p12'))
})
```
### 支付宝支付
**入参说明**
| 参数名 | 类型 | 必填| 默认值 | 说明 |
| :-------------: | :-----: | :--:| :--------------------------------------------------:| :------------------------------------:|
| appId | String | 是 | - | 当前应用在对应支付平台的 appId |
| mchId | String | 是| - | 商户号 |
| privateKey | String | 是 | - | 应用私钥字符串 |
| alipayPublicKey | String | 否 | - | 支付宝公钥,验签使用 |
| keyType | String | 否 | PKCS8 | 应用私钥字符串类型 |
| timeout | Number | 否 | 5000 | 请求超时时间,单位:毫秒 |
| signType | String | 否 | RSA2 | 签名类型 |
| sandbox | Boolean | 否 | false | 是否启用沙箱环境 |
| alipayRootCertPath | String | 否 | - | `1.0.6+`,支付宝根证书文件路径 |
| appCertPath | String | 否 | - | `1.0.6+`,应用公钥证书文件路径 |
| alipayPublicCertPath| String | 否 | - | `1.0.6+`,支付宝公钥证书文件路径 |
```js
const unipayIns = unipay.initAlipay({
appId: 'your appId',
mchId: 'your mchId',
privateKey: 'your privateKey',
// 如果不使用证书(普通公钥模式)需要alipayPublicKey
alipayPublicKey: 'you alipayPublicKey', // 使用支付时需传递此值做返回结果验签
// 如果使用证书需要传alipayRootCertPath、appCertPath、alipayPublicCertPath
alipayRootCertPath: path.join(__dirname,'../fixtures/alipayRootCert.crt'),
appCertPath: path.join(__dirname,'../fixtures/appCertPublicKey.crt'),
alipayPublicCertPath: path.join(__dirname,'../fixtures/alipayCertPublicKey_RSA2.crt'),
})
```
**常见问题**
- 支付宝支付时遇到`error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag`类似的错误时请确认一下自己的私钥格式,如果不是PKCS8需要在初始化时传入keyType参数,值为对应的私钥格式
### 苹果内购支付
**入参说明**
| 参数名 | 类型 | 必填| 默认值 | 说明 |
| :-------------: | :-----: | :--:| :--------------------------------------------------:| :------------------------------------:|
| sandbox | Boolean | 否 | false | 是否启用沙箱环境 |
| password | String | 否 |- | App 专用共享密钥,App 专用共享密钥是用于接收此 App 自动续期订阅收据的唯一代码。如果您要将此 App 转让给其他开发者或不想公开主共享密钥,建议使用 App 专用共享密钥。非自动续订场景不需要此参数|
| timeout | Number | 否 | 5000 | 请求超时时间,单位:毫秒 |
```js
const unipayIns = unipay.initAppleIapPayment({
sandbox: true,
password: 'your password',
})
```
### 微信小程序虚拟支付@wxpayVirtual
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
| :-------------: | :-----: | :--:| :---------------: | :------------------------------------: |
| appId | String | 是 |- | 微信小程序的appid |
| secret | String | 是 |- | 微信小程序的secret |
| mchId | String | 是 |- | 微信虚拟支付的商户id |
| offerId | String | 是 |- | 微信虚拟支付的支付应用ID |
| appKey | String | 是 |- | 微信虚拟支付的现网AppKey(正式环境) |
| sandboxAppKey | String | 是 |- | 微信虚拟支付的沙箱AppKey |
| rate | Number | 是 |100 | 微信虚拟支付的代币兑换比例,比如1元兑换100代币,那么这里就是100(需要开通虚拟支付的时候也设置成 1 人民币 = 100 代币) |
| token | String | 是 |- | 微信小程序通信的token,在开发 - 开发管理 - 消息推送 - Token(令牌) |
| encodingAESKey| String | 是 |- | 微信小程序消息加密密钥,必须43位,在开发 - 开发管理 - 消息推送 - EncodingAESKey(消息加解密密钥) |
| accessToken | String | 是 |- | 微信小程序的accessToken(注意此token有过期时间,需要自己做缓存) |
| sandbox | Boolean | 否 |false | 是否启用沙箱环境 |
```js
const unipayIns = unipay.initWeixinVirtualPayment({
"appId": "", // 微信小程序的appid
"secret": "", // 微信小程序的secret
"mchId": "", // 商户id
"offerId": "", // 支付应用ID
"appKey": "", // 现网AppKey(正式环境)
"sandboxAppKey": "", // 沙箱AppKey
"rate": 100, // 代币兑换比例,比如1元兑换100代币,那么这里就是100(需要开通虚拟支付的时候也设置成 1 人民币 = 100 代币)
"token": "", // 微信小程序通信的token,在开发 - 开发管理 - 消息推送 - Token(令牌)
"encodingAESKey": "", // 必须43位,微信小程序消息加密密钥,在开发 - 开发管理 - 消息推送 - EncodingAESKey(消息加解密密钥)
"accessToken": "", // 微信小程序的accessToken(注意此token有过期时间,需要自己做缓存)
"sandbox": false, // 是否是沙箱环境(注意:沙箱环境异步回调可能有延迟,建议直接正式环境测试)
})
```
## Api 列表
### 获取支付参数
`unipayIns.getOrderInfo`
**入参说明**
| 参数名 | 类型 | 必填| 默认值 | 说明| 支持平台 |
| :--------: | :----: | :--------------------------------:| :----: | :------------------------------------------------------------------------:| :----------------------: |
| openid | String |支付宝小程序、微信小程序必填,App端支付不需要| - |通过对应 [uni-id](uni-id/summary.md) 接口进行获取,服务商模式应使用子商户获取的openid| 支付宝小程序、微信小程序 |
| subject | String |支付宝支付必填,微信支付时忽略此项| - |订单标题| 支付宝支付 |
| body | String |微信支付必填| - |商品描述| 微信支付 |
| outTradeNo | String |必填 | - |商户订单号,有长度限制(微信支付为32字符以内,支付宝为64字符以内)、只能包含字母、数字、下划线;需保证在商户端不重复| |
| totalFee | Number |必填 | - |订单金额,单位:分| 支付宝小程序、微信小程序 |
| notifyUrl | String |必填 | - |支付结果通知地址,**需要注意支付宝支付时退款也会通知到此地址,务必处理好自己的业务逻辑**| |
| spbillCreateIp| String |必填 | - |客户端IP,云函数内可以通过`context.CLIENTIP`获取|- |
| tradeType | String| 是 | - | 交易类型;见下方 tradeType 的说明 |
| sceneInfo | Object |微信tradeType为MWEB时必填| - |见下方sceneInfo的说明|- |
**注意**
此接口支持直接传微信和支付宝官方文档上的参数,如微信的 `support_fapiao` 参数,转成驼峰 `supportFapiao` 即可
```js
let orderInfo = await unipayIns.getOrderInfo({
...前面参数省略
notifyUrl: 'https://xxx.xx', // 支付结果通知地址
supportFapiao: true
})
```
**tradeType的说明**
tradeType支持以下选项
- JSAPI 适用于:微信公众号支付、微信小程序支付、支付宝小程序支付
- APP 适用于:支付宝、微信APP支付
- NATIVE 适用于:支付宝、微信PC网站扫码支付
- MWEB 适用于:微信h5(非公众号)跳转微信页面支付(`新增于uni-pay 1.0.24`
**sceneInfo的说明**
微信支付tradeType为MWEB时需要传递以下格式的sceneInfo
```js
{
"h5Info": {
"type": "Wap",
"wapUrl": "https://pay.qq.com", // 开发者网站的网址
"wapName": "腾讯充值" // 开发者网站名称
}
}
```
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-------: | :----: | :----: | :--------------------------------------------------------------------: |
| orderInfo | Object | String | 客户端支付所需参数,直接返回给客户端即可,下面会介绍如何搭配客户端使用 |
**使用示例**
tradeType为NATIVE时直接将orderInfo.codeUrl转为二维码使用对应的客户端扫码支付即可
tradeType为MWEB时直接跳转到orderInfo.mwebUrl进行支付即可
```js
// 云函数 - getOrderInfo
exports.main = async function (event,context) {
let orderInfo = await unipayIns.getOrderInfo({
openid: 'user openid',
subject: '订单标题', // 微信支付时不可填写此项
body: '商品描述',
outTradeNo: '商户订单号',
totalFee: 1, // 金额,单位分
notifyUrl: 'https://xxx.xx' // 支付结果通知地址
})
return {
orderInfo
}
}
// 客户端
uniCloud.callFunction({
name: 'getOrderInfo',
success(res) {
uni.requestPayment({
// #ifdef APP-PLUS
provider: selectedProvider, // App端此参数必填,可以通过uni.getProvider获取
// #endif
// #ifdef MP-WEIXIN
...res.result.orderInfo,
// #endif
// #ifdef APP-PLUS || MP-ALIPAY
orderInfo: res.result.orderInfo,
// #endif
...res.result.orderInfo
success(){},
fail(){}
})
}
})
// 二维码支付
uniCloud.callFunction({
name: 'getOrderInfo',
success(res) {
// 以二维码形式展示此内容
console.log(res.result.orderInfo.codeurl)
}
})
```
### 查询订单
`unipayIns.orderQuery`, 根据商户订单号或者平台订单号查询订单信息,主要用于未接收到支付通知时可以使用此接口进行支付结果验证
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 支持平台 |
| :-----------: | :----: | :---------------------: | :----: | :--------: | :------: |
| outTradeNo | String | 和 transactionId 二选一 | - | 商户订单号 | - |
| transactionId | String | 和 outTradeNo 二选一 | - | 平台订单号 | - |
**返回值说明**
| 参数名 | 类型 |说明 | 支持平台 |
| :----------------:| :----:| :---------------------------------------------: |:-------:|
| appId | String|平台分配的应用 ID | 微信支付 |
| mchId | String|商户号,(微信支付文档里面叫商户号:mch_id,支付宝支付叫卖家id:seller_id)| 微信支付 |
| outTradeNo | String|商户订单号 | - |
| transactionId | String|平台订单号 | - |
| tradeState | String| 订单状态,见下方订单状态说明 | |
| totalFee | Number|标价金额 ,单位:分 | - |
| settlementTotalFee| Number|应结订单金额,单位:分 | 支付宝支付 |
| cashFee | Number|现金支付金额,单位:分 | - |
**订单状态**
微信支付:
SUCCESS—支付成功
REFUND—转入退款
NOTPAY—未支付
CLOSED—已关闭
REVOKED—已撤销(刷卡支付)
USERPAYING--用户支付中
PAYERROR--支付失败(其他原因,如银行返回失败)。
支付宝支付:
USERPAYING(交易创建,等待买家付款)
CLOSED(未付款交易超时关闭,或支付完成后全额退款)
SUCCESS(交易支付成功)
FINISHED(交易结束,不可退款)
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.orderQuery({
outTradeNo: 'outTradeNo',
})
return res
}
```
### 关闭订单
`unipayIns.closeOrder`,用于交易创建后,用户在一定时间内未进行支付,可调用该接口直接将未付款的交易进行关闭,避免重复支付。
**注意**
- 微信支付:订单生成后不能马上调用关单接口,最短调用时间间隔为 5 分钟。
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 支持平台 |
| :-----------: | :----: | :-------------------------------------------------: | :----: | :--------: | :--------: |
| outTradeNo | String | 使用微信时必填,使用支付宝时和 transactionId 二选一 | - | 商户订单号 | - |
| transactionId | String | 使用支付宝时和 outTradeNo 二选一 | - | 平台订单号 | 支付宝支付 |
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-----------: | :----: | :---------------: |:------:|
| appId | String | 平台分配的应用 ID | 微信支付V2 |
| mchId | String | 商户号 | 微信支付V2 |
| outTradeNo | String | 商户订单号 | 支付宝支付 |
| transactionId | String | 平台订单号 | 支付宝支付 |
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.closeOrder({
outTradeNo: 'outTradeNo',
})
return res
}
```
### 撤销订单
`unipayIns.cancelOrder`**此接口仅支付宝支持**,支付交易返回失败或支付系统超时,调用该接口撤销交易。如果此订单用户支付失败,支付宝系统会将此订单关闭;如果用户支付成功,支付宝系统会将此订单资金退还给用户。 注意:只有发生支付系统超时或者支付结果未知时可调用撤销,其他正常支付的单如需实现相同功能请调用申请退款 API。提交支付交易后调用【查询订单 API】,没有明确的支付结果再调用【撤销订单 API】。
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 支持平台 |
| :-----------: | :----: | :---------------------: | :----: | :--------: | :--------: |
| outTradeNo | String | 和 transactionId 二选一 | - | 商户订单号 | 支付宝支付 |
| transactionId | String | 和 outTradeNo 二选一 | - | 平台订单号 | 支付宝支付 |
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-----------: | :----: | :--------: | :--------: |
| outTradeNo | String | 商户订单号 | 支付宝支付 |
| transactionId | String | 平台订单号 | 支付宝支付 |
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.cancelOrder({
outTradeNo: 'outTradeNo',
})
return res
}
```
### 申请退款
`unipayIns.refund`,当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家。
**微信支付注意事项**
1. 交易时间超过一年的订单无法提交退款
2. 微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号
3. 请求频率限制:150qps,即每秒钟正常的申请退款请求次数不超过 150 次,错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过 6 次
4. 每个支付订单的部分退款次数不能超过 50 次
5. 如果同一个用户有多笔退款,建议分不同批次进行退款,避免并发退款导致退款失败
**入参说明**
| 参数名 | 类型 | 必填 | 默认值| 说明 | 支持平台 |
| :-----------: | :----:|:---------------------:| :----:| :----------: |:------:|
| outTradeNo | String| 和 transactionId 二选一 | - | 商户订单号 | - |
| transactionId | String| 和 outTradeNo 二选一 | - | 平台订单号 | - |
| outRefundNo | String| 微信支付必填,支付宝支付选填 | - | 商户退款单号 | - |
| totalFee | Number| 微信支付必填 | - | 订单总金额 | - |
| refundFee | Number| 必填 | - | 退款总金额 | 微信支付 |
| refundFeeType | String| 微信支付V3必填 | - | 货币种类 | 微信支付V3 |
| refundDesc | String| 选填 | - | 退款原因 | - |
| notifyUrl | String| 微信支付选填,支付宝不支持 | - | 退款通知 url,支付宝会通知获取支付参数时的通知地址| 微信支付 |
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-----------: | :----: | :----------: | :------: |
| outTradeNo | String | 商户订单号 | - |
| transactionId | String | 平台订单号 | - |
| outRefundNo | String | 商户退款单号 | 微信支付 |
| refundId | String | 平台退款单号 | - |
| refundFee | Number | 退款总金额 | - |
| cashRefundFee | Number | 现金退款金额 | - |
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.refund({
outTradeNo: '商户订单号',
outRefundNo: '商户退款单号', // 支付宝可不填此项
totalFee: 1, // 订单总金额,支付宝可不填此项
refundFee: 1, // 退款总金额
})
return res
}
```
### 查询退款
`unipayIns.refundQuery`,提交退款申请后,通过调用该接口查询退款状态。
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 支持平台 |
| :-----------: | :----: |:-----------------------------------------:| :----: | :--------------------------------------------------------------------------------: | :------: |
| outTradeNo | String | 微信支付V2四选一,支付宝和 transactionId 二选一 | - | 商户订单号 | - |
| transactionId | String | 微信支付V2四选一,支付宝和 outTradeNo 二选一 | - | 平台订单号 | - |
| outRefundNo | String | 微信支付V3必填,微信支付V2四选一,支付宝必填 | - | 商户退款单号 | - |
| refundId | String | 微信支付V2四选一 | - | 平台退款单号 | 微信支付 |
| offset | Number | 微信支付V2选填 | - | 偏移量,当部分退款次数超过 10 次时可使用,表示返回的查询结果从这个偏移量开始取记录 | - |
**注意**
- `outRefundNo`为使用支付宝请求退款接口时,传入的商户退款单号。如果在退款请求时未传入,则该值为创建交易时的商户订单号即`outTradeNo`
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :------------: | :-----------------------------: |:--------------------:|:-----:|
| outTradeNo | String | 商户订单号 | - |
| transactionId | String | 平台订单号 | - |
| totalFee | Number | 订单金额 | - |
| refundId | String | 平台退款单号,仅支付宝、微信支付V3返回 | - |
| refundFee | Number | 退款总金额 | - |
| refundDesc | String | 退款理由 | - |
| refundList | Array&lt;refundItem&gt; | 分笔退款信息,仅微信支付V2返回 | 微信支付V2 |
| refundRoyaltys | Array&lt;refundRoyaltysItem&gt; | 退分账明细信息,仅支付宝返回 | 支付宝支付 |
**refundItem 说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-----------------: | :---------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: |
| outRefundNo | String | 商户退款单号 | - |
| refundId | String | 平台退款单号 | - |
| refundChannel | String | 退款渠道,ORIGINAL—原路退款,BALANCE—退回到余额,OTHER_BALANCE—原账户异常退到其他余额账户,OTHER_BANKCARD—原银行卡异常退到其他银行卡 | |
| refundFee | Number | 申请退款金额 | - |
| settlementRefundFee | Number | 退款金额,退款金额=申请退款金额-非充值代金券退款金额,退款金额&lt;=申请退款金额 | |
| refundStatus | String | 退款状态,SUCCESS—退款成功,REFUNDCLOSE—退款关闭,PROCESSING—退款处理中,CHANGE—退款异常,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台(pay.weixin.qq.com)-交易中心,手动处理此笔退款。 | |
| couponRefundFee | Number | 总代金券退款金额 | - |
| couponRefundCount | Number | 退款代金券使用数量 | - |
| refundAccount | String | 退款资金来源 | - |
| refundRecvAccout | String | 退款入账账户 | - |
| refundSuccessTime | String | 退款成功时间 | - |
| couponList | Array&lt;couponItem&gt; | 分笔退款信息 | - |
**couponItem 说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-------------: | :----: | :----------------: | :------: |
| couponType | String | 代金券类型 | - |
| couponRefundId | String | 退款代金券 ID | - |
| couponRefundFee | String | 单个代金券退款金额 | - |
**refundRoyaltysItem 说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :---------: | :----: | :-------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: |
| fundChannel | String | 交易使用的资金渠道 | - |
| bankCode | String | 银行卡支付时的银行代码 | - |
| amount | Number | 该支付工具类型所使用的金额 | - |
| realAmount | Number | 渠道实际付款金额 | - |
| fundType | String | 渠道所使用的资金类型,目前只在资金渠道(fund_channel)是银行卡渠道(BANKCARD)的情况下才返回该信息(DEBIT_CARD:借记卡,CREDIT_CARD:信用卡,MIXED_CARD:借贷合一卡) | - |
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.refundQuery({
outTradeNo: '商户订单号',
outRefundNo: '商户退款单号', // 支付宝必填
})
return res
}
```
### 下载交易账单
`unipayIns.downloadBill`,商户可以通过该接口下载历史交易清单。**仅微信支付支持**
**注意:**
1. 微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账单中,跟原支付单订单号一致;
2. 微信在次日 9 点启动生成前一天的对账单,建议商户 10 点后再获取;
3. 对账单中涉及金额的字段单位为“元”。
4. 对账单接口只能下载三个月以内的账单。
5. 对账单是以商户号纬度来生成的,如一个商户号与多个 appid 有绑定关系,则使用其中任何一个 appid 都可以请求下载对账单。对账单中的 appid 取自交易时候提交的 appid,与请求下载对账单时使用的 appid 无关。
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 支持平台 |
| :------: | :----: | :--: | :----: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: |
| billDate | String | 必填 | - | 下载对账单的日期,格式:2014-06-03 | - |
| billType | String | 选填 | ALL | ALL(默认值),返回当日所有订单信息(不含充值退款订单),SUCCESS,返回当日成功支付的订单(不含充值退款订单),REFUND,返回当日退款订单(不含充值退款订单),RECHARGE_REFUND,返回当日充值退款订单 | - |
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-----: | :----: | :----------------------: | :------: |
| content | String | 文本表格的方式返回的数据 | - |
`content`示例如下
**当日所有订单**
交易时间,公众账号 ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,微信退款单号,商户退款单号,退款金额, 代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率
**当日成功支付的订单**
交易时间,公众账号 ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额, 代金券或立减优惠金额,商品名称,商户数据包,手续费,费率
**当日退款的订单**
交易时间,公众账号 ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额, 代金券或立减优惠金额,退款申请时间,退款成功时间,微信退款单号,商户退款单号,退款金额, 代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率
从第二行起,为数据记录,各参数以逗号分隔,参数前增加`符号,为标准键盘 1 左边键的字符,字段顺序与表头一致。
倒数第二行为订单统计标题,最后一行为统计数据
总交易单数,总交易额,总退款金额,总代金券或立减优惠退款金额,手续费总金额
举例如下:
```
交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,微信退款单号,商户退款单号,退款金额,代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率
`2014-11-10 16:33:45,`wx2421b1c4370ec43b,`10000100,`0,`1000,`1001690740201411100005734289,`1415640626,`085e9858e3ba5186aafcbaed1,`MICROPAY,`SUCCESS,`OTHERS,`CNY,`0.01,`0.0,`0,`0,`0,`0,`,`,`被扫支付测试,`订单额外描述,`0,`0.60%
`2014-11-10 16:46:14,`wx2421b1c4370ec43b,`10000100,`0,`1000,`1002780740201411100005729794,`1415635270,`085e9858e90ca40c0b5aee463,`MICROPAY,`SUCCESS,`OTHERS,`CNY,`0.01,`0.0,`0,`0,`0,`0,`,`,`被扫支付测试,`订单额外描述,`0,`0.60%
总交易单数,总交易额,总退款金额,总代金券或立减优惠退款金额,手续费总金额
`2,`0.02,`0.0,`0.0,`0
```
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.downloadBill({
billDate: '20200202',
})
return res
}
```
### 下载资金账单
`unipayIns.downloadFundflow`,商户可以通过该接口下载自 2017 年 6 月 1 日起的历史资金流水账单。**仅微信支持**
**说明:**
1. 资金账单中的数据反映的是商户微信账户资金变动情况;
2. 当日账单在次日上午 9 点开始生成,建议商户在上午 10 点以后获取;
3. 资金账单中涉及金额的字段单位为“元”。
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 支持平台 |
| :---------: | :----: | :--: | :----: | :---------------------------------------------------------------------: | :------: |
| billDate | String | 必填 | - | 下载对账单的日期,格式:2014-06-03 | - |
| accountType | String | 选填 | Basic | 账单的资金来源账户:Basic 基本账户,Operation 运营账户,Fees 手续费账户 | - |
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-----: | :----: | :----------------------: | :------: |
| content | String | 文本表格的方式返回的数据 | - |
`content`示例如下
- 第一行为表头
记账时间,微信支付业务单号,资金流水单号,业务名称,业务类型,收支类型,收支金额(元),账户结余(元),资金变更提交申请人,备注,业务凭证号
- 从第二行起,为资金流水数据,各参数以逗号分隔,参数前增加`符号,为标准键盘 1 左边键的字符,字段顺序与表头一致
- 倒数第二行为资金账单统计标题
资金流水总笔数,收入笔数,收入金额,支出笔数,支出金额
- 最后一行为统计数据
账单示例如下:
```
记账时间,微信支付业务单号,资金流水单号,业务名称,业务类型,收支类型,收支金额(元),账户结余(元),资金变更提交申请人,备注,业务凭证号
`2018-02-01 04:21:23,`50000305742018020103387128253,`1900009231201802015884652186,`退款,`退款,`支出,`0.02,`0.17,`system,`缺货,`REF4200000068201801293084726067
资金流水总笔数,收入笔数,收入金额,支出笔数,支出金额
`20.0,`17.0,`0.35,`3.0,`0.18
```
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.downloadFundflow({
billDate: '20200202',
})
return res
}
```
### 支付结果通知处理
**注意:支付宝在非全量退款时也会发送通知到支付时设置的notify_url**
`unipayIns.verifyPaymentNotify`,用于在使用云函数 Url 化的云函数内检验并处理支付结果。
**入参说明**
只接收对应云函数的`event`作为参数
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-----------: | :----: | :-------------------------------------------------: | :------: |
| totalFee | Number | 订单总金额 | - |
| cashFee | Number | 现金支付金额 | - |
| feeType | String | 货币类别 | - |
| outTradeNo | String | 商户订单号 | - |
| transactionId | String | 平台订单号 | - |
| timeEnd | String | 支付完成时间,格式为 yyyyMMddHHmmss | - |
| openid | String | 用户 id | - |
| returnCode | String | 值 SUCCESS 时为支付成功,通常需要校验订单金额等参数 | - |
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.verifyPaymentNotify(event)
// 处理完毕其他业务
// 注意如果处理成功需要严格按照下面的格式进行返回,否则厂商会持续通知
// 微信支付V3处理成功之后
return {
mpserverlessComposedResponse: true,
statusCode: 200,
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
code: 'SUCCESS',
message: '成功'
})
}
// 微信支付V2处理成功之后
return {
mpserverlessComposedResponse: true,
statusCode: 200,
headers: {
'content-type': 'text/xml;charset=utf-8'
},
body: `<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>`
}
// 支付宝处理成功后
return {
mpserverlessComposedResponse: true,
statusCode: 200,
headers: {
'content-type': 'text/plain'
},
body: "success"
}
}
```
### 退款结果通知@verify-refund-notify
**注意:支付宝在非全量退款时才会发送通知,通知地址为支付时设置的notify_url**
> uni-pay 1.0.17版本起新增对支付宝退款结果通知的支持
`unipayIns.verifyRefundNotify`,用于在使用云函数 Url 化的云函数内检验并处理支付结果。
**入参说明**
只接收对应云函数的`event`作为参数
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台|
| :-----------------: | :----:| :---------------------------------------------------: | :------:|
| totalFee | Number| 订单总金额 | - |
| refundFee | Number| 申请退款金额 | - |
| settlementTotalFee | Number| 应结订单金额,支付宝不返回 | - |
| settlementRefundFee | Number| 退款金额,支付宝不返回 | - |
| outTradeNo | String| 商户订单号 | - |
| transactionId | String| 平台订单号 | - |
| refundId | String| 平台退款单号,支付宝不返回 | - |
| outRefundNo | String| 商户退款单号 | - |
| refundStatus | String| SUCCESS-退款成功,CHANGE-退款异常,REFUNDCLOSE—退款关闭| - |
| refundAccount | String| 退款资金来源,支付宝不返回 | - |
| refundRecvAccout | String| 退款入账账户,支付宝不返回 | - |
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.verifyRefundNotify(event)
// 注意如果处理成功需要严格按照下面的格式进行返回,否则厂商会持续通知
// 微信处理成功之后
return {
mpserverlessComposedResponse: true,
statusCode: 200,
headers: {
'content-type': 'text/xml;charset=utf-8'
},
body: `<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>`
}
// 支付宝处理成功后
return {
mpserverlessComposedResponse: true,
statusCode: 200,
headers: {
'content-type': 'text/plain'
},
body: "success"
}
}
```
### 获取通知类型@check-notify-type
> 新增于 uni-id 1.0.17
`unipayIns.checkNotifyType`,用于在使用云函数 Url 化的云函数内检验当前通知的类型。由于支付宝支付在非全量退款时会调用支付时设置的notify_url,可以使用此接口在调用校验通知之前判断通知类型
**入参说明**
只接收对应云函数的`event`作为参数
**返回值说明**
此接口会返回一个字符串,可能的值如下
- `refund`:当前是一个退款通知
- `payment`:当前是一个支付结果通知
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.checkNotifyType(event)
if(res === 'refund') {
// 退款通知
} else if(res === 'payment') {
// 支付结果通知
}
}
```
### 苹果内购-校验支付凭证@verifyReceipt
`unipayIns.verifyReceipt`, 校验iap支付凭证返回交易信息。
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 支持平台 |
| :-----------: | :----: | :---------------------: | :----: | :--------: | :------: |
| receiptData | String | 是 | - | 支付凭证 | 苹果内购支付 |
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :----------------: | :----: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: |
| transactionId | String | 交易标示 | - |
| tradeState | String | 订单状态 ,微信支付: SUCCESS—支付成功,REFUND—转入退款,NOTPAY—未支付,PAYERROR--支付失败(其他原因,如银行返回失败),SUCCESS(交易支付成功)。 | - |
| totalFee | Number | 标价金额 ,单位:分 | - |
| settlementTotalFee | Number | 应结订单金额,单位:分 | - |
| receipt | Object | 收据信息 | - |
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.verifyReceipt({
receiptData: 'transactionReceipt',
})
return res
}
```
### 微信小程序虚拟支付@wxpayVirtualApi
[前往微信小程序虚拟支付API文档](./wxpay-virtual.md)
\ No newline at end of file
## 简介
此文档是微信小程序虚拟支付的云端API补充,使用此文档的API需要先安装 [uni-pay](./uni-app.md) 插件才能使用,仅 uni-app 项目可用,uni-app-x 项目暂不支持微信支付,也不支持微信小程序虚拟支付
## Api 列表
提示:微信虚拟支付有很多API,当前仅支持以下几个常用的API,后面会陆续增加其他API
### 充值代币、道具直购
`充值代币``道具直购` 涉及到前端API,需要搭配 `uni-pay` 组件使用,详情见 [充值代币文档](./uni-app.md#short-series-coin)[道具直购文档](./uni-app.md#short-series-goods)
### 查询用户代币余额@queryUserBalance
**接口名**
`queryUserBalance`
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
| :-----------: | :----: | :---:| :----: |:--------: |
| openid | String | 是 | - | 用户的openid |
| userIp | String | 是 | - | 用户的ip地址 |
**返回值说明**
| 参数名 | 类型 | 说明 |
| :-----------: | :----: | :--------: |
| balance | Number | 代币总余额,包括有价和赠送部分|
| presentBalance | Number | 赠送账户的代币余额 |
| sumSave | Number | 累计有价货币充值数量 |
| sumPresent | Number | 累计赠送无价货币数量 |
| sumBalance | Number | 历史总增加的代币金额 |
| sumCost | Number | 历史总消耗代币金额 |
| firstSaveFlag | Boolean| 是否满足首充活动标记。0:不满足。1:满足 |
**使用示例**
```js
// 引入 uni-pay-co 云对象
const uniPayCo = uniCloud.importObject("uni-pay-co");
// 请求微信虚拟支付API
let res = await uniPayCo.requestWxpayVirtualApi({
method: "queryUserBalance", // 请求方法
// 请求参数
data: {
openid: "", // 用户openid
userIp: "", // 用户IP
}
});
console.log('res: ', res);
```
### 扣减代币(一般用于代币支付)@currencyPay
**接口名**
`currencyPay`
**注意**
此API需要用到用户的 `sessionKey`,如果用户长时间没有活跃过小程序,则无法请求扣减代币接口,会报用户sessionKey不存在或已过期,请重新登录的错误
微信小程序虚拟支付退款后,如果退款的是代币充值订单,则退款成功后原本用户充值的代币不会自动扣减,需要执行此API才能扣减,而用户长时间没有活跃过小程序,则无法请求扣减代币接口,因此需要注意退款时间,时间相隔太长可能会导致出现无法扣减代币的尴尬情况
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
| :-----------: | :----: | :---:| :----: |:--------: |
| openid | String | 是 | - | 用户的openid |
| userIp | String | 是 | - | 用户的ip地址 |
| amount | Number | 是 | - | 支付的代币数量 |
| outTradeNo | String | 是 | - | 订单号 |
| deviceType | Number | 是 | - | 平台类型1-安卓 2-苹果 |
| payitem | String | 否 | - | 物品信息。记录到账户流水中。如:[{"productid":"物品id", "unit_price": 单价, "quantity": 数量}],注意只能是json字符串格式 |
| remark | String | 否 | - | 备注 |
| sessionKey | String | 否 | - | 用户的sessionKey,不传会尝试自动获取 |
**返回值说明**
| 参数名 | 类型 | 说明 |
| :-----------: | :----: | :--------: |
| balance | Number | 总余额,包括有价和赠送部分|
| usedPresentAmount | Number | 使用赠送部分的代币数量 |
| outTradeNo | String | 订单号原样返回 |
**使用示例**
```js
// 引入 uni-pay-co 云对象
const uniPayCo = uniCloud.importObject("uni-pay-co");
let outTradeNo = "test-" + Date.now(); // 商户订单号
// 请求微信虚拟支付API
let res = await uniPayCo.requestWxpayVirtualApi({
method: "currencyPay", // 请求方法
// 请求参数
data: {
openid: "", // 用户openid
userIp: "", // 用户IP
amount: 1, // 扣减的代币数量
outTradeNo, // 商户订单号
payitem: JSON.stringify([{ "productid": "test001", "unit_price": 1, "quantity": 1 }]),
remark: "备注",
deviceType: 1, // 平台类型1-安卓 仅支持传1
}
});
console.log('res: ', res);
```
### 代币支付退款(currencyPay接口的逆操作)@cancelCurrencyPay
**接口名**
`cancelCurrencyPay`
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
| :-----------: | :----: | :---:| :----: |:--------: |
| openid | String | 是 | - | 用户的openid |
| userIp | String | 是 | - | 用户的ip地址 |
| amount | Number | 是 | - | 退款金额 |
| outTradeNo | String | 是 | - | 订单号 |
| outRefundNo | String | 是 | - | 本次退款单的单号 |
| deviceType | Number | 是 | - | 平台类型1-安卓 2-苹果 |
**返回值说明**
| 参数名 | 类型 | 说明 |
| :-----------: | :----: | :--------: |
| outRefundNo | String | 退款订单号|
**使用示例**
```js
// 引入 uni-pay-co 云对象
const uniPayCo = uniCloud.importObject("uni-pay-co");
let outTradeNo = "";
let lastFourDigits = Date.now().toString().substr(-4);
// 请求微信虚拟支付API
let res = await uniPayCo.requestWxpayVirtualApi({
method: "cancelCurrencyPay", // 请求方法
// 请求参数
data: {
openid: "", // 用户openid
userIp: "", // 用户IP
amount: 1, // 撤回扣减的代币数量
outTradeNo, // 商户订单号
outRefundNo: `${outTradeNo}-${lastFourDigits}`,
deviceType: 1, // 平台类型1-安卓 仅支持传1
}
});
console.log('res: ', res);
```
### 代币赠送(currencyPay接口的逆操作)@presentCurrency
**接口名**
`presentCurrency`
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
| :-----------: | :----: | :---:| :----: |:--------: |
| openid | String | 是 | - | 用户的openid |
| userIp | String | 是 | - | 用户的ip地址 |
| amount | Number | 是 | - | 退款金额 |
| outTradeNo | String | 是 | - | 订单号 |
| deviceType | Number | 是 | - | 平台类型1-安卓 2-苹果 |
**返回值说明**
| 参数名 | 类型 | 说明 |
| :-----------: | :----: | :--------: |
| balance | String | 赠送后用户的代币余额|
| presentBalance | String | 用户收到的总赠送金额|
| outTradeNo | String | 赠送单号|
**使用示例**
```js
// 引入 uni-pay-co 云对象
const uniPayCo = uniCloud.importObject("uni-pay-co");
let outTradeNo = "test-" + Date.now(); // 商户订单号
// 请求微信虚拟支付API
let res = await uniPayCo.requestWxpayVirtualApi({
method: "presentCurrency", // 请求方法
// 请求参数
data: {
openid: "", // 用户openid
userIp: "", // 用户IP
amount: 1, // 赠送用户代币数量
outTradeNo, // 商户订单号
deviceType: 1, // 平台类型1-安卓 仅支持传1
}
});
console.log('res: ', res);
```
\ No newline at end of file
> 本文档为`uni-pay 1.x`版本文档。适用于老项目。
> 新项目请另行查阅 [uni-pay 2.x 版本文档](uni-pay.md)。
## 简介 文件已搬家,[前往新文档](./uni-pay/uni-pay-common.md)
`unipay``uniCloud`开发者提供了简单、易用、统一的支付能力封装。让开发者无需研究支付宝、微信等支付平台的后端开发、无需为它们编写不同代码,拿来即用,屏蔽差异。 <script>
export default {
`uni-app`前端已经封装的全端支付 api `uni.requestPayment`,现在服务端也封装好了`unipay for uniCloud`,从此开发者可以极快的完成前后一体的支付业务。 data() {
return {
目前已封装 App 端(微信支付和支付宝支付)、微信小程序、支付宝小程序的支付能力。
};
`unipay`是开源 sdk,可放心使用。本插件还包含示例工程,配置自己在微信和支付宝申请的相关配置后即可运行。 },
created(){
为了更好的体验支付流程可以在插件市场导入`unipay`的示例项目快速体验,[插件市场 unipay](https://ext.dcloud.net.cn/plugin?id=1835)[gitee仓库 unipay公共模块](https://gitee.com/dcloud/uniPay.git) this.init();
},
插件市场还有基于uniPay再次封装的模板,前端支付、管理端订单管理均已写好,拿去就用,见:[BaseCloud - 统一下单支付业务模块](https://ext.dcloud.net.cn/plugin?id=2668) mounted() {
**须知** },
methods: {
- unipay 对入参和返回值均做了驼峰转化,开发者在对照微信支付或者支付宝支付对应的文档时需要注意。 // 页面数据初始化函数
- 特殊参数`appId``mchId`需注意大小写 init(options = {}){
- 所有金额被统一为以分为单位(避免浮点误差) let url = window.location.href;
- 为避免无关参数干扰此文档仅列举必填参数,其余参数请参照[微信支付-小程序](https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1)[微信支付-App](https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1)[支付宝支付-小程序](https://opendocs.alipay.com/apis/api_1/alipay.trade.create)[支付宝支付-App](https://opendocs.alipay.com/apis/api_1/alipay.trade.app.pay) let newUrl = url.replace("unipay.html", "uni-pay/uni-pay-common.html");
- 微信支付沙箱环境不支持小程序支付,另外此沙箱环境只可以跑微信提供的测试用例不可以随意测试 window.location.href = newUrl;
- 无论是微信还是支付宝,沙箱环境都不确保稳定,如果使用沙箱的过程中遇到疑难问题建议切换成正式环境测试 }
}
**问题排查指引** };
</script>
- [支付宝支付蚂蚁技术支持](https://openclub.alipay.com/club/history/read/7695) \ No newline at end of file
## 引入 unipay
开发者可以自行选择是从插件市场导入还是从 npm 安装,引入方式略有不同,请看下面示例
```js
// 插件市场导入
const unipay = require('uni-pay')
// npm安装
const unipay = require('@dcloudio/unipay')
```
**注意**
- 插件市场导入的用法请参考[云函数公用模块](https://uniapp.dcloud.net.cn/uniCloud/cf-common)
## 初始化@init
进行初始化操作返回 unipay 实例
### 微信支付V3
> 新增于 ```uni-pay 1.1.0```
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|:----------------------:| :-----: |:--:|:----------------:|:----------------------------:|
| appId | String | 是 | - | 当前应用在对应支付平台的 appId |
| mchId | String | 是 | - | 商户号 |
| v3Key | String | 是 | - | API v3 密钥 |
| appCertPath | String | 是 | - | 商户 API 证书文件路径(文件路径与字符串二选一) |
| appCertContent | String | 是 | - | 商户 API 证书字符串(文件路径与字符串二选一) |
| appPrivateKeyPath | String | 是 | - | 商户 API 私钥文件路径(文件路径与字符串二选一) |
| appPrivateKeyContent | String | 是 | - | 商户 API 私钥字符串(文件路径与字符串二选一) |
| timeout | Number | 否 | 5000 | 请求超时时间,单位:毫秒 |
```js
const path = require('path'); // 引入内置的path模块
const unipayIns = unipay.initWeixinV3({
appId: 'your appId',
mchId: 'your mchId',
v3Key: 'you parterner key',
appCertPath: path.resolve(__dirname, 'your appCertPath'),
// appCertContent: "",
appPrivateKeyPath: path.resolve(__dirname, 'your appPrivateKeyPath'),
// appPrivateKeyContent: "",
})
```
**说明**
证书字符串与私钥字符串格式要求:
- 证书字符串不应包含`-----BEGIN CERTIFICATE-----``-----END CERTIFICATE-----`,并且证书内容不能换行
- 私钥字符串不应包含`-----BEGIN PRIVATE KEY-----``-----END PRIVATE KEY-----`,并且私钥内容不能换行
例:
```
// 证书内容
-----BEGIN CERTIFICATE-----
your certificate content...
-----END CERTIFICATE-----
// 证书字符串
appCertContent = 'your certificate content...'
```
### 微信支付v2
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|:---------:| :-----: | :----:|:-----------------:|:----------------------------:|
| appId | String | 是 | - | 当前应用在对应支付平台的 appId |
| mchId | String | 是 | - | 商户号 |
| subAppId | String | 否 | - | 子商户appId |
| subMchId | String | 否 | - | 子商户号 |
| key | String | 是 | - | 支付商户 key (API密钥) |
| pfx | String&#124;Buffer| 使用退款功能必填| - | 微信支付商户 API 证书,主要用于退款 |
| timeout | Number | 否 | 5000 | 请求超时时间,单位:毫秒 |
| signType | String | 否 | MD5 | 签名类型 |
| sandbox | Boolean | 否 | false | 是否启用沙箱环境 |
```js
const unipayIns = unipay.initWeixin({
appId: 'your appId',
mchId: 'your mchId',
key: 'you parterner key',
pfx: fs.readFileSync('/path/to/your/pfxfile'), // 建议以p12文件绝对路径进行读取,使用微信退款时需要
})
// 以证书放在云函数index.js同级的cert目录下为例,index.js内可以按照下面这个写
const fs = require('fs');
const path = require('path');
const unipayIns = unipay.initWeixin({
appId: 'your appId',
mchId: 'your mchId',
key: 'you parterner key',
pfx: fs.readFileSync(path.resolve(__dirname, 'cert/xxx.p12'))
})
```
### 支付宝支付
**入参说明**
| 参数名 | 类型 | 必填| 默认值 | 说明 |
| :-------------: | :-----: | :--:| :--------------------------------------------------:| :------------------------------------:|
| appId | String | 是 | - | 当前应用在对应支付平台的 appId |
| mchId | String | 是| - | 商户号 |
| privateKey | String | 是 | - | 应用私钥字符串 |
| alipayPublicKey | String | 否 | - | 支付宝公钥,验签使用 |
| keyType | String | 否 | PKCS8 | 应用私钥字符串类型 |
| timeout | Number | 否 | 5000 | 请求超时时间,单位:毫秒 |
| signType | String | 否 | RSA2 | 签名类型 |
| sandbox | Boolean | 否 | false | 是否启用沙箱环境 |
| alipayRootCertPath | String | 否 | - | `1.0.6+`,支付宝根证书文件路径 |
| appCertPath | String | 否 | - | `1.0.6+`,应用公钥证书文件路径 |
| alipayPublicCertPath| String | 否 | - | `1.0.6+`,支付宝公钥证书文件路径 |
```js
const unipayIns = unipay.initAlipay({
appId: 'your appId',
mchId: 'your mchId',
privateKey: 'your privateKey',
// 如果不使用证书(普通公钥模式)需要alipayPublicKey
alipayPublicKey: 'you alipayPublicKey', // 使用支付时需传递此值做返回结果验签
// 如果使用证书需要传alipayRootCertPath、appCertPath、alipayPublicCertPath
alipayRootCertPath: path.join(__dirname,'../fixtures/alipayRootCert.crt'),
appCertPath: path.join(__dirname,'../fixtures/appCertPublicKey.crt'),
alipayPublicCertPath: path.join(__dirname,'../fixtures/alipayCertPublicKey_RSA2.crt'),
})
```
**常见问题**
- 支付宝支付时遇到`error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag`类似的错误时请确认一下自己的私钥格式,如果不是PKCS8需要在初始化时传入keyType参数,值为对应的私钥格式
### 苹果内购支付
**入参说明**
| 参数名 | 类型 | 必填| 默认值 | 说明 |
| :-------------: | :-----: | :--:| :--------------------------------------------------:| :------------------------------------:|
| sandbox | Boolean | 否 | false | 是否启用沙箱环境 |
| password | String | 否 |- | App 专用共享密钥,App 专用共享密钥是用于接收此 App 自动续期订阅收据的唯一代码。如果您要将此 App 转让给其他开发者或不想公开主共享密钥,建议使用 App 专用共享密钥。非自动续订场景不需要此参数|
| timeout | Number | 否 | 5000 | 请求超时时间,单位:毫秒 |
```js
const unipayIns = unipay.initAppleIapPayment({
sandbox: true,
password: 'your password',
})
```
## Api 列表
### 获取支付参数
`unipayIns.getOrderInfo`
**入参说明**
| 参数名 | 类型 | 必填| 默认值 | 说明| 支持平台 |
| :--------: | :----: | :--------------------------------:| :----: | :------------------------------------------------------------------------:| :----------------------: |
| openid | String |支付宝小程序、微信小程序必填,App端支付不需要| - |通过对应 [uni-id](uni-id/summary.md) 接口进行获取,服务商模式应使用子商户获取的openid| 支付宝小程序、微信小程序 |
| subject | String |支付宝支付必填,微信支付时忽略此项| - |订单标题| 支付宝支付 |
| body | String |微信支付必填| - |商品描述| 微信支付 |
| outTradeNo | String |必填 | - |商户订单号,有长度限制(微信支付为32字符以内,支付宝为64字符以内)、只能包含字母、数字、下划线;需保证在商户端不重复| |
| totalFee | Number |必填 | - |订单金额,单位:分| 支付宝小程序、微信小程序 |
| notifyUrl | String |必填 | - |支付结果通知地址,**需要注意支付宝支付时退款也会通知到此地址,务必处理好自己的业务逻辑**| |
| spbillCreateIp| String |必填 | - |客户端IP,云函数内可以通过`context.CLIENTIP`获取|- |
| tradeType | String| 是 | - | 交易类型;见下方 tradeType 的说明 |
| sceneInfo | Object |微信tradeType为MWEB时必填| - |见下方sceneInfo的说明|- |
**注意**
此接口支持直接传微信和支付宝官方文档上的参数,如微信的 `support_fapiao` 参数,转成驼峰 `supportFapiao` 即可
```js
let orderInfo = await unipayIns.getOrderInfo({
...前面参数省略
notifyUrl: 'https://xxx.xx', // 支付结果通知地址
supportFapiao: true
})
```
**tradeType的说明**
tradeType支持以下选项
- JSAPI 适用于:微信公众号支付、微信小程序支付、支付宝小程序支付
- APP 适用于:支付宝、微信APP支付
- NATIVE 适用于:支付宝、微信PC网站扫码支付
- MWEB 适用于:微信h5(非公众号)跳转微信页面支付(`新增于uni-pay 1.0.24`
**sceneInfo的说明**
微信支付tradeType为MWEB时需要传递以下格式的sceneInfo
```js
{
"h5Info": {
"type": "Wap",
"wapUrl": "https://pay.qq.com", // 开发者网站的网址
"wapName": "腾讯充值" // 开发者网站名称
}
}
```
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-------: | :----: | :----: | :--------------------------------------------------------------------: |
| orderInfo | Object | String | 客户端支付所需参数,直接返回给客户端即可,下面会介绍如何搭配客户端使用 |
**使用示例**
tradeType为NATIVE时直接将orderInfo.codeUrl转为二维码使用对应的客户端扫码支付即可
tradeType为MWEB时直接跳转到orderInfo.mwebUrl进行支付即可
```js
// 云函数 - getOrderInfo
exports.main = async function (event,context) {
let orderInfo = await unipayIns.getOrderInfo({
openid: 'user openid',
subject: '订单标题', // 微信支付时不可填写此项
body: '商品描述',
outTradeNo: '商户订单号',
totalFee: 1, // 金额,单位分
notifyUrl: 'https://xxx.xx' // 支付结果通知地址
})
return {
orderInfo
}
}
// 客户端
uniCloud.callFunction({
name: 'getOrderInfo',
success(res) {
uni.requestPayment({
// #ifdef APP-PLUS
provider: selectedProvider, // App端此参数必填,可以通过uni.getProvider获取
// #endif
// #ifdef MP-WEIXIN
...res.result.orderInfo,
// #endif
// #ifdef APP-PLUS || MP-ALIPAY
orderInfo: res.result.orderInfo,
// #endif
...res.result.orderInfo
success(){},
fail(){}
})
}
})
// 二维码支付
uniCloud.callFunction({
name: 'getOrderInfo',
success(res) {
// 以二维码形式展示此内容
console.log(res.result.orderInfo.codeurl)
}
})
```
### 查询订单
`unipayIns.orderQuery`, 根据商户订单号或者平台订单号查询订单信息,主要用于未接收到支付通知时可以使用此接口进行支付结果验证
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 支持平台 |
| :-----------: | :----: | :---------------------: | :----: | :--------: | :------: |
| outTradeNo | String | 和 transactionId 二选一 | - | 商户订单号 | - |
| transactionId | String | 和 outTradeNo 二选一 | - | 平台订单号 | - |
**返回值说明**
| 参数名 | 类型 |说明 | 支持平台 |
| :----------------:| :----:| :---------------------------------------------: |:-------:|
| appId | String|平台分配的应用 ID | 微信支付 |
| mchId | String|商户号,(微信支付文档里面叫商户号:mch_id,支付宝支付叫卖家id:seller_id)| 微信支付 |
| outTradeNo | String|商户订单号 | - |
| transactionId | String|平台订单号 | - |
| tradeState | String| 订单状态,见下方订单状态说明 | |
| totalFee | Number|标价金额 ,单位:分 | - |
| settlementTotalFee| Number|应结订单金额,单位:分 | 支付宝支付 |
| cashFee | Number|现金支付金额,单位:分 | - |
**订单状态**
微信支付:
SUCCESS—支付成功
REFUND—转入退款
NOTPAY—未支付
CLOSED—已关闭
REVOKED—已撤销(刷卡支付)
USERPAYING--用户支付中
PAYERROR--支付失败(其他原因,如银行返回失败)。
支付宝支付:
USERPAYING(交易创建,等待买家付款)
CLOSED(未付款交易超时关闭,或支付完成后全额退款)
SUCCESS(交易支付成功)
FINISHED(交易结束,不可退款)
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.orderQuery({
outTradeNo: 'outTradeNo',
})
return res
}
```
### 关闭订单
`unipayIns.closeOrder`,用于交易创建后,用户在一定时间内未进行支付,可调用该接口直接将未付款的交易进行关闭,避免重复支付。
**注意**
- 微信支付:订单生成后不能马上调用关单接口,最短调用时间间隔为 5 分钟。
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 支持平台 |
| :-----------: | :----: | :-------------------------------------------------: | :----: | :--------: | :--------: |
| outTradeNo | String | 使用微信时必填,使用支付宝时和 transactionId 二选一 | - | 商户订单号 | - |
| transactionId | String | 使用支付宝时和 outTradeNo 二选一 | - | 平台订单号 | 支付宝支付 |
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-----------: | :----: | :---------------: |:------:|
| appId | String | 平台分配的应用 ID | 微信支付V2 |
| mchId | String | 商户号 | 微信支付V2 |
| outTradeNo | String | 商户订单号 | 支付宝支付 |
| transactionId | String | 平台订单号 | 支付宝支付 |
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.closeOrder({
outTradeNo: 'outTradeNo',
})
return res
}
```
### 撤销订单
`unipayIns.cancelOrder`**此接口仅支付宝支持**,支付交易返回失败或支付系统超时,调用该接口撤销交易。如果此订单用户支付失败,支付宝系统会将此订单关闭;如果用户支付成功,支付宝系统会将此订单资金退还给用户。 注意:只有发生支付系统超时或者支付结果未知时可调用撤销,其他正常支付的单如需实现相同功能请调用申请退款 API。提交支付交易后调用【查询订单 API】,没有明确的支付结果再调用【撤销订单 API】。
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 支持平台 |
| :-----------: | :----: | :---------------------: | :----: | :--------: | :--------: |
| outTradeNo | String | 和 transactionId 二选一 | - | 商户订单号 | 支付宝支付 |
| transactionId | String | 和 outTradeNo 二选一 | - | 平台订单号 | 支付宝支付 |
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-----------: | :----: | :--------: | :--------: |
| outTradeNo | String | 商户订单号 | 支付宝支付 |
| transactionId | String | 平台订单号 | 支付宝支付 |
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.cancelOrder({
outTradeNo: 'outTradeNo',
})
return res
}
```
### 申请退款
`unipayIns.refund`,当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家。
**微信支付注意事项**
1. 交易时间超过一年的订单无法提交退款
2. 微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号
3. 请求频率限制:150qps,即每秒钟正常的申请退款请求次数不超过 150 次,错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过 6 次
4. 每个支付订单的部分退款次数不能超过 50 次
5. 如果同一个用户有多笔退款,建议分不同批次进行退款,避免并发退款导致退款失败
**入参说明**
| 参数名 | 类型 | 必填 | 默认值| 说明 | 支持平台 |
| :-----------: | :----:|:---------------------:| :----:| :----------: |:------:|
| outTradeNo | String| 和 transactionId 二选一 | - | 商户订单号 | - |
| transactionId | String| 和 outTradeNo 二选一 | - | 平台订单号 | - |
| outRefundNo | String| 微信支付必填,支付宝支付选填 | - | 商户退款单号 | - |
| totalFee | Number| 微信支付必填 | - | 订单总金额 | - |
| refundFee | Number| 必填 | - | 退款总金额 | 微信支付 |
| refundFeeType | String| 微信支付V3必填 | - | 货币种类 | 微信支付V3 |
| refundDesc | String| 选填 | - | 退款原因 | - |
| notifyUrl | String| 微信支付选填,支付宝不支持 | - | 退款通知 url,支付宝会通知获取支付参数时的通知地址| 微信支付 |
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-----------: | :----: | :----------: | :------: |
| outTradeNo | String | 商户订单号 | - |
| transactionId | String | 平台订单号 | - |
| outRefundNo | String | 商户退款单号 | 微信支付 |
| refundId | String | 平台退款单号 | - |
| refundFee | Number | 退款总金额 | - |
| cashRefundFee | Number | 现金退款金额 | - |
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.refund({
outTradeNo: '商户订单号',
outRefundNo: '商户退款单号', // 支付宝可不填此项
totalFee: 1, // 订单总金额,支付宝可不填此项
refundFee: 1, // 退款总金额
})
return res
}
```
### 查询退款
`unipayIns.refundQuery`,提交退款申请后,通过调用该接口查询退款状态。
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 支持平台 |
| :-----------: | :----: |:-----------------------------------------:| :----: | :--------------------------------------------------------------------------------: | :------: |
| outTradeNo | String | 微信支付V2四选一,支付宝和 transactionId 二选一 | - | 商户订单号 | - |
| transactionId | String | 微信支付V2四选一,支付宝和 outTradeNo 二选一 | - | 平台订单号 | - |
| outRefundNo | String | 微信支付V3必填,微信支付V2四选一,支付宝必填 | - | 商户退款单号 | - |
| refundId | String | 微信支付V2四选一 | - | 平台退款单号 | 微信支付 |
| offset | Number | 微信支付V2选填 | - | 偏移量,当部分退款次数超过 10 次时可使用,表示返回的查询结果从这个偏移量开始取记录 | - |
**注意**
- `outRefundNo`为使用支付宝请求退款接口时,传入的商户退款单号。如果在退款请求时未传入,则该值为创建交易时的商户订单号即`outTradeNo`
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :------------: | :-----------------------------: |:--------------------:|:-----:|
| outTradeNo | String | 商户订单号 | - |
| transactionId | String | 平台订单号 | - |
| totalFee | Number | 订单金额 | - |
| refundId | String | 平台退款单号,仅支付宝、微信支付V3返回 | - |
| refundFee | Number | 退款总金额 | - |
| refundDesc | String | 退款理由 | - |
| refundList | Array&lt;refundItem&gt; | 分笔退款信息,仅微信支付V2返回 | 微信支付V2 |
| refundRoyaltys | Array&lt;refundRoyaltysItem&gt; | 退分账明细信息,仅支付宝返回 | 支付宝支付 |
**refundItem 说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-----------------: | :---------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: |
| outRefundNo | String | 商户退款单号 | - |
| refundId | String | 平台退款单号 | - |
| refundChannel | String | 退款渠道,ORIGINAL—原路退款,BALANCE—退回到余额,OTHER_BALANCE—原账户异常退到其他余额账户,OTHER_BANKCARD—原银行卡异常退到其他银行卡 | |
| refundFee | Number | 申请退款金额 | - |
| settlementRefundFee | Number | 退款金额,退款金额=申请退款金额-非充值代金券退款金额,退款金额&lt;=申请退款金额 | |
| refundStatus | String | 退款状态,SUCCESS—退款成功,REFUNDCLOSE—退款关闭,PROCESSING—退款处理中,CHANGE—退款异常,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台(pay.weixin.qq.com)-交易中心,手动处理此笔退款。 | |
| couponRefundFee | Number | 总代金券退款金额 | - |
| couponRefundCount | Number | 退款代金券使用数量 | - |
| refundAccount | String | 退款资金来源 | - |
| refundRecvAccout | String | 退款入账账户 | - |
| refundSuccessTime | String | 退款成功时间 | - |
| couponList | Array&lt;couponItem&gt; | 分笔退款信息 | - |
**couponItem 说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-------------: | :----: | :----------------: | :------: |
| couponType | String | 代金券类型 | - |
| couponRefundId | String | 退款代金券 ID | - |
| couponRefundFee | String | 单个代金券退款金额 | - |
**refundRoyaltysItem 说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :---------: | :----: | :-------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: |
| fundChannel | String | 交易使用的资金渠道 | - |
| bankCode | String | 银行卡支付时的银行代码 | - |
| amount | Number | 该支付工具类型所使用的金额 | - |
| realAmount | Number | 渠道实际付款金额 | - |
| fundType | String | 渠道所使用的资金类型,目前只在资金渠道(fund_channel)是银行卡渠道(BANKCARD)的情况下才返回该信息(DEBIT_CARD:借记卡,CREDIT_CARD:信用卡,MIXED_CARD:借贷合一卡) | - |
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.refundQuery({
outTradeNo: '商户订单号',
outRefundNo: '商户退款单号', // 支付宝必填
})
return res
}
```
### 下载交易账单
`unipayIns.downloadBill`,商户可以通过该接口下载历史交易清单。**仅微信支付支持**
**注意:**
1. 微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账单中,跟原支付单订单号一致;
2. 微信在次日 9 点启动生成前一天的对账单,建议商户 10 点后再获取;
3. 对账单中涉及金额的字段单位为“元”。
4. 对账单接口只能下载三个月以内的账单。
5. 对账单是以商户号纬度来生成的,如一个商户号与多个 appid 有绑定关系,则使用其中任何一个 appid 都可以请求下载对账单。对账单中的 appid 取自交易时候提交的 appid,与请求下载对账单时使用的 appid 无关。
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 支持平台 |
| :------: | :----: | :--: | :----: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: |
| billDate | String | 必填 | - | 下载对账单的日期,格式:2014-06-03 | - |
| billType | String | 选填 | ALL | ALL(默认值),返回当日所有订单信息(不含充值退款订单),SUCCESS,返回当日成功支付的订单(不含充值退款订单),REFUND,返回当日退款订单(不含充值退款订单),RECHARGE_REFUND,返回当日充值退款订单 | - |
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-----: | :----: | :----------------------: | :------: |
| content | String | 文本表格的方式返回的数据 | - |
`content`示例如下
**当日所有订单**
交易时间,公众账号 ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,微信退款单号,商户退款单号,退款金额, 代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率
**当日成功支付的订单**
交易时间,公众账号 ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额, 代金券或立减优惠金额,商品名称,商户数据包,手续费,费率
**当日退款的订单**
交易时间,公众账号 ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额, 代金券或立减优惠金额,退款申请时间,退款成功时间,微信退款单号,商户退款单号,退款金额, 代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率
从第二行起,为数据记录,各参数以逗号分隔,参数前增加`符号,为标准键盘 1 左边键的字符,字段顺序与表头一致。
倒数第二行为订单统计标题,最后一行为统计数据
总交易单数,总交易额,总退款金额,总代金券或立减优惠退款金额,手续费总金额
举例如下:
```
交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,微信退款单号,商户退款单号,退款金额,代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率
`2014-11-10 16:33:45,`wx2421b1c4370ec43b,`10000100,`0,`1000,`1001690740201411100005734289,`1415640626,`085e9858e3ba5186aafcbaed1,`MICROPAY,`SUCCESS,`OTHERS,`CNY,`0.01,`0.0,`0,`0,`0,`0,`,`,`被扫支付测试,`订单额外描述,`0,`0.60%
`2014-11-10 16:46:14,`wx2421b1c4370ec43b,`10000100,`0,`1000,`1002780740201411100005729794,`1415635270,`085e9858e90ca40c0b5aee463,`MICROPAY,`SUCCESS,`OTHERS,`CNY,`0.01,`0.0,`0,`0,`0,`0,`,`,`被扫支付测试,`订单额外描述,`0,`0.60%
总交易单数,总交易额,总退款金额,总代金券或立减优惠退款金额,手续费总金额
`2,`0.02,`0.0,`0.0,`0
```
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.downloadBill({
billDate: '20200202',
})
return res
}
```
### 下载资金账单
`unipayIns.downloadFundflow`,商户可以通过该接口下载自 2017 年 6 月 1 日起的历史资金流水账单。**仅微信支持**
**说明:**
1. 资金账单中的数据反映的是商户微信账户资金变动情况;
2. 当日账单在次日上午 9 点开始生成,建议商户在上午 10 点以后获取;
3. 资金账单中涉及金额的字段单位为“元”。
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 支持平台 |
| :---------: | :----: | :--: | :----: | :---------------------------------------------------------------------: | :------: |
| billDate | String | 必填 | - | 下载对账单的日期,格式:2014-06-03 | - |
| accountType | String | 选填 | Basic | 账单的资金来源账户:Basic 基本账户,Operation 运营账户,Fees 手续费账户 | - |
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-----: | :----: | :----------------------: | :------: |
| content | String | 文本表格的方式返回的数据 | - |
`content`示例如下
- 第一行为表头
记账时间,微信支付业务单号,资金流水单号,业务名称,业务类型,收支类型,收支金额(元),账户结余(元),资金变更提交申请人,备注,业务凭证号
- 从第二行起,为资金流水数据,各参数以逗号分隔,参数前增加`符号,为标准键盘 1 左边键的字符,字段顺序与表头一致
- 倒数第二行为资金账单统计标题
资金流水总笔数,收入笔数,收入金额,支出笔数,支出金额
- 最后一行为统计数据
账单示例如下:
```
记账时间,微信支付业务单号,资金流水单号,业务名称,业务类型,收支类型,收支金额(元),账户结余(元),资金变更提交申请人,备注,业务凭证号
`2018-02-01 04:21:23,`50000305742018020103387128253,`1900009231201802015884652186,`退款,`退款,`支出,`0.02,`0.17,`system,`缺货,`REF4200000068201801293084726067
资金流水总笔数,收入笔数,收入金额,支出笔数,支出金额
`20.0,`17.0,`0.35,`3.0,`0.18
```
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.downloadFundflow({
billDate: '20200202',
})
return res
}
```
### 支付结果通知处理
**注意:支付宝在非全量退款时也会发送通知到支付时设置的notify_url**
`unipayIns.verifyPaymentNotify`,用于在使用云函数 Url 化的云函数内检验并处理支付结果。
**入参说明**
只接收对应云函数的`event`作为参数
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :-----------: | :----: | :-------------------------------------------------: | :------: |
| totalFee | Number | 订单总金额 | - |
| cashFee | Number | 现金支付金额 | - |
| feeType | String | 货币类别 | - |
| outTradeNo | String | 商户订单号 | - |
| transactionId | String | 平台订单号 | - |
| timeEnd | String | 支付完成时间,格式为 yyyyMMddHHmmss | - |
| openid | String | 用户 id | - |
| returnCode | String | 值 SUCCESS 时为支付成功,通常需要校验订单金额等参数 | - |
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.verifyPaymentNotify(event)
// 处理完毕其他业务
// 注意如果处理成功需要严格按照下面的格式进行返回,否则厂商会持续通知
// 微信支付V3处理成功之后
return {
mpserverlessComposedResponse: true,
statusCode: 200,
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
code: 'SUCCESS',
message: '成功'
})
}
// 微信支付V2处理成功之后
return {
mpserverlessComposedResponse: true,
statusCode: 200,
headers: {
'content-type': 'text/xml;charset=utf-8'
},
body: `<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>`
}
// 支付宝处理成功后
return {
mpserverlessComposedResponse: true,
statusCode: 200,
headers: {
'content-type': 'text/plain'
},
body: "success"
}
}
```
### 退款结果通知@verify-refund-notify
**注意:支付宝在非全量退款时才会发送通知,通知地址为支付时设置的notify_url**
> uni-pay 1.0.17版本起新增对支付宝退款结果通知的支持
`unipayIns.verifyRefundNotify`,用于在使用云函数 Url 化的云函数内检验并处理支付结果。
**入参说明**
只接收对应云函数的`event`作为参数
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台|
| :-----------------: | :----:| :---------------------------------------------------: | :------:|
| totalFee | Number| 订单总金额 | - |
| refundFee | Number| 申请退款金额 | - |
| settlementTotalFee | Number| 应结订单金额,支付宝不返回 | - |
| settlementRefundFee | Number| 退款金额,支付宝不返回 | - |
| outTradeNo | String| 商户订单号 | - |
| transactionId | String| 平台订单号 | - |
| refundId | String| 平台退款单号,支付宝不返回 | - |
| outRefundNo | String| 商户退款单号 | - |
| refundStatus | String| SUCCESS-退款成功,CHANGE-退款异常,REFUNDCLOSE—退款关闭| - |
| refundAccount | String| 退款资金来源,支付宝不返回 | - |
| refundRecvAccout | String| 退款入账账户,支付宝不返回 | - |
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.verifyRefundNotify(event)
// 注意如果处理成功需要严格按照下面的格式进行返回,否则厂商会持续通知
// 微信处理成功之后
return {
mpserverlessComposedResponse: true,
statusCode: 200,
headers: {
'content-type': 'text/xml;charset=utf-8'
},
body: `<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>`
}
// 支付宝处理成功后
return {
mpserverlessComposedResponse: true,
statusCode: 200,
headers: {
'content-type': 'text/plain'
},
body: "success"
}
}
```
### 获取通知类型@check-notify-type
> 新增于 uni-id 1.0.17
`unipayIns.checkNotifyType`,用于在使用云函数 Url 化的云函数内检验当前通知的类型。由于支付宝支付在非全量退款时会调用支付时设置的notify_url,可以使用此接口在调用校验通知之前判断通知类型
**入参说明**
只接收对应云函数的`event`作为参数
**返回值说明**
此接口会返回一个字符串,可能的值如下
- `refund`:当前是一个退款通知
- `payment`:当前是一个支付结果通知
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.checkNotifyType(event)
if(res === 'refund') {
// 退款通知
} else if(res === 'payment') {
// 支付结果通知
}
}
```
### 苹果内购-校验支付凭证@verifyReceipt
`unipayIns.verifyReceipt`, 校验iap支付凭证返回交易信息。
**入参说明**
| 参数名 | 类型 | 必填 | 默认值 | 说明 | 支持平台 |
| :-----------: | :----: | :---------------------: | :----: | :--------: | :------: |
| receiptData | String | 是 | - | 支付凭证 | 苹果内购支付 |
**返回值说明**
| 参数名 | 类型 | 说明 | 支持平台 |
| :----------------: | :----: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: |
| transactionId | String | 交易标示 | - |
| tradeState | String | 订单状态 ,微信支付: SUCCESS—支付成功,REFUND—转入退款,NOTPAY—未支付,PAYERROR--支付失败(其他原因,如银行返回失败),SUCCESS(交易支付成功)。 | - |
| totalFee | Number | 标价金额 ,单位:分 | - |
| settlementTotalFee | Number | 应结订单金额,单位:分 | - |
| receipt | Object | 收据信息 | - |
**使用示例**
```js
exports.main = async function (event) {
let res = await unipayIns.verifyReceipt({
receiptData: 'transactionReceipt',
})
return res
}
```
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册