@alipay/alipay-aipay 1.0.2 → 1.2.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.
Files changed (67) hide show
  1. package/dist/commands/install.d.ts.map +1 -1
  2. package/dist/commands/install.js +71 -22
  3. package/dist/commands/install.js.map +1 -1
  4. package/dist/commands/prepare-tools.d.ts.map +1 -1
  5. package/dist/commands/prepare-tools.js +16 -4
  6. package/dist/commands/prepare-tools.js.map +1 -1
  7. package/dist/commands/update.d.ts.map +1 -1
  8. package/dist/commands/update.js +9 -9
  9. package/dist/commands/update.js.map +1 -1
  10. package/dist/index.js +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/utils/alipay-cli.d.ts +6 -0
  13. package/dist/utils/alipay-cli.d.ts.map +1 -1
  14. package/dist/utils/alipay-cli.js +38 -22
  15. package/dist/utils/alipay-cli.js.map +1 -1
  16. package/dist/utils/file.d.ts +9 -2
  17. package/dist/utils/file.d.ts.map +1 -1
  18. package/dist/utils/file.js +33 -3
  19. package/dist/utils/file.js.map +1 -1
  20. package/dist/utils/gpg.d.ts.map +1 -1
  21. package/dist/utils/gpg.js +97 -53
  22. package/dist/utils/gpg.js.map +1 -1
  23. package/dist/utils/jq.d.ts.map +1 -1
  24. package/dist/utils/jq.js +6 -9
  25. package/dist/utils/jq.js.map +1 -1
  26. package/dist/utils/npm.d.ts.map +1 -1
  27. package/dist/utils/npm.js +2 -6
  28. package/dist/utils/npm.js.map +1 -1
  29. package/dist/utils/shell.d.ts +15 -2
  30. package/dist/utils/shell.d.ts.map +1 -1
  31. package/dist/utils/shell.js +216 -12
  32. package/dist/utils/shell.js.map +1 -1
  33. package/package.json +3 -4
  34. package/skill/SKILL.md +77 -11
  35. package/skill/VERSION +1 -0
  36. package/skill/references/integration/flow.md +154 -136
  37. package/skill/references/integration/modules/alipay-sdk-reminder.md +29 -13
  38. package/skill/references/integration/modules/checklist.md +49 -19
  39. 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 +3 -3
  40. package/skill/references/integration/modules/code-examples/csharp/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.cs +27 -13
  41. 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 +3 -3
  42. package/skill/references/integration/modules/code-examples/java/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemoController.java +31 -12
  43. 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 +5 -4
  44. package/skill/references/integration/modules/code-examples/nodejs/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.js +69 -37
  45. 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 +3 -3
  46. package/skill/references/integration/modules/code-examples/php/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.php +38 -17
  47. 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 +3 -3
  48. package/skill/references/integration/modules/code-examples/python/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.py +31 -18
  49. package/skill/references/integration/modules/interface-guide.md +7 -3
  50. package/skill/references/integration/modules/sandbox/a2m-sandbox-test.md +77 -48
  51. package/skill/references/integration/modules/sandbox/alipay-sandbox-tool.md +8 -5
  52. package/skill/references/integration/modules/sandbox/sandbox-setup-guide.md +157 -117
  53. package/skill/references/integration/modules/scripts/local_402_sandbox_pay.py +35 -4
  54. package/skill/references/normal/alipay-cli-env.md +13 -11
  55. package/skill/references/normal/self-update.md +59 -0
  56. package/skill/references/onboarding/flow.md +202 -282
  57. package/skill/references/onboarding/modules/app-release.md +91 -35
  58. package/skill/references/onboarding/modules/authorization.md +49 -22
  59. package/skill/references/onboarding/modules/error-handling.md +19 -14
  60. package/skill/references/onboarding/modules/product-sign.md +23 -20
  61. package/skill/references/onboarding/modules/scripts/app.sh +139 -40
  62. package/skill/references/onboarding/modules/scripts/auth.sh +183 -101
  63. package/skill/references/onboarding/modules/scripts/error_handler.sh +148 -18
  64. package/skill/references/onboarding/modules/scripts/query_sign_status.sh +17 -11
  65. package/skill/references/onboarding/modules/scripts/service.sh +208 -37
  66. package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +1 -1
  67. package/skill/references/onboarding/modules/service-registration.md +72 -29
@@ -1,6 +1,6 @@
1
1
  # 支付宝支付集成校验清单
2
2
 
