@aeon-ai-pay/aigateway 0.1.5 → 0.2.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.
@@ -1,114 +0,0 @@
1
- # Create Virtual Card
2
-
3
- ## Prerequisites
4
-
5
- Before creating a card, confirm the following:
6
-
7
- 1. Wallet is configured — run `wallet-init`. If not ready, the CLI will auto-create one.
8
- 2. Service URL is configured (built-in default is available; no action needed unless user wants to override)
9
- 3. The `create` command automatically checks allowance and wallet balance before payment. If insufficient, it auto-initiates WalletConnect funding — no need to run `wallet` or `wallet-topup` separately.
10
-
11
- ## Workflow
12
-
13
- ### Step 1: Confirm Amount
14
-
15
- Ask the user how much to load onto the virtual card.
16
-
17
- - Amount limits are enforced by the CLI (`amountLimits.min` ~ `amountLimits.max`, from `wallet-init`).
18
- - Currency: USD (server handles crypto conversion)
19
-
20
- **If user does not specify an amount**, show the valid range and ask for confirmation (**copy must be verbatim**, variable substitution only):
21
- > "You can create a card of up to ${min}~${max}. How much would you like to load onto the card?"
22
-
23
- Once the user specifies an amount, **execute immediately** — no second confirmation needed.
24
-
25
- ### Step 2: Execute
26
-
27
- ```bash
28
- # Create card and auto-poll status
29
- npx @aeon-ai-pay/aigateway create --amount <amount> --poll
30
-
31
- # Optional: specify merchant app ID (defaults to TEST000001)
32
- npx @aeon-ai-pay/aigateway create --amount <amount> --app-id <merchantAppId> --poll
33
- ```
34
-
35
- > `--app-id` is the merchant identifier sent with the request; defaults to `TEST000001` when omitted.
36
-
37
- CLI automatically handles the full flow:
38
- 1. Send `GET /open/ai/x402/card/create?amount=X&appId=Y` → receive HTTP 402 + payment requirements (exact USDT amount)
39
- 2. Check allowance → if insufficient and no BNB, mark BNB needed
40
- 3. Check USDT balance → if insufficient, mark top-up needed
41
- 4. If top-up or BNB needed → auto-initiate WalletConnect funding (opens QR page, waits for user to confirm in wallet app)
42
- 5. After funding completes, auto-continue
43
- 6. Approve authorization (only on first use or when allowance insufficient, costs small amount of BNB)
44
- 7. Sign with the exact amount from the first 402 response using EIP-712
45
- 8. Retry request with `PAYMENT-SIGNATURE` header → receive HTTP 200
46
- 9. With `--poll`, polls up to 42 times (first 5 at 2-second intervals, then every 5 seconds) until card is ready
47
-
48
- ### Step 3: Parse Result
49
-
50
- **stdout** outputs JSON (parseable), **stderr** outputs progress logs.
51
-
52
- Successful output:
53
- ```json
54
- {
55
- "success": true,
56
- "data": {
57
- "code": "0",
58
- "msg": "success",
59
- "model": { "orderNo": "300217748668047431791" }
60
- },
61
- "paymentResponse": {
62
- "txHash": "0x...",
63
- "networkId": "eip155:56"
64
- }
65
- }
66
- ```
67
-
68
- With `--poll`, additional output after card is ready:
69
- ```json
70
- {
71
- "pollResult": {
72
- "orderNo": "300217748668047431791",
73
- "orderStatus": "SUCCESS",
74
- "channelStatus": "COMPLETED",
75
- "orderAmount": 0.6,
76
- "txHash": "0xabc...def",
77
- "cardLastFour": "4321",
78
- "cardBin": "485932",
79
- "cardScheme": "VISA",
80
- "cardBalance": 0.6,
81
- "cardStatus": "ACTIVE"
82
- }
83
- }
84
- ```
85
-
86
- ### Step 4: Present to User
87
-
88
- Fetching card details may take about 30 seconds. Output a waiting prompt first (**copy must be verbatim**):
89
- ```
90
- > Fetching card details, please wait...
91
- ```
92
-
93
- Once details are returned, on success (**copy must be verbatim**, variable substitution only):
94
- ```
95
- Order No: {orderNo}
96
- Card: {cardScheme} •••• {last4}
97
- State: Active
98
- Remaining balance: ${amount} USD
99
- Usage: 0 / 1 (single-use)
100
- ```
101
-
102
- Save the `orderNo` for subsequent status queries.
103
-
104
- ## Error Handling
105
-
106
- | Scenario | CLI Output | Action |
107
- |------|---------|---------|
108
- | Amount out of range | Error JSON with allowed range | Relay to user |
109
- | Wallet not configured | `Wallet not configured` | Run `wallet-init` |
110
- | Funding signature timeout (5 min) | `Payment approval timed out. Please try again.` | Relay to user, ask if they want to retry |
111
- | User rejected signature | `Payment approval was rejected. Please try again if you'd like to proceed.` | Relay to user, do not auto-retry |
112
- | Insufficient balance after funding | `Still insufficient USDT after funding` | Relay to user |
113
- | Network error | Server error JSON | Retry once, then report to user |
114
- | Transaction reverted | txHash | Suggest user check on BSCScan |
@@ -1,87 +0,0 @@
1
- # AEON AI Card Store
2
-
3
- ## When to use
4
-
5
- Trigger this reference when the user says:
6
- - "what can I buy"
7
- - "show me what's available"
8
- - "what can I do?"
9
- - "what can I use the card for"
10
-
11
- Present options conversationally. Do not dump the full list — highlight what's most relevant.
12
-
13
- ---
14
-
15
- ## Virtual Card Use Cases
16
-
17
- ### AEON Agent Card – Supported & Upcoming Use Cases
18
-
19
- **Coming Soon**
20
- - Subscriptions (ChatGPT, Claude, Midjourney)
21
- - Ads (Google, Meta)
22
- - Travel bookings
23
- - SaaS tools
24
-
25
- **Tell us what you want**
26
-
27
- Submit your request here:
28
- 👉 [Google Form link]
29
-
30
- ---
31
-
32
- ## x402 API Payments
33
-
34
- Pay these services directly using `x402 fetch`. No card needed — payment goes straight from your wallet.
35
-
36
- ### AI & Data
37
-
38
- | Service | What it does | Chain |
39
- |---|---|---|
40
- | AskClaude | Per-question Claude AI access ($0.01–$0.10/query) | Base |
41
- | Arch AI Tools | 53 AI tools: web search, image generation, fact-checking | Base |
42
- | Firecrawl | Web scraping and LLM-ready content extraction | Base |
43
- | Gloria AI | Real-time news data for agents | Base |
44
- | Minifetch | Web metadata and content summaries | Base |
45
-
46
- ### Blockchain & DeFi
47
-
48
- | Service | What it does | Chain |
49
- |---|---|---|
50
- | Messari | Crypto research and on-chain data | Base |
51
- | Nansen | Wallet intelligence and blockchain analytics | Base |
52
- | DiamondClaws | DeFi yield scoring and protocol risk analysis | Base |
53
- | Stakevia | Solana validator intelligence | Solana |
54
-
55
- ### Infrastructure
56
-
57
- | Service | What it does | Chain |
58
- |---|---|---|
59
- | Pinata | IPFS file uploads and retrievals, no account required | Base |
60
- | Run402 | Postgres databases and serverless functions, no signup | Base |
61
- | Alchemy | Pay-per-request RPC / blockchain API access | Base |
62
- | Robtex | DNS and network intelligence APIs | Base |
63
-
64
- ### Payments & Commerce
65
-
66
- | Service | What it does | Chain |
67
- |---|---|---|
68
- | Bitrefill | Buy gift cards and prepaid cards with crypto | Base |
69
- | ClawCredit | Access x402 services on credit, pay later | Base |
70
-
71
- Full registry: [x402.org/ecosystem](https://www.x402.org/ecosystem) · [x402list.fun](https://x402list.fun)
72
-
73
- ---
74
-
75
- ## How to Present to the User
76
-
77
- Example response when user has no specific intent:
78
-
79
- > "Here's what you can do with AEON AI Card:
80
- >
81
- > - **Virtual card** — coming soon: subscribe to ChatGPT, Claude, Midjourney, run ads, book travel
82
- > - **Pay AI APIs** — call Claude, Firecrawl, web search per request via x402
83
- > - **Access DeFi data** — Nansen, Messari on-chain analytics via x402
84
- >
85
- > What would you like to do?"
86
-
87
- Adapt based on context. For virtual card intent → route to create-card flow. For x402 API intent → route to x402 flow.
@@ -1,67 +0,0 @@
1
- import { resolve } from "../config.mjs";
2
- import { POLL_INTERVAL, MAX_POLLS } from "../constants.mjs";
3
- import { sanitizeOutput } from "../sanitize.mjs";
4
- import { emitOk, emitErr, logInfo } from "../output.mjs";
5
-
6
- export async function status(opts) {
7
- const { default: axios } = await import("axios");
8
- const serviceUrl = resolve(opts.serviceUrl, "AIGATEWAY_SERVICE_URL", "serviceUrl");
9
- const { orderNo, poll, appId } = opts;
10
-
11
- if (!serviceUrl) {
12
- emitErr("create-card-status", "SERVICE_URL_MISSING", {
13
- message: "Missing service URL. Set env AIGATEWAY_SERVICE_URL if you need to override the built-in default.",
14
- appId,
15
- });
16
- return;
17
- }
18
-
19
- const url = `${serviceUrl}/open/ai/x402/card/status?orderNo=${encodeURIComponent(orderNo)}&appId=${encodeURIComponent(appId)}`;
20
-
21
- if (!poll) {
22
- try {
23
- const res = await axios.get(url);
24
- const sanitized = sanitizeOutput(res.data);
25
- emitOk("create-card-status", { appId, ...sanitized }, sanitized);
26
- } catch (error) {
27
- emitErr("create-card-status", "SERVICE_UNAVAILABLE", {
28
- message: error.message,
29
- status: error.response?.status,
30
- data: error.response?.data,
31
- appId,
32
- });
33
- }
34
- return;
35
- }
36
-
37
- logInfo(`Polling ${url} every ${POLL_INTERVAL / 1000}s (max ${MAX_POLLS} times)`);
38
-
39
- for (let i = 1; i <= MAX_POLLS; i++) {
40
- try {
41
- const res = await axios.get(url);
42
- const model = res.data?.model;
43
-
44
- logInfo(
45
- `[${i}/${MAX_POLLS}] orderStatus=${model?.orderStatus} channelStatus=${model?.channelStatus} cardStatus=${model?.cardStatus || "-"}`,
46
- );
47
-
48
- if (model?.orderStatus === "SUCCESS" || model?.orderStatus === "FAIL") {
49
- const sanitized = sanitizeOutput(res.data);
50
- emitOk("create-card-status", { appId, ...sanitized }, sanitized);
51
- return;
52
- }
53
- } catch (e) {
54
- logInfo(`[${i}/${MAX_POLLS}] Error: ${e.message}`);
55
- }
56
-
57
- if (i < MAX_POLLS) {
58
- await new Promise((r) => setTimeout(r, POLL_INTERVAL));
59
- }
60
- }
61
-
62
- emitErr("create-card-status", "POLL_TIMEOUT", {
63
- orderNo,
64
- appId,
65
- message: "Polling timeout. Card may still be provisioning.",
66
- });
67
- }
@@ -1,352 +0,0 @@
1
- /**
2
- * create-card: issue a one-time virtual card by paying with USDT on BSC over the x402 protocol.
3
- *
4
- * Server endpoint: GET {serviceUrl}/open/ai/x402/card/create?amount=<usd>&appId=<merchant>
5
- * Flow: fetch payment requirements -> check balance + allowance
6
- * -> (if balance is insufficient) top up via funding.mjs/fundSessionKey
7
- * -> submit x402 EIP-712 signature -> optionally poll status
8
- */
9
- import { createX402Api, decodePaymentResponse, fetchPaymentRequirements } from "../x402.mjs";
10
- import { resolve } from "../config.mjs";
11
- import { getWalletBalance, getAllowance } from "../balance.mjs";
12
- import { sanitizeOutput } from "../sanitize.mjs";
13
- import axios from "axios";
14
- import {
15
- MIN_AMOUNT, MAX_AMOUNT, POLL_INTERVAL, MAX_POLLS,
16
- } from "../constants.mjs";
17
- import { WalletConnectError } from "../walletconnect.mjs";
18
- import {
19
- fundSessionKey,
20
- promptTopupAmount,
21
- MIN_TOPUP_USDT,
22
- TOPUP_PRESETS,
23
- } from "../funding.mjs";
24
- import { emitOk, emitErr, logInfo } from "../output.mjs";
25
-
26
- export async function createCard(opts) {
27
- logInfo("Creating Agent Card...");
28
- const serviceUrl = resolve(opts.serviceUrl, "AIGATEWAY_SERVICE_URL", "serviceUrl");
29
- const privateKey = resolve(opts.privateKey, "EVM_PRIVATE_KEY", "privateKey");
30
- const { amount, poll, appId, dryRun } = opts;
31
- const amountNum = parseFloat(amount);
32
-
33
- if (!serviceUrl) {
34
- emitErr("create-card", "SERVICE_URL_MISSING", {
35
- message: "Missing service URL. Set env AIGATEWAY_SERVICE_URL if you need to override the built-in default.",
36
- appId,
37
- });
38
- return;
39
- }
40
- if (!privateKey) {
41
- emitErr("create-card", "WALLET_NOT_CONFIGURED", { appId });
42
- return;
43
- }
44
- if (isNaN(amountNum) || amountNum < MIN_AMOUNT) {
45
- emitErr("create-card", "AMOUNT_OUT_OF_RANGE", {
46
- message: `Amount must be at least $${MIN_AMOUNT}. Allowed range: $${MIN_AMOUNT} ~ $${MAX_AMOUNT} USD.`,
47
- min: MIN_AMOUNT,
48
- max: MAX_AMOUNT,
49
- appId,
50
- });
51
- return;
52
- }
53
- if (amountNum > MAX_AMOUNT) {
54
- emitErr("create-card", "AMOUNT_OUT_OF_RANGE", {
55
- message: `Amount must not exceed $${MAX_AMOUNT}. Allowed range: $${MIN_AMOUNT} ~ $${MAX_AMOUNT} USD.`,
56
- min: MIN_AMOUNT,
57
- max: MAX_AMOUNT,
58
- appId,
59
- });
60
- return;
61
- }
62
-
63
- const url = `${serviceUrl}/open/ai/x402/card/create?amount=${encodeURIComponent(amount)}&appId=${encodeURIComponent(appId)}`;
64
- logInfo("Fetching payment requirements...");
65
- let requiredUsdt;
66
- let paymentReq;
67
- try {
68
- paymentReq = await fetchPaymentRequirements(url);
69
- requiredUsdt = paymentReq.amountUsdt;
70
- logInfo(`Required: ${requiredUsdt} USDT (pay to ${paymentReq.payTo})`);
71
- } catch (e) {
72
- emitErr("create-card", "PAYMENT_FETCH_FAILED", {
73
- message: `Failed to fetch payment requirements: ${e.message}`,
74
- appId,
75
- });
76
- return;
77
- }
78
-
79
- logInfo("Checking wallet...");
80
- let needTopup = false;
81
- let needGas = false;
82
- let sessionAddress;
83
- let topupAmount = null;
84
- let balanceInitialUsdt = null;
85
- let balanceBeforeChargeUsdt = null;
86
-
87
- try {
88
- const { address, usdt, bnb, bnbRaw } = await getWalletBalance(privateKey);
89
- sessionAddress = address;
90
- balanceInitialUsdt = usdt;
91
- balanceBeforeChargeUsdt = usdt;
92
- const usdtNum = parseFloat(usdt);
93
- logInfo(`Wallet: ${address}`);
94
- logInfo(`Balance: ${usdt} USDT, ${bnb} BNB`);
95
-
96
- const allowance = await getAllowance(address);
97
- const requiredWei = BigInt(paymentReq.amountWei);
98
- if (requiredWei === 0n) {
99
- emitErr("create-card", "INVALID_PAYMENT_AMOUNT", {
100
- message: "Server returned invalid payment amount (0). Please retry later.",
101
- appId,
102
- });
103
- return;
104
- }
105
- if (allowance >= requiredWei) {
106
- logInfo("Allowance sufficient, no approve needed.");
107
- } else {
108
- logInfo(`Allowance ${allowance} < required ${requiredWei}; approve needed.`);
109
- if (bnbRaw === 0n) {
110
- needGas = true;
111
- logInfo("No BNB for approve gas, will request BNB transfer.");
112
- }
113
- }
114
-
115
- if (usdtNum < requiredUsdt) {
116
- needTopup = true;
117
- const shortfall = requiredUsdt - usdtNum;
118
- const minTopup = Math.max(MIN_TOPUP_USDT, Math.ceil(shortfall));
119
- logInfo(`USDT insufficient: have ${usdtNum}, need ${requiredUsdt}, shortfall ${shortfall.toFixed(6)} (top-up minimum: ${minTopup} USDT)`);
120
-
121
- if (opts.topupAmount != null && String(opts.topupAmount).trim() !== "") {
122
- const amt = Number(opts.topupAmount);
123
- if (!Number.isFinite(amt) || amt <= 0) {
124
- emitErr("create-card", "AMOUNT_INVALID", {
125
- message: `Invalid --topup-amount: ${opts.topupAmount}`,
126
- appId,
127
- });
128
- return;
129
- }
130
- if (amt < minTopup) {
131
- emitErr("create-card", "TOPUP_AMOUNT_TOO_SMALL", {
132
- message: `--topup-amount ${amt} USDT is below the ${minTopup} USDT minimum for this call.`,
133
- minTopup,
134
- appId,
135
- });
136
- return;
137
- }
138
- topupAmount = String(opts.topupAmount);
139
- logInfo(`Using --topup-amount: ${topupAmount} USDT`);
140
- } else if (process.stdin.isTTY) {
141
- topupAmount = await promptTopupAmount(minTopup);
142
- logInfo(`Selected top-up amount: ${topupAmount} USDT`);
143
- } else {
144
- const presets = TOPUP_PRESETS.filter((v) => v >= minTopup);
145
- emitErr("create-card", "TOPUP_REQUIRED", {
146
- message: `USDT balance is below the ${minTopup} USDT minimum for this call. Choose a top-up amount and rerun with --topup-amount <usdt>.`,
147
- minTopup,
148
- required: requiredUsdt,
149
- currentBalance: balanceInitialUsdt,
150
- address: sessionAddress,
151
- appId,
152
- presets,
153
- hint: `Rerun: aigateway wallet-topup --amount <usdt> --app-id ${appId}`,
154
- });
155
- return;
156
- }
157
- }
158
- } catch (e) {
159
- emitErr("create-card", "BALANCE_CHECK_FAILED", {
160
- message: `Balance check failed: ${e.message}`,
161
- appId,
162
- });
163
- return;
164
- }
165
-
166
- // Dry-run: exit after preflight checks complete
167
- if (dryRun) {
168
- const preview = {
169
- dryRun: true,
170
- appId,
171
- url,
172
- paymentRequirements: {
173
- amountUsdt: requiredUsdt,
174
- amountWei: paymentReq.amountWei,
175
- asset: paymentReq.asset,
176
- payTo: paymentReq.payTo,
177
- orderNo: paymentReq.orderNo,
178
- },
179
- wallet: { address: sessionAddress },
180
- decision: { needTopup, needGas, topupAmount },
181
- will: [
182
- ...(needTopup ? ["fund_usdt_via_walletconnect"] : []),
183
- ...(needGas ? ["fund_bnb_via_walletconnect"] : []),
184
- "approve_or_skip",
185
- "sign_payment_eip712",
186
- "submit_to_facilitator",
187
- ...(poll ? ["poll_status"] : []),
188
- ],
189
- };
190
- emitOk("create-card", preview, { success: true, ...preview });
191
- return;
192
- }
193
-
194
- // WalletConnect top-up
195
- if (needTopup || needGas) {
196
- logInfo("Funding flow triggered...");
197
- try {
198
- await fundSessionKey({
199
- sessionAddress,
200
- usdtAmount: needTopup ? topupAmount : null,
201
- needGas,
202
- });
203
- } catch (e) {
204
- if (e instanceof WalletConnectError) {
205
- emitErr("create-card", e.code, { message: e.message, address: sessionAddress, appId });
206
- } else {
207
- emitErr("create-card", "FUNDING_FAILED", { message: e.message, address: sessionAddress, appId });
208
- }
209
- return;
210
- }
211
-
212
- logInfo("Re-checking wallet balance...");
213
- try {
214
- const { usdt, bnbRaw } = await getWalletBalance(privateKey);
215
- balanceBeforeChargeUsdt = usdt;
216
- const usdtNum = parseFloat(usdt);
217
- if (needGas && bnbRaw === 0n) {
218
- emitErr("create-card", "INSUFFICIENT_BNB", {
219
- message: "No BNB for approve transaction after funding. Run 'aigateway wallet-gas' to add BNB manually.",
220
- address: sessionAddress,
221
- appId,
222
- });
223
- return;
224
- }
225
- if (usdtNum < requiredUsdt) {
226
- emitErr("create-card", "INSUFFICIENT_USDT", {
227
- message: "Still insufficient USDT after funding.",
228
- required: `${requiredUsdt} USDT`,
229
- available: `${usdt} USDT`,
230
- address: sessionAddress,
231
- appId,
232
- });
233
- return;
234
- }
235
- } catch (e) {
236
- emitErr("create-card", "BALANCE_CHECK_FAILED", {
237
- message: `Balance re-check failed: ${e.message}`,
238
- appId,
239
- });
240
- return;
241
- }
242
- }
243
-
244
- const { client } = createX402Api(privateKey);
245
- logInfo(`Creating card: $${amount} USD via ${url}`);
246
-
247
- try {
248
- const { x402HTTPClient } = await import("@aeon-ai-pay/core/client");
249
- const httpClient = new x402HTTPClient(client);
250
-
251
- const raw402 = paymentReq.raw402Response;
252
- const getHeader = (name) => {
253
- const value = raw402.headers[name] ?? raw402.headers[name.toLowerCase()];
254
- return typeof value === "string" ? value : undefined;
255
- };
256
- const paymentRequired = httpClient.getPaymentRequiredResponse(getHeader, raw402.data);
257
- const paymentPayload = await client.createPaymentPayload(paymentRequired);
258
- const paymentHeaders = httpClient.encodePaymentSignatureHeader(paymentPayload);
259
-
260
- const response = await axios.get(url, {
261
- headers: { ...paymentHeaders, "Access-Control-Expose-Headers": "PAYMENT-RESPONSE" },
262
- });
263
- const paymentResponse = decodePaymentResponse(response.headers);
264
- const orderNo = paymentReq.orderNo || response.data?.model?.orderNo || response.data?.orderNo;
265
-
266
- let balanceAfterUsdt = null;
267
- try {
268
- const after = await getWalletBalance(privateKey);
269
- balanceAfterUsdt = after.usdt;
270
- } catch (e) {
271
- logInfo(`Post-payment balance check failed: ${e.message}`);
272
- }
273
-
274
- const sanitizedData = sanitizeOutput(response.data);
275
- const successData = {
276
- appId,
277
- orderNo,
278
- amount,
279
- data: sanitizedData,
280
- paymentResponse,
281
- balance: {
282
- initial: balanceInitialUsdt,
283
- before: balanceBeforeChargeUsdt,
284
- after: balanceAfterUsdt,
285
- charged: requiredUsdt,
286
- topup: topupAmount,
287
- },
288
- };
289
-
290
- function findCardStatus(obj) {
291
- if (!obj || typeof obj !== 'object') return null;
292
- if (obj.cardStatus) return obj.cardStatus;
293
- for (const v of Object.values(obj)) {
294
- const found = findCardStatus(v);
295
- if (found) return found;
296
- }
297
- return null;
298
- }
299
- const initialOrderStatus = response.data?.model?.orderStatus;
300
- const initialCardStatus = findCardStatus(response.data);
301
- const cardReady = initialOrderStatus === "SUCCESS" || initialOrderStatus === "FAIL" || initialCardStatus === "ACTIVE";
302
-
303
- if (cardReady) {
304
- logInfo(`Card ready (orderStatus=${initialOrderStatus}, cardStatus=${initialCardStatus}), no polling needed.`);
305
- emitOk("create-card", successData, { success: true, ...successData });
306
- return;
307
- }
308
-
309
- if (poll && orderNo) {
310
- logInfo(`\nPolling status for orderNo: ${orderNo}`);
311
- const pollResult = await pollStatus(serviceUrl, orderNo, appId);
312
- successData.pollResult = pollResult;
313
- emitOk("create-card", successData, { success: true, ...successData, pollResult });
314
- return;
315
- }
316
-
317
- if (poll && !orderNo) {
318
- logInfo("Warning: No orderNo available for polling. Query status manually.");
319
- }
320
- emitOk("create-card", successData, { success: true, ...successData });
321
- } catch (error) {
322
- emitErr("create-card", "PAYMENT_FAILED", {
323
- message: error.message,
324
- status: error.response?.status,
325
- data: error.response?.data,
326
- appId,
327
- });
328
- }
329
- }
330
-
331
- async function pollStatus(serviceUrl, orderNo, appId) {
332
- for (let i = 1; i <= MAX_POLLS; i++) {
333
- if (i > 1) {
334
- const delay = i <= 5 ? 2000 : POLL_INTERVAL;
335
- await new Promise((r) => setTimeout(r, delay));
336
- }
337
- try {
338
- const res = await axios.get(
339
- `${serviceUrl}/open/ai/x402/card/status?orderNo=${encodeURIComponent(orderNo)}&appId=${encodeURIComponent(appId)}`,
340
- );
341
- const model = res.data?.model;
342
- logInfo(`[${i}/${MAX_POLLS}] orderStatus=${model?.orderStatus} channelStatus=${model?.channelStatus}`);
343
- if (model?.orderStatus === "SUCCESS" || model?.orderStatus === "FAIL" || model?.cardStatus === "ACTIVE") {
344
- return sanitizeOutput(model);
345
- }
346
- } catch (e) {
347
- logInfo(`[${i}/${MAX_POLLS}] Poll error: ${e.message}`);
348
- }
349
- }
350
- logInfo(`Polling timeout after ${MAX_POLLS} attempts. Check manually with: aigateway create-card-status --order-no ${orderNo}`);
351
- return null;
352
- }