@alipay/alipay-aipay 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/alipay-aipay.js +6 -0
- package/dist/commands/install.d.ts +11 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +182 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/prepare-tools.d.ts +5 -0
- package/dist/commands/prepare-tools.d.ts.map +1 -0
- package/dist/commands/prepare-tools.js +29 -0
- package/dist/commands/prepare-tools.js.map +1 -0
- package/dist/commands/uninstall.d.ts +10 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +82 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/commands/update.d.ts +10 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +155 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/alipay-cli.d.ts +15 -0
- package/dist/utils/alipay-cli.d.ts.map +1 -0
- package/dist/utils/alipay-cli.js +221 -0
- package/dist/utils/alipay-cli.js.map +1 -0
- package/dist/utils/config.d.ts +48 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +207 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/file.d.ts +55 -0
- package/dist/utils/file.d.ts.map +1 -0
- package/dist/utils/file.js +154 -0
- package/dist/utils/file.js.map +1 -0
- package/dist/utils/gpg.d.ts +14 -0
- package/dist/utils/gpg.d.ts.map +1 -0
- package/dist/utils/gpg.js +247 -0
- package/dist/utils/gpg.js.map +1 -0
- package/dist/utils/jq.d.ts +14 -0
- package/dist/utils/jq.d.ts.map +1 -0
- package/dist/utils/jq.js +221 -0
- package/dist/utils/jq.js.map +1 -0
- package/dist/utils/npm.d.ts +17 -0
- package/dist/utils/npm.d.ts.map +1 -0
- package/dist/utils/npm.js +42 -0
- package/dist/utils/npm.js.map +1 -0
- package/dist/utils/shell.d.ts +11 -0
- package/dist/utils/shell.d.ts.map +1 -0
- package/dist/utils/shell.js +54 -0
- package/dist/utils/shell.js.map +1 -0
- package/package.json +48 -7
- package/skill/SKILL.md +118 -0
- package/skill/references/integration/flow.md +487 -0
- package/skill/references/integration/modules/alipay-sdk-reminder.md +256 -0
- package/skill/references/integration/modules/checklist.md +122 -0
- package/skill/references/integration/modules/code-examples/csharp/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/345/205/263/351/227/255/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +68 -0
- package/skill/references/integration/modules/code-examples/csharp/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +77 -0
- package/skill/references/integration/modules/code-examples/csharp/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +74 -0
- package/skill/references/integration/modules/code-examples/csharp/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +73 -0
- package/skill/references/integration/modules/code-examples/csharp/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +58 -0
- package/skill/references/integration/modules/code-examples/csharp/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +128 -0
- package/skill/references/integration/modules/code-examples/csharp/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.cs +500 -0
- package/skill/references/integration/modules/code-examples/java/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/345/205/263/351/227/255/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +72 -0
- package/skill/references/integration/modules/code-examples/java/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +80 -0
- package/skill/references/integration/modules/code-examples/java/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +78 -0
- package/skill/references/integration/modules/code-examples/java/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +77 -0
- package/skill/references/integration/modules/code-examples/java/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +66 -0
- package/skill/references/integration/modules/code-examples/java/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +135 -0
- package/skill/references/integration/modules/code-examples/java/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemoController.java +610 -0
- package/skill/references/integration/modules/code-examples/nodejs/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/345/205/263/351/227/255/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +51 -0
- package/skill/references/integration/modules/code-examples/nodejs/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +57 -0
- package/skill/references/integration/modules/code-examples/nodejs/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +55 -0
- package/skill/references/integration/modules/code-examples/nodejs/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +54 -0
- package/skill/references/integration/modules/code-examples/nodejs/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +77 -0
- package/skill/references/integration/modules/code-examples/nodejs/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +65 -0
- package/skill/references/integration/modules/code-examples/nodejs/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.js +461 -0
- package/skill/references/integration/modules/code-examples/php/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/345/205/263/351/227/255/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +59 -0
- package/skill/references/integration/modules/code-examples/php/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +67 -0
- package/skill/references/integration/modules/code-examples/php/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +66 -0
- package/skill/references/integration/modules/code-examples/php/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +65 -0
- package/skill/references/integration/modules/code-examples/php/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +56 -0
- package/skill/references/integration/modules/code-examples/php/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +107 -0
- package/skill/references/integration/modules/code-examples/php/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.php +444 -0
- package/skill/references/integration/modules/code-examples/python/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/345/205/263/351/227/255/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +57 -0
- package/skill/references/integration/modules/code-examples/python/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +65 -0
- package/skill/references/integration/modules/code-examples/python/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +63 -0
- package/skill/references/integration/modules/code-examples/python/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +62 -0
- package/skill/references/integration/modules/code-examples/python/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +48 -0
- package/skill/references/integration/modules/code-examples/python/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +43 -0
- package/skill/references/integration/modules/code-examples/python/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.py +465 -0
- package/skill/references/integration/modules/interface-guide.md +90 -0
- package/skill/references/integration/modules/product-decision.md +90 -0
- package/skill/references/integration/modules/sandbox/a2m-sandbox-test.md +270 -0
- package/skill/references/integration/modules/sandbox/alipay-sandbox-tool.md +133 -0
- package/skill/references/integration/modules/sandbox/sandbox-setup-guide.md +218 -0
- package/skill/references/integration/modules/scripts/local_402_sandbox_pay.py +659 -0
- package/skill/references/normal/alipay-cli-env.md +214 -0
- package/skill/references/normal/rejection-guide.md +37 -0
- package/skill/references/normal/scripts/common.sh +32 -0
- package/skill/references/normal/scripts/detect_dev_tool.sh +46 -0
- package/skill/references/onboarding/flow.md +573 -0
- package/skill/references/onboarding/modules/app-release.md +415 -0
- package/skill/references/onboarding/modules/authorization.md +269 -0
- package/skill/references/onboarding/modules/error-handling.md +251 -0
- package/skill/references/onboarding/modules/mcc-reference.md +209 -0
- package/skill/references/onboarding/modules/product-sign.md +304 -0
- package/skill/references/onboarding/modules/scripts/app.sh +705 -0
- package/skill/references/onboarding/modules/scripts/ar_sign_apply.sh +248 -0
- package/skill/references/onboarding/modules/scripts/auth.sh +505 -0
- package/skill/references/onboarding/modules/scripts/error_handler.sh +452 -0
- package/skill/references/onboarding/modules/scripts/query_sign_status.sh +146 -0
- package/skill/references/onboarding/modules/scripts/service.sh +267 -0
- package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +152 -0
- package/skill/references/onboarding/modules/service-registration.md +294 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2M 智能收产品接入示例 - Node.js 版本
|
|
3
|
+
*
|
|
4
|
+
* 本文件演示完整的智能收产品接入流程:
|
|
5
|
+
* 1. 返回 402 Payment-Needed Header
|
|
6
|
+
* 2. 验证 Payment-Proof 支付凭证
|
|
7
|
+
* 3. 发送履约回执确认
|
|
8
|
+
* 4. 返回资源内容
|
|
9
|
+
*
|
|
10
|
+
* 依赖安装:
|
|
11
|
+
* npm install express alipay-sdk crypto-js
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const express = require('express');
|
|
15
|
+
const { AlipaySdk } = require('alipay-sdk');
|
|
16
|
+
const crypto = require('crypto');
|
|
17
|
+
|
|
18
|
+
const app = express();
|
|
19
|
+
const PORT = 3000;
|
|
20
|
+
|
|
21
|
+
// ==================== 配置信息(实际使用时请从配置中心读取)====================
|
|
22
|
+
const CONFIG = {
|
|
23
|
+
// 支付宝配置
|
|
24
|
+
alipay: {
|
|
25
|
+
appId: '<APP_ID>',
|
|
26
|
+
privateKey: '<APP_PRIVATE_KEY>', // 请填写您的应用私钥(PKCS#1 格式)
|
|
27
|
+
alipayPublicKey: '<ALIPAY_PUBLIC_KEY>', // 请填写您的支付宝公钥
|
|
28
|
+
gateway: 'https://openapi.alipay.com/gateway.do',
|
|
29
|
+
sellerId: '<SELLER_ID_2088>', // 商户 ID(2088 格式)
|
|
30
|
+
serviceId: '<SERVICE_ID>', // 商户服务 ID
|
|
31
|
+
merchantPrivateKey: '<APP_PRIVATE_KEY>' // 请填写您的应用私钥(用于商家签名)
|
|
32
|
+
},
|
|
33
|
+
// 资源服务配置
|
|
34
|
+
resource: {
|
|
35
|
+
path: '/demo/a2m/resource',
|
|
36
|
+
goodsName: 'AI 生成内容服务'
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// 初始化支付宝 SDK
|
|
41
|
+
const alipaySdk = new AlipaySdk({
|
|
42
|
+
appId: CONFIG.alipay.appId,
|
|
43
|
+
privateKey: CONFIG.alipay.privateKey,
|
|
44
|
+
alipayPublicKey: CONFIG.alipay.alipayPublicKey,
|
|
45
|
+
gateway: CONFIG.alipay.gateway,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// ==================== 工具方法 ====================
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 格式化支付宝时间戳:yyyy-MM-dd HH:mm:ss
|
|
52
|
+
*/
|
|
53
|
+
function formatAlipayTimestamp(date = new Date()) {
|
|
54
|
+
const pad = (n) => n.toString().padStart(2, '0');
|
|
55
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 生成商家签名(seller_signature)
|
|
60
|
+
* @param {Object} params - 待签名参数
|
|
61
|
+
* @param {String} privateKey - 商户私钥
|
|
62
|
+
* @returns {String} Base64 编码的签名
|
|
63
|
+
*/
|
|
64
|
+
function generateSellerSignature(params, privateKey) {
|
|
65
|
+
// 1. 按 key 字典序排序
|
|
66
|
+
const keys = Object.keys(params).sort();
|
|
67
|
+
|
|
68
|
+
// 2. 拼接签名内容
|
|
69
|
+
const signContent = keys
|
|
70
|
+
.filter(key => params[key] !== null && params[key] !== '')
|
|
71
|
+
.map((key, index) => {
|
|
72
|
+
const value = params[key];
|
|
73
|
+
return `${key}=${value}`;
|
|
74
|
+
})
|
|
75
|
+
.join('&');
|
|
76
|
+
|
|
77
|
+
// 3. RSA2 签名
|
|
78
|
+
const sign = crypto
|
|
79
|
+
.createSign('RSA-SHA256')
|
|
80
|
+
.update(signContent, 'utf8')
|
|
81
|
+
.sign(privateKey, 'base64');
|
|
82
|
+
|
|
83
|
+
return sign;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 格式化日期为 ISO 8601 带时区偏移量格式(如 2026-05-15T12:08:36+08:00)
|
|
88
|
+
* @param {Date} date - 日期对象
|
|
89
|
+
* @returns {String} ISO 8601 带时区偏移量字符串
|
|
90
|
+
*/
|
|
91
|
+
function formatISO8601WithTimezone(date) {
|
|
92
|
+
const pad = (n) => n.toString().padStart(2, '0');
|
|
93
|
+
const year = date.getFullYear();
|
|
94
|
+
const month = pad(date.getMonth() + 1);
|
|
95
|
+
const day = pad(date.getDate());
|
|
96
|
+
const hours = pad(date.getHours());
|
|
97
|
+
const minutes = pad(date.getMinutes());
|
|
98
|
+
const seconds = pad(date.getSeconds());
|
|
99
|
+
const offset = -date.getTimezoneOffset();
|
|
100
|
+
const offsetHours = pad(Math.floor(Math.abs(offset) / 60));
|
|
101
|
+
const offsetMinutes = pad(Math.abs(offset) % 60);
|
|
102
|
+
const offsetSign = offset >= 0 ? '+' : '-';
|
|
103
|
+
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}${offsetSign}${offsetHours}:${offsetMinutes}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Base64URL 编码
|
|
108
|
+
*/
|
|
109
|
+
function base64UrlEncode(str) {
|
|
110
|
+
return Buffer.from(str, 'utf8')
|
|
111
|
+
.toString('base64')
|
|
112
|
+
.replace(/\+/g, '-')
|
|
113
|
+
.replace(/\//g, '_')
|
|
114
|
+
.replace(/=/g, '');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Base64URL 解码
|
|
119
|
+
*/
|
|
120
|
+
function base64UrlDecode(str) {
|
|
121
|
+
// 补充 padding
|
|
122
|
+
let padding = '=';
|
|
123
|
+
while (str.length % 4) {
|
|
124
|
+
str += padding;
|
|
125
|
+
}
|
|
126
|
+
str = str.replace(/-/g, '+').replace(/_/g, '/');
|
|
127
|
+
return Buffer.from(str, 'base64').toString('utf8');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ==================== 智能收产品接入示例接口 ====================
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 智能收产品统一接口
|
|
134
|
+
*
|
|
135
|
+
* 完整流程演示:
|
|
136
|
+
* 1. 不带 Payment-Proof Header:返回 HTTP 402 + Payment-Needed Header
|
|
137
|
+
* 2. 带 Payment-Proof Header:验证支付 → 自动履约 → 返回资源
|
|
138
|
+
*
|
|
139
|
+
* @route GET /demo/a2m/resource
|
|
140
|
+
* @param {String} Payment-Proof - 支付凭证(从 Header 获取,可选)
|
|
141
|
+
* @returns {Object} 未支付时返回 402,已支付时返回资源内容
|
|
142
|
+
*/
|
|
143
|
+
app.get(CONFIG.resource.path, async (req, res) => {
|
|
144
|
+
const paymentProof = req.headers['payment-proof'];
|
|
145
|
+
|
|
146
|
+
// 场景 1:用户未支付,返回 402 + Payment-Needed Header
|
|
147
|
+
if (!paymentProof || paymentProof.trim() === '') {
|
|
148
|
+
return createPaymentRequiredResponse(req, res);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 场景 2:用户已支付,验证 Payment-Proof 并返回资源
|
|
152
|
+
return verifyPaymentAndDeliverResource(req, res, paymentProof);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 创建 402 支付请求响应
|
|
157
|
+
*/
|
|
158
|
+
async function createPaymentRequiredResponse(req, res) {
|
|
159
|
+
try {
|
|
160
|
+
// 1. 构造订单信息
|
|
161
|
+
const outTradeNo = `ORDER_${Date.now()}`;
|
|
162
|
+
const amount = '0.01'; // 单位:元
|
|
163
|
+
const currency = 'CNY';
|
|
164
|
+
const resourceId = CONFIG.resource.path;
|
|
165
|
+
const goodsName = CONFIG.resource.goodsName;
|
|
166
|
+
|
|
167
|
+
// 2. 计算支付截止时间(30 分钟后),使用带时区偏移量的 ISO 8601 格式
|
|
168
|
+
const payBefore = new Date(Date.now() + 30 * 60 * 1000);
|
|
169
|
+
const payBeforeStr = formatISO8601WithTimezone(payBefore);
|
|
170
|
+
|
|
171
|
+
// 3. 生成商家签名
|
|
172
|
+
const sellerSignature = generateSellerSignature({
|
|
173
|
+
amount,
|
|
174
|
+
currency,
|
|
175
|
+
goods_name: goodsName,
|
|
176
|
+
out_trade_no: outTradeNo,
|
|
177
|
+
pay_before: payBeforeStr,
|
|
178
|
+
resource_id: resourceId,
|
|
179
|
+
seller_id: CONFIG.alipay.sellerId,
|
|
180
|
+
service_id: CONFIG.alipay.serviceId
|
|
181
|
+
}, CONFIG.alipay.merchantPrivateKey);
|
|
182
|
+
|
|
183
|
+
// 4. 构造 Payment-Needed Header 内容
|
|
184
|
+
const paymentNeeded = {
|
|
185
|
+
protocol: {
|
|
186
|
+
out_trade_no: outTradeNo,
|
|
187
|
+
amount,
|
|
188
|
+
currency,
|
|
189
|
+
resource_id: resourceId,
|
|
190
|
+
pay_before: payBeforeStr,
|
|
191
|
+
seller_signature: sellerSignature,
|
|
192
|
+
seller_sign_type: 'RSA2',
|
|
193
|
+
seller_unique_id: CONFIG.alipay.sellerId
|
|
194
|
+
},
|
|
195
|
+
method: {
|
|
196
|
+
seller_name: '测试商户',
|
|
197
|
+
seller_id: CONFIG.alipay.sellerId,
|
|
198
|
+
seller_app_id: CONFIG.alipay.appId,
|
|
199
|
+
goods_name: goodsName,
|
|
200
|
+
seller_unique_id_key: 'seller_id',
|
|
201
|
+
service_id: CONFIG.alipay.serviceId
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// 5. Base64URL 编码
|
|
206
|
+
const paymentNeededEncoded = base64UrlEncode(JSON.stringify(paymentNeeded));
|
|
207
|
+
|
|
208
|
+
// 6. 构造 402 响应
|
|
209
|
+
res.set('Payment-Needed', paymentNeededEncoded);
|
|
210
|
+
res.status(402).json({
|
|
211
|
+
code: 'Payment-Needed',
|
|
212
|
+
message: '需要支付',
|
|
213
|
+
out_trade_no: outTradeNo,
|
|
214
|
+
amount,
|
|
215
|
+
currency,
|
|
216
|
+
goods_name: goodsName
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
console.log(`创建支付订单成功:outTradeNo=${outTradeNo}, amount=${amount}`);
|
|
220
|
+
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.error('创建订单失败:', error.message);
|
|
223
|
+
res.status(500).json({
|
|
224
|
+
code: 'CREATE_ORDER_ERROR',
|
|
225
|
+
message: '创建订单失败:' + error.message
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* 验证支付凭证并交付资源
|
|
232
|
+
*/
|
|
233
|
+
async function verifyPaymentAndDeliverResource(req, res, paymentProof) {
|
|
234
|
+
try {
|
|
235
|
+
// 1. 从 Payment-Proof 中解析订单信息
|
|
236
|
+
let paymentProofValue, tradeNo, clientSession;
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
const decodedProof = base64UrlDecode(paymentProof);
|
|
240
|
+
const proofJson = JSON.parse(decodedProof);
|
|
241
|
+
|
|
242
|
+
// 从 protocol 层获取 payment_proof 和 trade_no
|
|
243
|
+
if (proofJson.protocol) {
|
|
244
|
+
paymentProofValue = proofJson.protocol.payment_proof;
|
|
245
|
+
tradeNo = proofJson.protocol.trade_no;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// 从 method 层获取 client_session
|
|
249
|
+
if (proofJson.method) {
|
|
250
|
+
clientSession = proofJson.method.client_session;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 校验必要字段
|
|
254
|
+
if (!paymentProofValue || paymentProofValue.trim() === '') {
|
|
255
|
+
return res.status(400).json({
|
|
256
|
+
code: 'INVALID_PAYMENT_PROOF_FORMAT',
|
|
257
|
+
message: 'Payment-Proof 格式错误:缺少 payment_proof'
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (!tradeNo || tradeNo.trim() === '') {
|
|
262
|
+
return res.status(400).json({
|
|
263
|
+
code: 'INVALID_PAYMENT_PROOF_FORMAT',
|
|
264
|
+
message: 'Payment-Proof 格式错误:缺少 trade_no'
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.error('Payment-Proof 解析失败:', error.message);
|
|
270
|
+
return res.status(400).json({
|
|
271
|
+
code: 'INVALID_PAYMENT_PROOF_FORMAT',
|
|
272
|
+
message: 'Payment-Proof 格式错误:' + error.message
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// 2. 调用支付宝 API 验证支付凭证
|
|
277
|
+
const verifyResponse = await alipaySdk.exec('alipay.aipay.agent.payment.verify', {
|
|
278
|
+
bizContent: {
|
|
279
|
+
payment_proof: paymentProofValue,
|
|
280
|
+
trade_no: tradeNo,
|
|
281
|
+
client_session: clientSession
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// 3. 验证失败,返回错误
|
|
286
|
+
// 注意:SDK 返回的响应可能是扁平结构(code/trade_no 在顶层),
|
|
287
|
+
// 也可能嵌套在 alipay_aipay_agent_payment_verify_response 键下,需兼容两种情况
|
|
288
|
+
const responseData = verifyResponse.alipay_aipay_agent_payment_verify_response || verifyResponse;
|
|
289
|
+
if (responseData.code !== '10000') {
|
|
290
|
+
console.error('支付凭证验证失败:', responseData.sub_msg);
|
|
291
|
+
return res.status(400).json({
|
|
292
|
+
code: responseData.sub_code,
|
|
293
|
+
message: responseData.sub_msg
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// 4. 验证成功,获取订单信息
|
|
298
|
+
const {
|
|
299
|
+
trade_no: verifyTradeNo,
|
|
300
|
+
out_trade_no: verifyOutTradeNo,
|
|
301
|
+
resource_id: resourceId,
|
|
302
|
+
active
|
|
303
|
+
} = responseData;
|
|
304
|
+
|
|
305
|
+
console.log(`支付凭证验证成功:tradeNo=${verifyTradeNo}, outTradeNo=${verifyOutTradeNo}`);
|
|
306
|
+
|
|
307
|
+
// 5. 校验凭证有效性(active=true 表示凭证有效)
|
|
308
|
+
if (active !== true) {
|
|
309
|
+
console.error(`支付凭证无效或已过期:outTradeNo=${verifyOutTradeNo}`);
|
|
310
|
+
return res.status(400).json({
|
|
311
|
+
code: 'INVALID_PAYMENT_PROOF',
|
|
312
|
+
message: '支付凭证无效或已过期'
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// 6. 【TODO】查询订单是否存在(以数据库为准)
|
|
317
|
+
// const order = await orderRepository.findByOutTradeNo(verifyOutTradeNo);
|
|
318
|
+
// if (!order) {
|
|
319
|
+
// return res.status(404).json({
|
|
320
|
+
// code: 'ORDER_NOT_FOUND',
|
|
321
|
+
// message: '订单不存在'
|
|
322
|
+
// });
|
|
323
|
+
// }
|
|
324
|
+
|
|
325
|
+
// 7. 【TODO】资源防串校验
|
|
326
|
+
// if (resourceId !== order.resourceId) {
|
|
327
|
+
// return res.status(403).json({
|
|
328
|
+
// code: 'RESOURCE_ID_MISMATCH',
|
|
329
|
+
// message: '资源 ID 不匹配,可能存在资源串改风险'
|
|
330
|
+
// });
|
|
331
|
+
// }
|
|
332
|
+
|
|
333
|
+
// 8. 【TODO】履约防重放校验(数据库幂等控制)
|
|
334
|
+
// if (order.fulfillStatus === 'FULFILLED') {
|
|
335
|
+
// return res.status(200).json({
|
|
336
|
+
// code: 'ALREADY_FULFILLED',
|
|
337
|
+
// message: '订单已履约,不重复提供',
|
|
338
|
+
// already_fulfilled: true
|
|
339
|
+
// });
|
|
340
|
+
// }
|
|
341
|
+
|
|
342
|
+
// 9. 执行业务逻辑,生成资源内容
|
|
343
|
+
const serviceResult = generateServiceResource(resourceId);
|
|
344
|
+
|
|
345
|
+
// 10. 【TODO】履约记录落库(用于审计/售后/对账)
|
|
346
|
+
// await fulfillmentRecordRepository.save({ ... });
|
|
347
|
+
|
|
348
|
+
// 11. 【TODO】保存待确认履约状态
|
|
349
|
+
// 实际生产中建议先保存 serviceResult 和 PENDING_CONFIRM 状态。
|
|
350
|
+
// fulfillment.confirm 成功后再标记 FULFILLED;如果确认失败,
|
|
351
|
+
// 允许同一笔 Payment-Proof 重试确认,避免误返回成功。
|
|
352
|
+
// await orderRepository.update(verifyOutTradeNo, {
|
|
353
|
+
// orderStatus: 'PAID',
|
|
354
|
+
// fulfillStatus: 'PENDING_CONFIRM',
|
|
355
|
+
// tradeNo: verifyTradeNo,
|
|
356
|
+
// serviceResult
|
|
357
|
+
// });
|
|
358
|
+
|
|
359
|
+
console.log(`资源已生成,准备发送履约确认:outTradeNo=${verifyOutTradeNo}, tradeNo=${verifyTradeNo}`);
|
|
360
|
+
|
|
361
|
+
// 12. 发送履约确认到支付宝,确认成功后才返回成功交付
|
|
362
|
+
const fulfillmentConfirmed = await sendFulfillmentConfirm(verifyTradeNo);
|
|
363
|
+
if (!fulfillmentConfirmed) {
|
|
364
|
+
return res.status(502).json({
|
|
365
|
+
code: 'FULFILLMENT_CONFIRM_FAILED',
|
|
366
|
+
message: '资源已生成但履约确认失败,请稍后使用同一 Payment-Proof 重试'
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// 12.1 【TODO】履约确认成功后,再标记订单已履约
|
|
371
|
+
// await orderRepository.update(verifyOutTradeNo, {
|
|
372
|
+
// fulfillStatus: 'FULFILLED',
|
|
373
|
+
// fulfilledAt: new Date()
|
|
374
|
+
// });
|
|
375
|
+
|
|
376
|
+
console.log(`履约确认成功:outTradeNo=${verifyOutTradeNo}, tradeNo=${verifyTradeNo}`);
|
|
377
|
+
|
|
378
|
+
// 13. 构造 Payment-Validation Header
|
|
379
|
+
const paymentValidation = {
|
|
380
|
+
trade_no: verifyTradeNo,
|
|
381
|
+
out_trade_no: verifyOutTradeNo,
|
|
382
|
+
validated: true,
|
|
383
|
+
resource_id: resourceId
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const paymentValidationEncoded = base64UrlEncode(JSON.stringify(paymentValidation));
|
|
387
|
+
res.set('Payment-Validation', paymentValidationEncoded);
|
|
388
|
+
|
|
389
|
+
// 14. 返回资源内容
|
|
390
|
+
res.json({
|
|
391
|
+
resource_id: resourceId,
|
|
392
|
+
content: serviceResult,
|
|
393
|
+
trade_no: verifyTradeNo,
|
|
394
|
+
out_trade_no: verifyOutTradeNo,
|
|
395
|
+
already_fulfilled: false,
|
|
396
|
+
fulfillment_confirmed: true
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
} catch (error) {
|
|
400
|
+
console.error('支付凭证验证异常:', error.message);
|
|
401
|
+
res.status(500).json({
|
|
402
|
+
code: 'VERIFY_FAILED',
|
|
403
|
+
message: '支付凭证验证失败:' + error.message
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* 生成服务资源内容
|
|
410
|
+
*/
|
|
411
|
+
function generateServiceResource(resourceId) {
|
|
412
|
+
return JSON.stringify({
|
|
413
|
+
status: 'success',
|
|
414
|
+
service_type: 'AI_CONTENT_GENERATION',
|
|
415
|
+
resource_id: resourceId,
|
|
416
|
+
content: '这是 AI 生成的内容示例,可根据实际业务替换为任意数字服务内容',
|
|
417
|
+
generated_at: formatISO8601WithTimezone(new Date())
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* 发送履约确认
|
|
423
|
+
*/
|
|
424
|
+
async function sendFulfillmentConfirm(tradeNo) {
|
|
425
|
+
try {
|
|
426
|
+
console.log(`开始发送履约确认:tradeNo=${tradeNo}`);
|
|
427
|
+
|
|
428
|
+
const response = await alipaySdk.exec('alipay.aipay.agent.fulfillment.confirm', {
|
|
429
|
+
bizContent: {
|
|
430
|
+
trade_no: tradeNo
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// 注意:SDK 返回的响应可能是扁平结构,也可能嵌套在响应键下,需兼容两种情况
|
|
435
|
+
const responseData = response.alipay_aipay_agent_fulfillment_confirm_response || response;
|
|
436
|
+
if (responseData.code === '10000') {
|
|
437
|
+
console.log(`履约确认成功:tradeNo=${tradeNo}`);
|
|
438
|
+
return true;
|
|
439
|
+
} else {
|
|
440
|
+
console.error(`履约确认失败:tradeNo=${tradeNo}, errorCode=${responseData.sub_code}, errorMsg=${responseData.sub_msg}`);
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
} catch (error) {
|
|
445
|
+
console.error(`履约确认异常:tradeNo=${tradeNo}, error=${error.message}`);
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ==================== 启动服务 ====================
|
|
451
|
+
|
|
452
|
+
if (require.main === module) {
|
|
453
|
+
app.listen(PORT, () => {
|
|
454
|
+
console.log(`A2M 智能收服务已启动:http://localhost:${PORT}${CONFIG.resource.path}`);
|
|
455
|
+
console.log('测试步骤:');
|
|
456
|
+
console.log('1. 无 Payment-Proof Header: curl http://localhost:3000/demo/a2m/resource');
|
|
457
|
+
console.log('2. 有 Payment-Proof Header: curl -H "Payment-Proof: <value>" http://localhost:3000/demo/a2m/resource');
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
module.exports = app;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# 统一收单交易关闭接口 - PHP 示例
|
|
2
|
+
|
|
3
|
+
## 请求示例
|
|
4
|
+
|
|
5
|
+
```php
|
|
6
|
+
<?php
|
|
7
|
+
require_once __DIR__ . '/aop/AopClient.php';
|
|
8
|
+
require_once __DIR__ . '/aop/request/AlipayTradeCloseRequest.php';
|
|
9
|
+
|
|
10
|
+
// 初始化 SDK(PHP 使用 PKCS#1 格式私钥,Java 使用 PKCS#8 格式,请勿混用)
|
|
11
|
+
$aop = new AopClient();
|
|
12
|
+
$aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
|
|
13
|
+
// 沙箱环境: $aop->gatewayUrl = 'https://openapi-sandbox.dl.alipaydev.com/gateway.do';
|
|
14
|
+
$aop->appId = '<-- 请填写您的AppId,例如:20190xxxxxx -->';
|
|
15
|
+
$aop->rsaPrivateKey = '<-- 请填写您的应用私钥(PKCS#1格式),例如:MIIE...(脱敏示例) -->';
|
|
16
|
+
$aop->alipayrsaPublicKey = '<-- 请填写您的支付宝公钥,例如:MIIB...(脱敏示例) -->';
|
|
17
|
+
$aop->format = 'json';
|
|
18
|
+
$aop->postCharset = 'UTF-8';
|
|
19
|
+
$aop->signType = 'RSA2';
|
|
20
|
+
|
|
21
|
+
// 构造请求参数
|
|
22
|
+
$request = new AlipayTradeCloseRequest();
|
|
23
|
+
$bizContent = [
|
|
24
|
+
'out_trade_no' => 'MERCHANT_ORDER_NO_PLACEHOLDER', // 商户订单号,与 trade_no 二选一
|
|
25
|
+
// 'trade_no' => 'ALIPAY_TRADE_NO_PLACEHOLDER', // 支付宝交易号,同时传时以 trade_no 为准
|
|
26
|
+
];
|
|
27
|
+
$request->setBizContent(json_encode($bizContent, JSON_UNESCAPED_UNICODE));
|
|
28
|
+
|
|
29
|
+
// 调用接口
|
|
30
|
+
$response = $aop->execute($request);
|
|
31
|
+
$responseApiName = str_replace('.', '_', $request->getApiMethodName()) . '_response';
|
|
32
|
+
$result = $response->$responseApiName;
|
|
33
|
+
|
|
34
|
+
if (!empty($result->code) && $result->code == '10000') {
|
|
35
|
+
echo "交易关闭成功";
|
|
36
|
+
} else {
|
|
37
|
+
echo "调用失败: " . $result->sub_code . " - " . $result->sub_msg;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 响应示例
|
|
42
|
+
### 正常示例
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"code": "10000",
|
|
46
|
+
"msg": "Success",
|
|
47
|
+
"trade_no": "ALIPAY_TRADE_NO_PLACEHOLDER",
|
|
48
|
+
"out_trade_no": "MERCHANT_ORDER_NO_PLACEHOLDER"
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
### 异常示例
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"code": "20000",
|
|
55
|
+
"msg": "Service Currently Unavailable",
|
|
56
|
+
"sub_code": "isp.unknow-error",
|
|
57
|
+
"sub_msg": "系统繁忙"
|
|
58
|
+
}
|
|
59
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# 统一收单交易查询接口 - PHP 示例
|
|
2
|
+
|
|
3
|
+
## 请求示例
|
|
4
|
+
|
|
5
|
+
```php
|
|
6
|
+
<?php
|
|
7
|
+
require_once __DIR__ . '/aop/AopClient.php';
|
|
8
|
+
require_once __DIR__ . '/aop/request/AlipayTradeQueryRequest.php';
|
|
9
|
+
|
|
10
|
+
// 初始化 SDK(PHP 使用 PKCS#1 格式私钥,Java 使用 PKCS#8 格式,请勿混用)
|
|
11
|
+
$aop = new AopClient();
|
|
12
|
+
$aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
|
|
13
|
+
// 沙箱环境: $aop->gatewayUrl = 'https://openapi-sandbox.dl.alipaydev.com/gateway.do';
|
|
14
|
+
$aop->appId = '<-- 请填写您的AppId,例如:20190xxxxxx -->';
|
|
15
|
+
$aop->rsaPrivateKey = '<-- 请填写您的应用私钥(PKCS#1格式),例如:MIIE...(脱敏示例) -->';
|
|
16
|
+
$aop->alipayrsaPublicKey = '<-- 请填写您的支付宝公钥,例如:MIIB...(脱敏示例) -->';
|
|
17
|
+
$aop->format = 'json';
|
|
18
|
+
$aop->postCharset = 'UTF-8';
|
|
19
|
+
$aop->signType = 'RSA2';
|
|
20
|
+
|
|
21
|
+
// 构造请求参数
|
|
22
|
+
$request = new AlipayTradeQueryRequest();
|
|
23
|
+
$bizContent = [
|
|
24
|
+
'out_trade_no' => 'MERCHANT_ORDER_NO_PLACEHOLDER', // 商户订单号,与 trade_no 二选一
|
|
25
|
+
// 'trade_no' => 'ALIPAY_TRADE_NO_PLACEHOLDER', // 支付宝交易号,同时传时以 trade_no 为准
|
|
26
|
+
];
|
|
27
|
+
$request->setBizContent(json_encode($bizContent, JSON_UNESCAPED_UNICODE));
|
|
28
|
+
|
|
29
|
+
// 调用接口
|
|
30
|
+
$response = $aop->execute($request);
|
|
31
|
+
$responseApiName = str_replace('.', '_', $request->getApiMethodName()) . '_response';
|
|
32
|
+
$result = $response->$responseApiName;
|
|
33
|
+
|
|
34
|
+
if (!empty($result->code) && $result->code == '10000') {
|
|
35
|
+
// trade_status: TRADE_SUCCESS/TRADE_FINISHED 表示支付成功,TRADE_CLOSED 表示已关闭
|
|
36
|
+
echo "交易状态: " . $result->trade_status . ", 订单金额: " . $result->total_amount;
|
|
37
|
+
} else {
|
|
38
|
+
echo "调用失败: " . $result->sub_code . " - " . $result->sub_msg;
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 响应示例
|
|
43
|
+
### 正常示例
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"code": "10000",
|
|
47
|
+
"msg": "Success",
|
|
48
|
+
"trade_no": "ALIPAY_TRADE_NO_PLACEHOLDER",
|
|
49
|
+
"out_trade_no": "MERCHANT_ORDER_NO_PLACEHOLDER",
|
|
50
|
+
"trade_status": "TRADE_SUCCESS",
|
|
51
|
+
"total_amount": "88.88",
|
|
52
|
+
"buyer_pay_amount": "8.88",
|
|
53
|
+
"receipt_amount": "15.25",
|
|
54
|
+
"buyer_logon_id": "159****5620",
|
|
55
|
+
"buyer_user_id": "<BUYER_USER_ID>",
|
|
56
|
+
"send_pay_date": "2014-11-27 15:45:57"
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
### 异常示例
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"code": "20000",
|
|
63
|
+
"msg": "Service Currently Unavailable",
|
|
64
|
+
"sub_code": "isp.unknow-error",
|
|
65
|
+
"sub_msg": "系统繁忙"
|
|
66
|
+
}
|
|
67
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# 统一收单交易退款接口 - PHP 示例
|
|
2
|
+
|
|
3
|
+
## 请求示例
|
|
4
|
+
|
|
5
|
+
```php
|
|
6
|
+
<?php
|
|
7
|
+
require_once __DIR__ . '/aop/AopClient.php';
|
|
8
|
+
require_once __DIR__ . '/aop/request/AlipayTradeRefundRequest.php';
|
|
9
|
+
|
|
10
|
+
// 初始化 SDK(PHP 使用 PKCS#1 格式私钥,Java 使用 PKCS#8 格式,请勿混用)
|
|
11
|
+
$aop = new AopClient();
|
|
12
|
+
$aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
|
|
13
|
+
// 沙箱环境: $aop->gatewayUrl = 'https://openapi-sandbox.dl.alipaydev.com/gateway.do';
|
|
14
|
+
$aop->appId = '<-- 请填写您的AppId,例如:20190xxxxxx -->';
|
|
15
|
+
$aop->rsaPrivateKey = '<-- 请填写您的应用私钥(PKCS#1格式),例如:MIIE...(脱敏示例) -->';
|
|
16
|
+
$aop->alipayrsaPublicKey = '<-- 请填写您的支付宝公钥,例如:MIIB...(脱敏示例) -->';
|
|
17
|
+
$aop->format = 'json';
|
|
18
|
+
$aop->postCharset = 'UTF-8';
|
|
19
|
+
$aop->signType = 'RSA2';
|
|
20
|
+
|
|
21
|
+
// 构造请求参数
|
|
22
|
+
$request = new AlipayTradeRefundRequest();
|
|
23
|
+
$bizContent = [
|
|
24
|
+
'out_trade_no' => 'MERCHANT_ORDER_NO_PLACEHOLDER', // 商户订单号
|
|
25
|
+
// 'trade_no' => 'ALIPAY_TRADE_NO_PLACEHOLDER', // 支付宝交易号,与 out_trade_no 二选一
|
|
26
|
+
'refund_amount' => '0.01', // 退款金额
|
|
27
|
+
'refund_reason' => '测试退款', // 退款原因
|
|
28
|
+
'out_request_no' => 'RF' . time(), // 退款请求号,每次退款需不同
|
|
29
|
+
];
|
|
30
|
+
$request->setBizContent(json_encode($bizContent, JSON_UNESCAPED_UNICODE));
|
|
31
|
+
|
|
32
|
+
// 调用接口
|
|
33
|
+
$response = $aop->execute($request);
|
|
34
|
+
$responseApiName = str_replace('.', '_', $request->getApiMethodName()) . '_response';
|
|
35
|
+
$result = $response->$responseApiName;
|
|
36
|
+
|
|
37
|
+
if (!empty($result->code) && $result->code == '10000') {
|
|
38
|
+
$fundChange = $result->fund_change ?? 'N';
|
|
39
|
+
echo "退款状态: " . ($fundChange === 'Y' ? '退款成功' : '未发生资金变化');
|
|
40
|
+
} else {
|
|
41
|
+
echo "调用失败: " . $result->sub_code . " - " . $result->sub_msg;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 响应示例
|
|
46
|
+
### 正常示例
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"code": "10000",
|
|
50
|
+
"msg": "Success",
|
|
51
|
+
"trade_no": "ALIPAY_TRADE_NO_PLACEHOLDER",
|
|
52
|
+
"out_trade_no": "MERCHANT_ORDER_NO_PLACEHOLDER",
|
|
53
|
+
"buyer_logon_id": "159****5620",
|
|
54
|
+
"fund_change": "Y",
|
|
55
|
+
"refund_fee": "0.01"
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
### 异常示例
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"code": "20000",
|
|
62
|
+
"msg": "Service Currently Unavailable",
|
|
63
|
+
"sub_code": "isp.unknow-error",
|
|
64
|
+
"sub_msg": "系统繁忙"
|
|
65
|
+
}
|
|
66
|
+
```
|