3
- 本文档整理了支付宝支付集成过程中的通用校验项,仅包含必须校验和高危项目。集成完成后,请逐项核对并通过校验模板输出结果。校验结果供参考,开发者务必按照支付宝最新开放平台文档进行检查。
3
+ 本文档整理了支付宝支付集成过程中的通用校验项,仅包含必须校验和高危项目。代码实现后、集成收口前必须逐项核对;逐项核对不等于为每项安装工具或启动运行环境,自动执行边界以 `../flow.md` 步骤 7 为准,无法自动取得证据时标记为“人工待验证”。默认按第八节摘要模板输出,用户要求明细时再展开全部逐项结果。校验结果供参考,开发者务必按照支付宝最新开放平台文档进行检查。
4
4
 
5
5
  ---
6
6
 
@@ -8,15 +8,21 @@
8
8
 
9
9
  | 校验项 | 校验要求 | 说明 |
10
10
  |----------|----------|----------|
11
- | 私钥存储位置 | 私钥不得保存在客户端代码中 | 构造交易数据并签名必须在商家服务端完成,私钥绝对不能保存在商家APP客户端中,也不能从服务端下发。商户需妥善保存私钥,不建议在代码或配置中明文写入私钥,应当通过加密配置文件或秘钥管理平台进行管理 |
11
+ | 私钥存储位置 | 私钥不得保存在客户端代码中 | 构造交易数据并签名必须在商家服务端完成,私钥绝对不能保存在商家APP客户端中,也不能从服务端下发。商户需妥善保存私钥,不建议在代码或配置中明文写入私钥,应当通过加密配置文件或密钥管理平台进行管理 |
12
12
  | 私钥日志安全 | 私钥不得出现在日志中 | 私钥的保密等级比源码高,出现在日志中将增加私钥泄露的风险 |
13
13
  | 私钥仓库安全 | 私钥不得上传公共仓库 | 私钥泄露将导致应用和支付宝交互的安全性完全丧失 |
14
14
  | 沙箱配置后置校验 | 沙箱 appId、应用私钥、支付宝公钥写入项目后必须完成配置准确性校验 | 必须确认写入配置来自同一份沙箱返回配置、与沙箱返回配置一致、字段选择正确、SDK 配置匹配,私钥不是占位符、示例值或其他来源的密钥 |
15
+ | 用户项目文件保护 | 既有项目结构、文件和无关支付能力均保留;新项目只创建在尚不存在或为空的目录 | 检查本轮变更不存在未经独立二次确认的既有文件删除、既有文件整体覆盖,或与已确认目标产品无关的修改;新建文件不属于既有文件整体覆盖。发现违规立即判定不通过并停止后续流程 |
15
16
 
16
17
  ---
17
18
 
18
19
  ## 二、异步通知校验
19
20
 
21
+ 先区分两种验收层级,再判断是否通过:
22
+
23
+ - **本地正式验收 / 本地生产参数验收模式**:允许暂时没有可公网访问的 HTTPS `notify_url`。这一层必须检查“通知处理代码是否已实现”和“支付结果是否可通过主动查询兜底确认”;公网可访问性标记为“人工待验证”,不作为阻断本地正式验收的单一条件。
24
+ - **真实生产上线**:必须具备可公网访问的 HTTPS `notify_url`,并完成验签、关键字段校验、幂等、成功响应 `success` 和补偿查询。未满足时不得判定生产就绪。
25
+
20
26
  | 校验项 | 校验要求 | 说明 |
21
27
  |----------|----------|----------|
22
28
  | 验签必须执行 | 收到异步通知后必须先验签 | 确保通知来自支付宝 |
@@ -26,15 +32,27 @@
26
32
  | 响应值 | 商户侧处理成功后需返回字符串 `success` | 否则支付宝会重试通知 |
27
33
  | URL 格式 | notify_url 不能包含空格和 HTML 标签 | 否则可能导致通知失败 |
28
34
  | 重定向 | notify_url 不能重定向 | 重定向会导致支付宝收不到 success |
29
- | 外网可访问 | notify_url 必须外网可访问 | 需确保支付宝服务器可访问 |
35
+ | 外网可访问 | 真实生产上线时,notify_url 必须外网可访问;本地生产参数验收模式下标记为人工待验证 | 需确保支付宝服务器可访问;本地 `localhost`、局域网地址或 TLS 未就绪域名不阻塞本地正式验收 |
36
+
37
+ ## 三、适用接口覆盖校验
38
+
39
+ | 校验项 | 校验要求 | 说明 |
40
+ |----------|----------|----------|
41
+ | 网站支付/APP支付 | 已实现产品下单、交易查询、退款、退款查询、关闭交易和异步通知处理代码 | 以产品和接口文档为准;缺少本地示例不得作为省略理由。公网通知联调是否完成单独在上一节判断 |
42
+ | 按量付费 | 已实现 402 协议、`alipay.aipay.agent.payment.verify` 和 `alipay.aipay.agent.fulfillment.confirm` | 不使用网站支付/APP支付的通用收单接口 |
43
+ | 限定范围 | 用户明确排除的适用接口已列为待办 | 存在未实现的适用接口时不得判定完整集成 |
30
44
 
