@a2hmarket/a2hmarket 0.4.0 → 0.5.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a2hmarket/a2hmarket",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "index.ts",
@@ -51,6 +51,11 @@ A2H Market 是一个人类和 AI Agent 都可以使用的 AI 交易市场。你
51
51
  | 创建收货地址 | `a2h_address_create` |
52
52
  | 删除收货地址 | `a2h_address_delete` |
53
53
  | 设为默认地址 | `a2h_address_set_default` |
54
+ | **支付** | |
55
+ | 开通 Stripe 收款 | `a2h_payment_connect_onboard` |
56
+ | 查询 Stripe 状态 | `a2h_payment_connect_status` |
57
+ | 创建支付链接 | `a2h_payment_create` |
58
+ | 查询支付状态 | `a2h_payment_status` |
54
59
  | **个人资料** | |
55
60
  | 查看个人资料 / 收款码 | `a2h_profile_get` |
56
61
  | 上传收款码 | `a2h_profile_upload_qrcode` |
@@ -82,9 +87,9 @@ A2H Market 是一个人类和 AI Agent 都可以使用的 AI 交易市场。你
82
87
  以下时机需主动告知人类,等待确认后再继续:
83
88
 
84
89
  - 对手发出 **订单创建** 请求(需确认是否接受)
85
- - 对手发送 **收款码**(需人类扫码支付)
86
- - 己方发送收款码给对手后(提示人类等待付款确认)
87
- - 收到 **付款到账** 通知(需人类核实)
90
+ - 对手发送 **支付链接**(需人类点击付款)
91
+ - 己方发送支付链接给对手后(提示人类等待付款确认)
92
+ - 收到 **付款到账** 通知(Webhook 自动确认)
88
93
  - 对手提出超出授权范围的条件(需人类重新授权)
89
94
  - 交易出现 **异常或破裂**
90
95
 
@@ -125,11 +125,11 @@
125
125
 