31
45
  ---
32
46
 
33
- ## 三、支付结果处理校验
47
+ ## 四、支付结果处理校验
34
48
 
35
49
  | 校验项 | 校验要求 | 说明 |
36
50
  |----------|----------|----------|
37
51
  | 前台同步结果防范 | 前台同步跳转结果仅作通知,**不能作为支付成功的依据** | 必须以异步通知或查询接口结果为准 |
52
+ | 网站支付回跳默认行为 | 用户未明确关闭同步回跳时,已配置项目实际 `return_url` | 地址不得包含示例域名、占位符、猜测端口或未实现路由 |
53
+ | 网站支付回跳页可用性 | 启动服务后 GET 访问精确 `return_url`,并通过 Agent 浏览器/UI 验证或用户明确确认证明页面正常渲染 | 不得出现 404、500、空白页、可见报错、认证循环或重定向循环;单页应用直接刷新路由也必须可用 |
54
+ | 网站支付回跳状态展示 | 先验证同步参数签名,或使用与当前用户会话绑定的服务端订单标识;再通过服务端查询或已验签通知状态展示最终支付结果 | 同步回跳参数不得直接改写订单或作为支付成功依据,不得使用可任意修改的订单号访问其他订单 |
55
+ | 网站支付关闭同步回跳 | 仅当用户已明确关闭时适用;SDK 请求未传 `return_url` | 必须保留异步通知、交易查询和商户订单查询页,且沙箱提示不得声称付款后会自动返回商户网站 |
38
56
  | 支付成功判断 | 仅 `trade_status` 为 `TRADE_SUCCESS` 或 `TRADE_FINISHED` 时才认定付款成功 | 其他状态不算支付成功 |
39
57
  | 主动查询兜底 | 主动查询订单状态必须作为支付结果确认的兜底逻辑 | 无论异步通知是否到达,都应具备主动查询订单状态的能力,防止因网络抖动、通知延迟或丢失导致的状态不一致 |
40
58
  | 未知异常处理 | 网络超时或未知异常时,必须调用查询接口确认 | 不能简单推断为成功或失败 |
@@ -45,7 +63,7 @@
45
63
 
46
64
  ---
47
65
 
48
- ## 四、按量付费专项校验
66
+ ## 五、按量付费专项校验
49
67
 
50
68
  | 校验项 | 校验要求 | 说明 |
51
69
  |----------|----------|----------|
@@ -56,11 +74,13 @@
56
74
  | 金额一致性 | 必须校验本地订单金额与 `Payment-Needed` 账单金额和服务定价一致;若验付接口返回金额字段,再校验实际支付金额 | 防止金额篡改或错误定价 |
57
75
  | 幂等履约 | 同一订单重复携带 `Payment-Proof` 时不得重复发放资源 | 应返回历史履约结果或可重试确认结果 |
58
76
  | 履约确认 | 资源生成后必须调用 `alipay.aipay.agent.fulfillment.confirm` | 确认成功后再标记订单 `FULFILLED` 并返回成功交付 |
77
+ | 沙箱 serviceId | 沙箱联调的 `serviceId` 固定为 `api_mock_service_id` | 不向用户索要正式 `serviceId`,不使用其他占位符 |
59
78
  | 沙箱空字段边界 | 沙箱联调中字段为空可用于排查定位,生产环境关键字段为空必须按异常处理 | 不得把沙箱容错逻辑直接迁移成生产跳过校验 |
79
+ | 生产实现完整性 | 订单持久化、本地订单匹配、金额一致性、资源防串、幂等履约、待确认状态和确认失败重试均已实际实现 | 任一关键控制仍为 TODO、注释、内存演示或伪代码时只能判定沙箱演示可测试,不得判定生产就绪 |
60
80
 
61
81
  ---
62
82
 
63
- ## 五、退款校验
83
+ ## 六、退款校验
64
84
 
65
85
  | 校验项 | 校验要求 | 说明 |
66
86
  |----------|----------|----------|
@@ -70,7 +90,7 @@
70
90
 
71
91
  ---
72
92
 
73
- ## 六、上线前校验
93
+ ## 七、上线前校验
74
94
 
75
95
  | 校验项 | 校验要求 | 说明 |
76
96
  |----------|----------|----------|
@@ -79,6 +99,7 @@
79
99
  | 密钥切换 | 使用生产环境的应用私钥和支付宝公钥 | 与沙箱密钥不同 |
80
100
  | 生产密钥对应关系 | `appId`、应用公钥、应用私钥属于同一套生产应用密钥 | 不得混用沙箱应用、其他生产应用或另一套重新生成的密钥 |
81
101
  | 生产私钥格式 | 应用私钥格式与项目语言 SDK 匹配 | Java 使用 PKCS#8;非 Java 使用 PKCS#1。格式不匹配时使用支付宝开放平台密钥工具转换 |
102
+ | 按量付费 serviceId 替换 | 生产配置中不得保留 `api_mock_service_id` | 上线前替换为服务市场实际返回的真实 `serviceId` |
82
103
  | 证书切换 | 公钥证书模式需切换为生产环境证书 | 应用公钥证书、支付宝公钥证书、支付宝根证书 |
83
104
  | 产品开通状态 | 确认产品已在生产环境开通 | 沙箱无需开通,生产环境需要申请开通 |
84
105
  | 私钥安全 | 私钥不在代码、日志、公共仓库中明文存储 | 使用安全的方式管理私钥 |
@@ -87,33 +108,42 @@
87
108
 
88
109
  ---
89
110
 
90
- ## 七、集成结果校验模板
111
+ ## 八、集成结果输出模板
91
112
 
92
- > 复制以下模板,逐项填写校验结果并输出。校验结果说明:
113
+ > 内部逐项判断使用以下状态:
93
114
  > - ✅ 通过:已实现且符合规范
94
115
  > - ❌ 不通过:未实现或不符合规范,需整改
95
116
  > - ⚠️ 部分通过:部分实现,需补充完善
96
117
  > - ➖ 不适用:当前集成场景不涉及此项
118
+ > - 🟡 人工待验证:当前环境无法自动取得充分证据
119
+
120
+ 默认对客输出不重复罗列每个通过或不适用项,但必须保留所有适用项的检查结论和依据。用户要求完整明细时,再输出逐项表格。
121
+
122
+ 集成校验结论必须与沙箱测试结论分开:沙箱配置字段完整、配置后置校验通过、网站支付付款指引已提供或 APP支付不适用步骤 5,都不得表述为“沙箱测试通过”或“沙箱环境测试通过”。只有按量付费步骤 5 已自动完成 402 Payment-Needed、沙箱收银、Payment-Proof 重试和资源交付全链路并取得成功证据时,才可在本摘要中复用“按量付费沙箱测试通过”的结论。
123
+
124
+ 涉及网站支付或 APP支付 时,校验结论还必须显式区分:
125
+ - “本地正式验收完成 / 本地生产参数验收完成,公网异步通知待补齐”
126
+ - “真实生产上线条件已满足”
127
+
128
+ 未完成公网 HTTPS `notify_url` 联调时,禁止把第一种表述写成第二种。
97
129
 
98
130
  ### 一、校验结论
99
131
  [校验结果整体结论,输出校验结果和风险情况]
100
132
  ---
101
133
 
102
- ### 二、校验清单
134
+ ### 二、通过摘要
135
+
136
+ [列出已完成检查的类别及通过情况,不逐行重复通过项。]
137
+
138
+ ### 三、需要处理的项目
103
139
 
104
- | 校验类别 | 校验项 | 校验结果 | 问题描述 |
140
+ | 校验项 | 状态 | 问题或缺少的证据 | 下一步 |
105
141
  |------|------|------|------|
106
- | 密钥与安全校验 | | | |
107
- | 异步通知校验 | | | |
108
- | 支付结果处理校验 | | | |
109
- | 按量付费专项校验 | | | |
110
- | 退款校验 | | | |
111
- | 上线前校验 | | | |
112
- | **合计** | | | |
142
+ | 仅列出不通过、部分通过和人工待验证项 | | | |
113
143
 
114
144
  ---
115
145
 
116
- ### 三、风险说明
146
+ ### 四、风险说明
117
147
 
118
148
  [未通过校验项(❌不通过、⚠️部分通过)的风险说明,需要提醒用户发布前完成整改或确认风险可接受。]
119
149
 
@@ -12,8 +12,8 @@ using Aop.Api.Domain;
12
12
  IAopClient alipayClient = new DefaultAopClient(GetAlipayConfig());
13
13
 
14
14
  var request = new AlipayTradePagePayRequest();
15
- request.SetNotifyUrl("https://your-domain.com/notify"); // 异步通知地址,必须传入
16
- request.SetReturnUrl("https://your-domain.com/return"); // 同步跳转地址,必须传入
15
+ request.SetNotifyUrl("https://your-domain.com/notify"); // 真实生产或公网联调时传入;本地生产参数验收模式可临时不传,但仍需实现通知处理代码
16
+ request.SetReturnUrl("https://your-domain.com/return"); // 默认传入并替换为已验证结果页;仅用户明确关闭同步回跳时省略
17
17
 
18
18
  var model = new AlipayTradePagePayModel