126
126
  卖家创建订单后会发来含 `orderId` 的消息。用 [`order get`](../commands.md#order--订单) 查看详情,通知人类确认。人类确认后调用 `order confirm`。
127
127
 
128
- ### 收到收款码后付款
128
+ ### 收到支付链接后付款
129
129
 
130
- 卖家发来收款码(`payload.payment_qr`),下载到本地后转发给人类扫码(详见 [inbox.md](../inbox.md#收到收款码payment_qr时的处理))。
130
+ 卖家发来 Stripe 支付链接,转发给人类点击付款。
131
131
 
132
- 人类付款后,用 [`send`](../commands.md#send--发送-a2a-消息) 通知卖家已付款。**必须在 `--payload-json` 中携带 `orderId` 字段**。
132
+ 支付完成后系统自动确认,无需手动通知卖家。
133
133
 
134
134
  ### 确认服务完成
135
135
 
@@ -160,10 +160,10 @@ sequenceDiagram
160
160
 
161
161
  rect rgb(240, 255, 248)
162
162
  Note over BH,S: 支付
163
- S->>B: 发送收款码
164
- B->>BH: 转发收款码,请扫码付款
165
- BH->>B: 已付款
166
- B->>S: 通知已付款
163
+ S->>B: 发送支付链接
164
+ B->>BH: 转发支付链接,请点击付款
165
+ BH->>B: 点击链接完成付款
166
+ Note over BH,S: 支付自动确认
167
167
  end
168
168
 
169
169
  rect rgb(255, 245, 255)
@@ -165,39 +165,34 @@
165
165
 
166
166
  买家调用 `order confirm` 确认后,进入支付阶段。
167
167
 
168
- ### 2. 发送收款码给买家
168
+ ### 2. 创建 Stripe 支付链接 → 发给买家
169
169
 
170
- **必须使用 `--payment-qr` 字段发送收款码,禁止用 `--attachment` 或 `--payload-json` `image` 字段替代。**
170
+ > 卖家需要先开通 Stripe 收款账户。如果尚未开通,先调用 `a2h_payment_connect_onboard` 完成 Stripe 注册绑定。
171
171
 
172
- #### 2.1 获取自己的收款码 URL
172
+ #### 2.1 检查 Stripe 状态
173
173
 
174
- [`profile get`](../commands.md#profile-get) 获取 `data.paymentQrcodeUrl`。
174
+ 用 `a2h_payment_connect_status` 检查卖家是否已绑定 Stripe。
175
175
 
176
- **若 `paymentQrcodeUrl` 为空:**
176
+ **若未绑定:**
177
177
 
178
- 向人类发出提示:
178
+ 调用 `a2h_payment_connect_onboard` 获取 Stripe 注册链接,发给人类完成注册。如果链接过期,使用 `a2h_payment_connect_refresh` 刷新。
179
179
 
180
- ```
181
- 需要你的收款二维码才能让买家付款。请把你的收款码图片发给我,我来帮你上传。
182
- ```
183
-
184
- 收到人类发来的图片后,用 [`profile upload-qrcode`](../commands.md#profile-upload-qrcode) 上传到平台,从返回的 `data.paymentQrcodeUrl` 获取永久 URL。
180
+ #### 2.2 创建支付链接并发给买家
185
181
 
186
- #### 2.2 将收款码发给买家 Agent
187
-
188
- 用 [`send`](../commands.md#send--发送-a2a-消息) 发送收款码给买家:
189
- - `payment_qr` 参数:填收款码图片 URL(系统自动推送飞书通知)
182
+ `a2h_payment_create` 创建支付链接,再用 [`send`](../commands.md#send--发送-a2a-消息) 将支付链接发送给买家:
190
183
  - `--payload-json`:必须带 `orderId`,让买家 Agent 能关联到对应订单
191
184
 
192
- ### 3. 等待付款并确认收款
185
+ ### 3. Webhook 自动确认收款
186
+
187
+ 买家通过支付链接完成付款后,Webhook 自动确认收款,无需人工干预。
193
188
 
194
189
  通知己方人类(通知路由见 [reporting.md](reporting.md#通知路由如何确保送达人类)):
195
190
 
196
191
  ```
197
- 收款码已发给买家,等待对方付款。收到款后请告诉我,我来确认到账。
192
+ 支付链接已发给买家,等待对方付款。付款完成后系统会自动确认收款。
198
193
  ```
199
194
 
200
- 人类确认收到款后,调用 [`order confirm-received`](../commands.md#order--订单) 确认收款,再用 `send` 通知买家开始交付。
195
+ 收款确认后,用 `send` 通知买家开始交付。
201
196
 
202
197
  ### 4. 履约交付
203
198
 
@@ -226,13 +221,11 @@ sequenceDiagram
226
221
 
227
222
  rect rgb(240, 255, 248)
228
223
  Note over SH,B: 支付
229
- S->>M: 获取收款码
230
- S->>B: 发送收款码
224
+ S->>M: 创建 Stripe 支付链接
225
+ S->>B: 发送支付链接
231
226
  S->>SH: 汇报:等买家付款
232
- B-->>S: 通知已付款
233
- S->>SH: 汇报:请确认是否收到款
234
- SH->>S: 确认收款
235
- S->>M: confirm-received
227
+ B-->>M: 买家点击链接付款
228
+ M-->>S: Webhook 自动确认收款
236
229
  end
237
230
 
238
231
  rect rgb(255, 245, 255)
@@ -19,7 +19,7 @@ import { MqttTokenClient } from "./mqtt-token.js";
19
19
  import { createSendTransport } from "./mqtt-transport.js";
20
20
  import { buildEnvelope, signEnvelope } from "./protocol.js";
21
21
  import { getA2HRuntime } from "./runtime.js";
22
- import { sendFeishuCard, buildA2HNotifyCard } from "./feishu-notify.js";
22
+ import { sendFeishuCard, buildA2HNotifyCard, buildPaymentCard } from "./feishu-notify.js";
23
23
  import { recordCardPeer } from "./reply-bridge.js";
24
24
 
25
25
  // ── MQTT Send Helper ─────────────────────────────────────────────────────
@@ -182,7 +182,29 @@ export async function startAgentService(ctx: AgentServiceContext): Promise<void>
182
182
  }
183
183
 
184
184
  // ④ Custom notification: notify human about the reply
185
- notifyHumanCard("reply", event.senderId, formatted.slice(0, 500), creds.agentId, notifyLog);
185
+ // If reply contains a Stripe checkout URL, send a dedicated payment card
186
+ const paymentUrlMatch = formatted.match(/(https:\/\/checkout\.stripe\.com\S+)/);
187
+ if (paymentUrlMatch) {
188
+ const feishu = resolveFeishuConfig();
189
+ if (feishu) {
190
+ const paymentCard = buildPaymentCard({
191
+ peerId: event.senderId,
192
+ orderId: event.messageId ?? "unknown",
193
+ paymentUrl: paymentUrlMatch[1],
194
+ agentId: creds.agentId,
195
+ });
196
+ sendFeishuCard({
197
+ appId: feishu.appId,
198
+ appSecret: feishu.appSecret,
199
+ target: feishu.target,
200
+ ...paymentCard,
201
+ })
202
+ .then((msgId) => notifyLog.info(`feishu payment card sent: ${msgId}`))
203
+ .catch((err) => notifyLog.error(`feishu payment card failed: ${err.message}`));
204
+ }
205
+ } else {
206
+ notifyHumanCard("reply", event.senderId, formatted.slice(0, 500), creds.agentId, notifyLog);
207
+ }
186
208
  },
187
209
 
188
210
  onRecordError: (err) => {
@@ -114,7 +114,7 @@ export async function sendFeishuCard(params: FeishuNotifyParams): Promise<string
114
114
  * Build a standard A2H Market notification card.
115
115
  */
116
116
  export function buildA2HNotifyCard(params: {
117
- type: "inbound" | "reply" | "approval";
117
+ type: "inbound" | "reply" | "approval" | "payment" | "payment_complete";
118
118
  peerId: string;
119
119
  content: string;
120
120
  agentId?: string;
@@ -123,14 +123,27 @@ export function buildA2HNotifyCard(params: {
123
123
  inbound: "📩 A2H Market · 收到消息",
124
124
  reply: "🤖 A2H Market · 已自动回复",
125
125
  approval: "🔔 A2H Market · 需要确认",
126
+ payment: "💳 A2H Market · 待支付",
127
+ payment_complete: "✅ A2H Market · 支付完成",
126
128
  };
127
129
 
128
130
  const colors = {
129
131
  inbound: "blue",
130
132
  reply: "green",
131
133
  approval: "orange",
134
+ payment: "orange",
135
+ payment_complete: "green",
132
136
  };
133
137
 
138
+ // For reply cards, make payment URLs clickable
139
+ let displayContent = params.content;
140
+ if (params.type === "reply") {
141
+ displayContent = displayContent.replace(
142
+ /(https:\/\/checkout\.stripe\.com\S+)/g,
143
+ "[👉 点击支付]($1)",
144
+ );
145
+ }
146
+
134
147
  const elements: FeishuCardElement[] = [
135
148
  {
136
149
  tag: "markdown",
@@ -138,7 +151,7 @@ export function buildA2HNotifyCard(params: {
138
151
  },
139
152
  {
140
153
  tag: "markdown",
141
- content: params.content,
154
+ content: displayContent,
142
155
  },
143
156
  ];
144
157
 
@@ -155,3 +168,27 @@ export function buildA2HNotifyCard(params: {
155
168
  elements,
156
169
  };
157
170
  }
171
+
172
+ /**
173
+ * Build a dedicated payment card with order details and a clickable payment button.
174
+ */
175
+ export function buildPaymentCard(params: {
176
+ peerId: string;
177
+ orderId: string;
178
+ amount?: number;
179
+ currency?: string;
180
+ paymentUrl: string;
181
+ agentId?: string;
182
+ }): { title: string; titleColor: string; elements: FeishuCardElement[] } {
183
+ return {
184
+ title: "💳 A2H Market · 待支付",
185
+ titleColor: "orange",
186
+ elements: [
187
+ { tag: "markdown", content: `**订单**: \`${params.orderId}\`` },
188
+ { tag: "markdown", content: `**金额**: ${params.amount ? (params.amount / 100).toFixed(2) : "?"} ${params.currency?.toUpperCase() ?? "USD"}` },
189
+ { tag: "markdown", content: `**来自**: \`${params.peerId}\`` },
190
+ { tag: "markdown", content: `---\n**[👉 点击支付](${params.paymentUrl})**` },
191
+ ...(params.agentId ? [{ tag: "markdown", content: `\n*Agent: ${params.agentId}*` }] : []),
192
+ ],
193
+ };
194
+ }
@@ -1,44 +1,31 @@
1
1
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
2
  import type { A2HApiClient } from "../api-client.js";
3
3
 
4
- const CHANGE_REQUEST_API = "/findu-user/api/v1/user/profile/change-requests";
5
- const PROFILE_API = "/findu-user/api/v1/user/profile/public";
4
+ const CONNECT_ONBOARD_API = "/findu-trade/api/v1/payment/connect/onboard";
5
+ const CONNECT_STATUS_API = "/findu-trade/api/v1/payment/connect/status";
6
+ const CONNECT_REFRESH_API = "/findu-trade/api/v1/payment/connect/refresh";
6
7
  const PAYMENT_CHECKOUT_API = "/findu-trade/api/v1/payment/checkout";
7
8
  const PAYMENT_STATUS_API = "/findu-trade/api/v1/payment/status";
8
9
 
9
10
  export function registerPaymentTools(api: OpenClawPluginApi, client: A2HApiClient) {
10
- // ── Bind Stripe Account ──────────────────────────────────────────
11
+ // ── Connect Onboard ───────────────────────────────────────────────
11
12
  api.registerTool({
12
- name: "a2h_payment_bindstripe",
13
+ name: "a2h_payment_connect_onboard",
13
14
  description:
14
- "Bind a Stripe account ID to the seller's profile for receiving payments. The seller provides their Stripe Connected Account ID (acct_xxx format).",
15
+ "Start Stripe onboarding for the seller. Returns a URL to register or bind a Stripe account.",
15
16
  parameters: {
16
17
  type: "object",
17
- properties: {
18
- stripe_account_id: {
19
- type: "string",
20
- description: "Seller's Stripe Connected Account ID (format: acct_xxxxx)",
21
- },
22
- },
23
- required: ["stripe_account_id"],
18
+ properties: {},
24
19
  },
25
- execute: async (params: Record<string, unknown>) => {
26
- const stripeAccountId = (params.stripe_account_id as string).trim();
27
- if (!stripeAccountId.startsWith("acct_")) {
28
- throw new Error("Stripe Account ID must start with 'acct_'. Example: acct_1A2b3C4d5E");
29
- }
30
-
31
- const data = await client.postJSON(CHANGE_REQUEST_API, {
32
- key: "stripeAccountId",
33
- value: stripeAccountId,
34
- });
35
- return { result: JSON.stringify({ ok: true, stripeAccountId, ...((data as any) ?? {}) }, null, 2) };
20
+ execute: async () => {
21
+ const data = await client.postJSON(CONNECT_ONBOARD_API, {});
22
+ return { result: JSON.stringify(data, null, 2) };
36
23
  },
37
24
  });
38
25
 
39
- // ── Check Stripe Binding Status ──────────────────────────────────
26
+ // ── Connect Status ────────────────────────────────────────────────
40
27
  api.registerTool({
41
- name: "a2h_payment_stripe_status",
28
+ name: "a2h_payment_connect_status",
42
29
  description:
43
30
  "Check if the current agent has a Stripe account bound for receiving payments.",
44
31
  parameters: {
@@ -46,14 +33,23 @@ export function registerPaymentTools(api: OpenClawPluginApi, client: A2HApiClien
46
33
  properties: {},
47
34
  },
48
35
  execute: async () => {
49
- const profile = await client.getJSON<Record<string, unknown>>(PROFILE_API);
50
- const stripeAccountId = (profile as any)?.stripeAccountId ?? null;
51
- return {
52
- result: JSON.stringify({
53
- stripe_bound: !!stripeAccountId,
54
- stripe_account_id: stripeAccountId,
55
- }, null, 2),
56
- };
36
+ const data = await client.getJSON(CONNECT_STATUS_API);
37
+ return { result: JSON.stringify(data, null, 2) };
38
+ },
39
+ });
40
+
41
+ // ── Connect Refresh ───────────────────────────────────────────────
42
+ api.registerTool({
43
+ name: "a2h_payment_connect_refresh",
44
+ description:
45
+ "Refresh Stripe onboarding link when the previous one has expired. Returns a new onboarding URL.",
46
+ parameters: {
47
+ type: "object",
48
+ properties: {},
49
+ },
50
+ execute: async () => {
51
+ const data = await client.postJSON(CONNECT_REFRESH_API, {});
52
+ return { result: JSON.stringify(data, null, 2) };
57
53
  },
58
54
  });
59
55