19
19
  {
@@ -55,4 +55,4 @@ private static AlipayConfig GetAlipayConfig()
55
55
  <input type="submit" value="立即支付" style="display:none" >
56
56
  </form>
57
57
  <script>document.forms[0].submit();</script>
58
- ```
58
+ ```
@@ -1,7 +1,12 @@
1
1
  /*
2
2
  * A2M 智能收产品接入示例 - C# 版本
3
+ *
4
+ * 本示例展示 A2M 核心协议与 SDK 调用流程。
5
+ * 订单持久化、本地订单匹配、金额一致性、资源防串、幂等履约和失败重试
6
+ * 需要结合商户实际数据库与订单模型实现。在上述控制完成实现并通过 checklist 前,
7
+ * 禁止将本文件原样用于生产或判定为生产就绪。
3
8
  *
4
- * 本文件演示完整的智能收产品接入流程:
9
+ * 本文件演示 A2M 核心协议调用流程:
5
10
  * 1. 返回 402 Payment-Needed Header
6
11
  * 2. 验证 Payment-Proof 支付凭证
7
12
  * 3. 发送履约回执确认
@@ -45,7 +50,7 @@ namespace A2MPaymentDemo
45
50
  };
46
51
 
47
52
  private static readonly string SellerId = "<SELLER_ID_2088>"; // 商户 ID(2088 格式)
48
- private static readonly string ServiceId = "<SERVICE_ID>"; // 商户服务 ID
53
+ private static readonly string ServiceId = "api_mock_service_id"; // 仅用于沙箱联调;上线前替换为服务市场真实 serviceId
49
54
  private static readonly string MerchantPrivateKey = "<APP_PRIVATE_KEY>"; // 请填写您的应用私钥(用于商家签名)
50
55
 
51
56
  private const string ResourcePath = "/demo/a2m/resource";
@@ -76,7 +81,7 @@ namespace A2MPaymentDemo
76
81
  await VerifyPaymentAndDeliverResource(response, paymentProof);
77
82
  });
78
83
 
79
- Console.WriteLine($"A2M 智能收服务已启动:http://localhost:5000{ResourcePath}");
84
+ Console.WriteLine($"A2M 按量付费服务已启动:http://localhost:5000{ResourcePath}");
80
85
  Console.WriteLine("测试步骤:");
81
86
  Console.WriteLine($"1. 无 Payment-Proof Header: curl http://localhost:5000{ResourcePath}");
82
87
  Console.WriteLine($"2. 有 Payment-Proof Header: curl -H \"Payment-Proof: <value>\" http://localhost:5000{ResourcePath}");
@@ -147,9 +152,9 @@ namespace A2MPaymentDemo
147
152
  }
148
153
 
149
154
  // 3. RSA2 签名
150
- using (var rsa = new RSACryptoServiceProvider())
155
+ using (var rsa = RSA.Create())
151
156
  {
152
- rsa.ImportPkcs8PrivateKey(Convert.FromBase64String(privateKey), out _);
157
+ rsa.ImportRSAPrivateKey(Convert.FromBase64String(privateKey), out _);
153
158
  var signature = rsa.SignData(Encoding.UTF8.GetBytes(signContent.ToString()), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
154
159
  return Convert.ToBase64String(signature);
155
160
  }
@@ -351,6 +356,8 @@ namespace A2MPaymentDemo
351
356
  var verifyTradeNo = verifyResponse.TradeNo;
352
357
  var verifyOutTradeNo = verifyResponse.OutTradeNo;
353
358
  var resourceId = verifyResponse.ResourceId;
359
+ // 当前 demo 在沙箱字段为空时回退到固定资源;生产实现必须从本地订单读取并校验资源 ID。
360
+ var resourceIdVerified = string.IsNullOrWhiteSpace(resourceId) ? ResourcePath : resourceId;
354
361
  var active = verifyResponse.Active;
355
362
 
356
363
  Console.WriteLine($"支付凭证验证成功:tradeNo={verifyTradeNo}, outTradeNo={verifyOutTradeNo}");
@@ -374,7 +381,7 @@ namespace A2MPaymentDemo
374
381
  // 8. 【TODO】履约防重放校验
375
382
 
376
383
  // 9. 生成资源内容
377
- var serviceResult = GenerateServiceResource(resourceId);
384
+ var serviceResult = GenerateServiceResource(resourceIdVerified);
378
385
 
379
386
  // 10. 【TODO】履约记录落库
380
387
  // 11. 【TODO】保存待确认履约状态
@@ -382,10 +389,11 @@ namespace A2MPaymentDemo
382
389
  // fulfillment.confirm 成功后再标记 FULFILLED;如果确认失败,
383
390
  // 允许同一笔 Payment-Proof 重试确认,避免误返回成功。
384
391
 
385
- Console.WriteLine($"资源已生成,准备发送履约确认:outTradeNo={verifyOutTradeNo}, tradeNo={verifyTradeNo}");
392
+ var fulfillmentTradeNo = string.IsNullOrWhiteSpace(verifyTradeNo) ? tradeNo : verifyTradeNo;
393
+ Console.WriteLine($"资源已生成,准备发送履约确认:outTradeNo={verifyOutTradeNo}, tradeNo={fulfillmentTradeNo}");
386
394
 
387
395
  // 12. 发送履约确认到支付宝,确认成功后才返回成功交付
388
- var fulfillmentConfirmed = await SendFulfillmentConfirm(verifyTradeNo);
396
+ var fulfillmentConfirmed = await SendFulfillmentConfirm(fulfillmentTradeNo);
389
397
  if (!fulfillmentConfirmed)
390
398
  {
391
399
  response.StatusCode = 502;
@@ -402,15 +410,15 @@ namespace A2MPaymentDemo
402
410
  // order.FulfillStatus = "FULFILLED";
403
411
  // order.FulfilledAt = DateTime.Now;
404
412
 
405
- Console.WriteLine($"履约确认成功:outTradeNo={verifyOutTradeNo}, tradeNo={verifyTradeNo}");
413
+ Console.WriteLine($"履约确认成功:outTradeNo={verifyOutTradeNo}, tradeNo={fulfillmentTradeNo}");
406
414
 
407
415
  // 13. 构造 Payment-Validation Header
408
416
  var paymentValidation = new
409
417
  {
410
- trade_no = verifyTradeNo,
418
+ trade_no = fulfillmentTradeNo,
411
419
  out_trade_no = verifyOutTradeNo,
412
420
  validated = true,
413
- resource_id = resourceId
421
+ resource_id = resourceIdVerified
414
422
  };
415
423
 
416
424
  var paymentValidationJson = JsonSerializer.Serialize(paymentValidation, new JsonSerializerOptions
@@ -422,9 +430,9 @@ namespace A2MPaymentDemo
422
430
  // 14. 返回资源内容
423
431
  var responseBody = new
424
432
  {
425
- resource_id = resourceId,
433
+ resource_id = resourceIdVerified,
426
434
  content = serviceResult,
427
- trade_no = verifyTradeNo,
435
+ trade_no = fulfillmentTradeNo,
428
436
  out_trade_no = verifyOutTradeNo,
429
437
  already_fulfilled = false,
430
438
  fulfillment_confirmed = true
@@ -467,6 +475,12 @@ namespace A2MPaymentDemo
467
475
  /// </summary>
468
476
  private static async Task<bool> SendFulfillmentConfirm(string tradeNo)
469
477
  {
478
+ if (string.IsNullOrWhiteSpace(tradeNo))
479
+ {
480
+ Console.WriteLine("履约确认失败:tradeNo 为空");
481
+ return false;
482
+ }
483
+
470
484
  try
471
485
  {
472
486
  Console.WriteLine($"开始发送履约确认:tradeNo={tradeNo}");
@@ -20,8 +20,8 @@ public class AlipayTradePagePay {
20
20
  AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig());
21
21
 
22
22
  AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
23
- request.setNotifyUrl("https://your-domain.com/notify"); // 异步通知地址,必须传入
24
- request.setReturnUrl("https://your-domain.com/return"); // 同步跳转地址,必须传入
23
+ request.setNotifyUrl("https://your-domain.com/notify"); // 真实生产或公网联调时传入;本地生产参数验收模式可临时不传,但仍需实现通知处理代码
24
+ request.setReturnUrl("https://your-domain.com/return"); // 默认传入并替换为已验证结果页;仅用户明确关闭同步回跳时省略
25
25
 
26
26
  AlipayTradePagePayModel model = new AlipayTradePagePayModel();
27
27
  model.setOutTradeNo("MERCHANT_ORDER_NO_PLACEHOLDER"); // 商户订单号,必填
@@ -63,4 +63,4 @@ public class AlipayTradePagePay {
63
63
  <input type="submit" value="立即支付" style="display:none" >
64
64
  </form>
65
65
  <script>document.forms[0].submit();</script>
66
- ```
66
+ ```
@@ -13,6 +13,12 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
+ /*
17
+ * 本示例展示 A2M 核心协议与 SDK 调用流程。
18
+ * 订单持久化、本地订单匹配、金额一致性、资源防串、幂等履约和失败重试
19
+ * 需要结合商户实际数据库与订单模型实现。在上述控制完成实现并通过 checklist 前,
20
+ * 禁止将本文件原样用于生产或判定为生产就绪。
21
+ */
16
22
  package com.alipay.aipayweb.demo;
17
23
 
18
24
  import com.alibaba.fastjson.JSONObject;
@@ -44,7 +50,7 @@ import java.util.*;
44
50
  /**
45
51
  * A2M智能收产品接入示例 Controller
46
52
  *
47
- * 本Controller为开发者示例代码,演示完整的智能收产品接入流程:
53
+ * 本Controller为开发者示例代码,演示 A2M 核心协议调用流程:
48
54
  * 1. 返回 402 Payment-Needed Header
49
55
  * 2. 验证 Payment-Proof 支付凭证
50
56
  * 3. 发送履约回执确认
@@ -83,6 +89,7 @@ public class A2MPaymentDemoController {
83
89
  * 支付宝SDK客户端
84
90
  */
85
91
  private final AlipayClient alipayClient;
92
+ private static final String RESOURCE_PATH = "/demo/a2m/resource";
86
93
 
87
94
  public A2MPaymentDemoController() {
88
95
  // 使用AlipayConfig初始化支付宝客户端(推荐方式)
@@ -115,7 +122,7 @@ public class A2MPaymentDemoController {
115
122
  /**
116
123
  * 智能收产品统一接口
117
124
  *
118
- * 完整流程演示:
125
+ * 核心协议流程演示:
119
126
  * 1. 不带 Payment-Proof Header:返回 HTTP 402 + Payment-Needed Header
120
127
  * 2. 带 Payment-Proof Header:验证支付 → 自动履约 → 返回资源
121
128
  *
@@ -146,7 +153,7 @@ public class A2MPaymentDemoController {
146
153
  String outTradeNo = "ORDER_" + System.currentTimeMillis(); //用户可自定义外部订单号的生成逻辑
147
154
  String amount = "0.01"; // 单位:元
148
155
  String currency = "CNY"; //固定用CNY
149
- String resourceId = "/demo/a2m/resource"; //用户可自定义资源id的生成逻辑,用于资源防串
156
+ String resourceId = RESOURCE_PATH; //用户可自定义资源id的生成逻辑,用于资源防串
150
157
  String goodsName = "AI 生成内容服务"; //用户可自行定义商品名称
151
158
 
152
159
  // 2. 计算支付截止时间(30分钟后),用户可自行设置
@@ -173,7 +180,7 @@ public class A2MPaymentDemoController {
173
180
  // 4. 生成商家签名(需要使用商户私钥)
174
181
  // 注意:实际使用时请从配置中读取商户ID和服务ID
175
182
  String sellerId = "<SELLER_ID_2088>"; // 商户ID(2088格式)
176
- String serviceId = "<SERVICE_ID>"; // 商户服务ID
183
+ String serviceId = "api_mock_service_id"; // 仅用于沙箱联调;上线前替换为服务市场真实 serviceId
177
184
 
178
185
  Map<String, String> signParams = new HashMap<>();
179
186
  signParams.put("amount", amount);
@@ -336,6 +343,10 @@ public class A2MPaymentDemoController {
336
343
  String verifyTradeNo = verifyResponse.getTradeNo(); // 支付宝订单号
337
344
  String verifyOutTradeNo = verifyResponse.getOutTradeNo(); // 商户订单号
338
345
  String resourceId = verifyResponse.getResourceId(); // 资源ID
346
+ // 当前 demo 在沙箱字段为空时回退到固定资源;生产实现必须从本地订单读取并校验资源 ID。
347
+ String resourceIdVerified = resourceId == null || resourceId.trim().isEmpty()
348
+ ? RESOURCE_PATH
349
+ : resourceId;
339
350
  Boolean active = verifyResponse.getActive(); // 凭证有效标识
340
351
 
341
352
  System.out.println("支付凭证验证成功: tradeNo=" + verifyTradeNo + ", outTradeNo=" + verifyOutTradeNo);
@@ -425,7 +436,7 @@ public class A2MPaymentDemoController {
425
436
  // orderRepository.save(order);
426
437
 
427
438
  // 10. 执行业务逻辑,生成资源内容
428
- String serviceResult = generateServiceResource(resourceId);
439
+ String serviceResult = generateServiceResource(resourceIdVerified);
429
440
 
430
441
  // 11. 履约记录落库(用于审计/售后/对账)
431
442
  // 示例逻辑:
@@ -449,10 +460,13 @@ public class A2MPaymentDemoController {
449
460
  // 注意:如果生成资源会产生扣减额度、调用第三方、写业务数据等副作用,
450
461
  // 必须把 serviceResult 和幂等键落库后再允许重试,否则重试可能重复生成或重复扣减。
451
462
 
452
- System.out.println("资源已生成,准备发送履约确认: outTradeNo=" + verifyOutTradeNo + ", tradeNo=" + verifyTradeNo);
463
+ String fulfillmentTradeNo = verifyTradeNo == null || verifyTradeNo.trim().isEmpty()
464
+ ? tradeNo
465
+ : verifyTradeNo;
466
+ System.out.println("资源已生成,准备发送履约确认: outTradeNo=" + verifyOutTradeNo + ", tradeNo=" + fulfillmentTradeNo);
453
467
 
454
468
  // 13. 发送履约确认到支付宝
455
- boolean fulfillmentConfirmed = sendFulfillmentConfirm(verifyTradeNo);
469
+ boolean fulfillmentConfirmed = sendFulfillmentConfirm(fulfillmentTradeNo);
456
470
  if (!fulfillmentConfirmed) {
457
471
  JSONObject errorResponse = new JSONObject();
458
472
  errorResponse.put("code", "FULFILLMENT_CONFIRM_FAILED");
@@ -467,14 +481,14 @@ public class A2MPaymentDemoController {
467
481
  // order.setUpdatedAt(LocalDateTime.now());
468
482
  // orderRepository.save(order);
469
483
 
470
- System.out.println("履约确认成功: outTradeNo=" + verifyOutTradeNo + ", tradeNo=" + verifyTradeNo);
484
+ System.out.println("履约确认成功: outTradeNo=" + verifyOutTradeNo + ", tradeNo=" + fulfillmentTradeNo);
471
485
 
472
486
  // payload 只包含最核心的验证信息
473
487
  JSONObject payload = new JSONObject();
474
- payload.put("trade_no", verifyTradeNo); // 支付宝订单号
488
+ payload.put("trade_no", fulfillmentTradeNo); // 支付宝订单号
475
489
  payload.put("out_trade_no", verifyOutTradeNo); // 商户订单号
476
490
  payload.put("validated", true); // 验证通过标识
477
- payload.put("resource_id", resourceId); // 资源ID(校验用)
491
+ payload.put("resource_id", resourceIdVerified); // 资源ID(校验用)
478
492
 
479
493
  // Base64URL编码
480
494
  String paymentValidationEncoded = Base64.getUrlEncoder()
@@ -483,9 +497,9 @@ public class A2MPaymentDemoController {
483
497
 
484
498
  // 14. 返回资源内容
485
499
  JSONObject resourceContent = new JSONObject();
486
- resourceContent.put("resource_id", resourceId);
500
+ resourceContent.put("resource_id", resourceIdVerified);
487
501
  resourceContent.put("content", serviceResult);
488
- resourceContent.put("trade_no", verifyTradeNo);
502
+ resourceContent.put("trade_no", fulfillmentTradeNo);
489
503
  resourceContent.put("out_trade_no", verifyOutTradeNo);
490
504
  resourceContent.put("already_fulfilled", false);
491
505
  resourceContent.put("fulfillment_confirmed", true);
@@ -547,6 +561,11 @@ public class A2MPaymentDemoController {
547
561
  * @return 履约确认是否成功
548
562
  */
549
563
  private boolean sendFulfillmentConfirm(String tradeNo) {
564
+ if (tradeNo == null || tradeNo.trim().isEmpty()) {
565
+ System.err.println("履约确认失败: tradeNo 为空");
566
+ return false;
567
+ }
568
+
550
569
  try {
551
570
  System.out.println("开始发送履约确认: tradeNo=" + tradeNo);
552
571
 
@@ -17,13 +17,14 @@ const alipaySdk = new AlipaySdk({
17
17
 
18
18
  // ⚠️ 网站支付必须使用 pageExec() 方法,不能使用 exec()
19
19
  // ⚠️ pageExec() 第二个参数必须传 'POST'
20
- // ⚠️ notifyUrl 和 returnUrl 必须传入,否则可能导致验签失败
20
+ // ⚠️ notifyUrl 在真实生产或公网联调时传入;本地生产参数验收模式可临时不传,但仍需实现通知处理代码
21
+ // ⚠️ returnUrl 默认传入,仅在用户明确关闭同步回跳时省略
21
22
  const result = alipaySdk.pageExec(
22
23
  "alipay.trade.page.pay",
23
24
  "POST",
24
25
  {
25
- notifyUrl: "https://your-domain.com/notify", // 异步通知地址,必须传入
26
- returnUrl: "https://your-domain.com/return", // 同步跳转地址,必须传入
26
+ notifyUrl: "https://your-domain.com/notify", // 真实生产或公网联调时传入;本地生产参数验收模式可临时不传
27
+ returnUrl: "https://your-domain.com/return", // 默认传入并替换为已验证结果页;仅用户明确关闭同步回跳时省略
27
28
  bizContent: {
28
29
  out_trade_no: "MERCHANT_ORDER_NO_PLACEHOLDER", // 商户订单号,必填
29
30
  total_amount: "88.88", // 订单总金额,必填,单位为元,精确到小数点后两位
@@ -74,4 +75,4 @@ if (data.paymentHtml) {
74
75
  <input type="submit" value="立即支付" style="display:none" >
75
76
  </form>
76
77
  <script>document.forms[0].submit();</script>
77
- ```
78
+ ```