@xiashe/sdk 0.1.12 → 0.1.15
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/alipayWallet.mjs +88 -17
- package/index.mjs +1 -1
- package/package.json +1 -1
package/alipayWallet.mjs
CHANGED
|
@@ -8,6 +8,7 @@ const DEFAULT_TIMEOUT_MS = 8 * 60 * 1000;
|
|
|
8
8
|
const RESOURCE_FETCH_TIMEOUT_MS = 20 * 1000;
|
|
9
9
|
const MAX_OUTPUT_BYTES = 32 * 1024;
|
|
10
10
|
const SENSITIVE_OUTPUT = /payment[-_ ]?proof|payment[-_ ]?needed|seller_signature|runtimeToken|authorization\s*:/i;
|
|
11
|
+
const SENSITIVE_USER_OUTPUT = /payment[-_ ]?proof|payment[-_ ]?needed|seller_signature|runtimeToken|authorization\s*:|trade[_ -]?no|交易(?:流水)?号|out[_ -]?shake|client[_ -]?session|payguard|validateclientparent|agentfamily/i;
|
|
11
12
|
const DEFAULT_STATE_DIR = path.join(os.homedir(), '.xiashe', 'alipay-402');
|
|
12
13
|
|
|
13
14
|
export class AlipayWalletError extends Error {
|
|
@@ -65,7 +66,7 @@ function statePaths(resourceUrl, options = {}) {
|
|
|
65
66
|
const directory = stateDirectory(options);
|
|
66
67
|
return {
|
|
67
68
|
directory,
|
|
68
|
-
paymentNeededPath: path.join(directory, `${base}.
|
|
69
|
+
paymentNeededPath: path.join(directory, `${base}.txt`),
|
|
69
70
|
statePath: path.join(directory, `${base}.json`)
|
|
70
71
|
};
|
|
71
72
|
}
|
|
@@ -118,8 +119,12 @@ function defaultRunFile(command, args, options) {
|
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
function normalizedRunnerResult(result) {
|
|
121
|
-
if (typeof result === 'string') return { stdout: result, stderr: '' };
|
|
122
|
-
return {
|
|
122
|
+
if (typeof result === 'string') return { stdout: result, stderr: '', exitCode: 0 };
|
|
123
|
+
return {
|
|
124
|
+
stdout: String(result?.stdout || ''),
|
|
125
|
+
stderr: String(result?.stderr || ''),
|
|
126
|
+
exitCode: Number.isInteger(result?.exitCode) ? result.exitCode : 0
|
|
127
|
+
};
|
|
123
128
|
}
|
|
124
129
|
|
|
125
130
|
async function runOfficialCli(args, options = {}) {
|
|
@@ -140,6 +145,16 @@ async function runOfficialCli(args, options = {}) {
|
|
|
140
145
|
if (error?.killed || error?.signal === 'SIGTERM') {
|
|
141
146
|
throw new AlipayWalletError('ALIPAY_WALLET_TIMEOUT', '支付宝钱包等待确认超时;请用同一支付命令的 status 查询状态。');
|
|
142
147
|
}
|
|
148
|
+
// The official buyer CLI can use a non-zero process exit for a user-action
|
|
149
|
+
// state such as “payment pending”. Preserve this invocation's output for
|
|
150
|
+
// the caller to parse before classifying it as a real command failure.
|
|
151
|
+
if (Number.isInteger(error?.code)) {
|
|
152
|
+
return {
|
|
153
|
+
stdout: String(error.stdout || ''),
|
|
154
|
+
stderr: String(error.stderr || ''),
|
|
155
|
+
exitCode: error.code
|
|
156
|
+
};
|
|
157
|
+
}
|
|
143
158
|
throw new AlipayWalletError('ALIPAY_WALLET_COMMAND_FAILED', '支付宝官方钱包未完成该操作。');
|
|
144
159
|
}
|
|
145
160
|
}
|
|
@@ -169,6 +184,29 @@ function paymentStatusFromOutput(stdout, stderr) {
|
|
|
169
184
|
};
|
|
170
185
|
}
|
|
171
186
|
|
|
187
|
+
function userVisibleWalletOutput(stdout, stderr) {
|
|
188
|
+
const lines = `${stdout}\n${stderr}`
|
|
189
|
+
.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, '')
|
|
190
|
+
.split(/\r?\n/)
|
|
191
|
+
.map((line) => line.trim())
|
|
192
|
+
.filter((line) => line && !SENSITIVE_USER_OUTPUT.test(line))
|
|
193
|
+
.slice(0, 12);
|
|
194
|
+
return lines.length ? lines.join('\n').slice(0, 1_200) : null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function walletFailureCode(stdout, stderr, exitCode) {
|
|
198
|
+
const output = `${stdout}\n${stderr}`;
|
|
199
|
+
if (/TRADE_PROGRESS_FAILED/i.test(output)) return 'wallet_trade_progress_failed';
|
|
200
|
+
if (/余额不足|insufficient\s+balance/i.test(output)) return 'insufficient_balance';
|
|
201
|
+
if (/已过期|过期|expired/i.test(output)) return 'payment_expired';
|
|
202
|
+
if (/钱包.*(?:未开启|未授权)|wallet.*(?:not\s+enabled|not\s+authorized)/i.test(output)) return 'wallet_not_ready';
|
|
203
|
+
if (/买家.*(?:不匹配|校验失败)|buyer.*(?:mismatch|identity)/i.test(output)) return 'buyer_identity_rejected';
|
|
204
|
+
if (/payguard|validateclientparent|agentfamily=unknown|agent\s*family.*unknown/i.test(output)) return 'wallet_runtime_not_supported';
|
|
205
|
+
if (/账单.*(?:无效|签名)|签名.*(?:无效|失败)|bill.*(?:invalid|signature)/i.test(output)) return 'payment_needed_rejected';
|
|
206
|
+
if (/network|网络|timeout|超时/i.test(output)) return 'wallet_network_error';
|
|
207
|
+
return Number.isInteger(exitCode) && exitCode !== 0 ? 'official_cli_nonzero_exit' : 'official_cli_unclassified';
|
|
208
|
+
}
|
|
209
|
+
|
|
172
210
|
function paymentMessage(status) {
|
|
173
211
|
if (status === 'completed') return '支付宝钱包显示本次付款已完成;正在由服务端确认权益。';
|
|
174
212
|
if (status === 'pending_confirmation') return '支付宝已发起本次付款确认;请打开支付宝 App 完成确认,然后运行 status。';
|
|
@@ -176,6 +214,16 @@ function paymentMessage(status) {
|
|
|
176
214
|
return '支付宝官方钱包已被调用,但尚未返回可确认的付款状态;请运行 status 查询,或重新发起付款。';
|
|
177
215
|
}
|
|
178
216
|
|
|
217
|
+
function paymentFailureMessage(failureCode) {
|
|
218
|
+
if (failureCode === 'wallet_trade_progress_failed') {
|
|
219
|
+
return '支付宝钱包未能创建可确认的交易;未授予付费 Skill 权益。请在重新加载后的受支持 Agent 环境中重试。';
|
|
220
|
+
}
|
|
221
|
+
if (failureCode === 'wallet_runtime_not_supported') {
|
|
222
|
+
return '当前 Agent 运行环境不支持支付宝 AI付;未授予付费 Skill 权益。';
|
|
223
|
+
}
|
|
224
|
+
return '支付宝官方钱包没有生成可确认的付款状态;未授予付费 Skill 权益。';
|
|
225
|
+
}
|
|
226
|
+
|
|
179
227
|
async function fetchPaymentNeeded(resourceUrl, options = {}) {
|
|
180
228
|
const fetchFn = options.fetch || globalThis.fetch;
|
|
181
229
|
const controller = new AbortController();
|
|
@@ -207,15 +255,20 @@ async function fetchPaymentNeeded(resourceUrl, options = {}) {
|
|
|
207
255
|
}
|
|
208
256
|
}
|
|
209
257
|
|
|
210
|
-
function publicPaymentResult(state, status, diagnostic) {
|
|
258
|
+
function publicPaymentResult(state, status, diagnostic, options = {}) {
|
|
259
|
+
const commandFailed = options.commandFailed === true;
|
|
260
|
+
const failureCode = options.failureCode || 'official_cli_nonzero_exit';
|
|
261
|
+
const failed = status === 'failed' || status === 'wallet_command_failed';
|
|
211
262
|
return {
|
|
212
|
-
ok:
|
|
263
|
+
ok: !failed,
|
|
213
264
|
status,
|
|
214
265
|
provider: 'alipay-agent-payment',
|
|
215
266
|
attemptId: `alipay-402:${state.resourceDigest.slice(0, 16)}`,
|
|
216
267
|
walletInvoked: true,
|
|
217
|
-
message: paymentMessage(status),
|
|
218
|
-
diagnostic
|
|
268
|
+
message: failed ? paymentFailureMessage(failureCode) : paymentMessage(status),
|
|
269
|
+
diagnostic,
|
|
270
|
+
...(options.userVisible ? { userVisible: options.userVisible } : {}),
|
|
271
|
+
...(commandFailed ? { failureCode } : {})
|
|
219
272
|
};
|
|
220
273
|
}
|
|
221
274
|
|
|
@@ -286,20 +339,29 @@ export async function payAlipay402Resource(input, options = {}) {
|
|
|
286
339
|
};
|
|
287
340
|
await writePaymentState(state, options);
|
|
288
341
|
|
|
289
|
-
const
|
|
342
|
+
const walletInvocation = await runOfficialCli([
|
|
290
343
|
'402-buyer-pay',
|
|
291
344
|
'--session-id', state.sessionId,
|
|
292
345
|
'--file', state.paymentNeededPath,
|
|
293
346
|
'--resource-url', resourceUrl,
|
|
294
347
|
'--intent-summary', intentSummary
|
|
295
348
|
], options);
|
|
296
|
-
const result = paymentStatusFromOutput(stdout, stderr);
|
|
297
|
-
|
|
349
|
+
const result = paymentStatusFromOutput(walletInvocation.stdout, walletInvocation.stderr);
|
|
350
|
+
const commandFailed = walletInvocation.exitCode !== 0 && result.status === 'unknown';
|
|
351
|
+
const status = commandFailed ? 'wallet_command_failed' : result.status;
|
|
352
|
+
state.status = status;
|
|
298
353
|
state.queryCredential = result.credential;
|
|
299
354
|
state.updatedAt = new Date().toISOString();
|
|
300
355
|
await writePaymentState(state, options);
|
|
301
|
-
if (
|
|
302
|
-
|
|
356
|
+
if (status === 'completed') await rm(paths.paymentNeededPath, { force: true });
|
|
357
|
+
const failed = commandFailed || status === 'failed';
|
|
358
|
+
return publicPaymentResult(state, status, result.diagnostic, {
|
|
359
|
+
commandFailed: failed,
|
|
360
|
+
failureCode: failed ? walletFailureCode(walletInvocation.stdout, walletInvocation.stderr, walletInvocation.exitCode) : undefined,
|
|
361
|
+
userVisible: ['pending_confirmation', 'completed'].includes(status)
|
|
362
|
+
? userVisibleWalletOutput(walletInvocation.stdout, walletInvocation.stderr)
|
|
363
|
+
: null
|
|
364
|
+
});
|
|
303
365
|
}
|
|
304
366
|
|
|
305
367
|
/** Queries an existing local payment attempt using the official query credential. */
|
|
@@ -319,19 +381,28 @@ export async function queryAlipay402Resource(input, options = {}) {
|
|
|
319
381
|
const queryArg = state.queryCredential.type === 'out_shake_no'
|
|
320
382
|
? ['--out-shake-no', state.queryCredential.value]
|
|
321
383
|
: ['-t', state.queryCredential.value];
|
|
322
|
-
const
|
|
384
|
+
const walletInvocation = await runOfficialCli([
|
|
323
385
|
'402-query-payment-status',
|
|
324
386
|
...queryArg,
|
|
325
387
|
'--resource-url', resourceUrl
|
|
326
388
|
], options);
|
|
327
|
-
const result = paymentStatusFromOutput(stdout, stderr);
|
|
328
|
-
|
|
389
|
+
const result = paymentStatusFromOutput(walletInvocation.stdout, walletInvocation.stderr);
|
|
390
|
+
const commandFailed = walletInvocation.exitCode !== 0 && result.status === 'unknown';
|
|
391
|
+
const status = commandFailed ? 'wallet_command_failed' : result.status;
|
|
392
|
+
state.status = status;
|
|
329
393
|
state.queryCredential = result.credential || state.queryCredential;
|
|
330
394
|
state.updatedAt = new Date().toISOString();
|
|
331
395
|
await writePaymentState(state, options);
|
|
332
|
-
if (
|
|
396
|
+
if (status === 'completed') {
|
|
333
397
|
const { paymentNeededPath } = statePaths(resourceUrl, options);
|
|
334
398
|
await rm(paymentNeededPath, { force: true });
|
|
335
399
|
}
|
|
336
|
-
|
|
400
|
+
const failed = commandFailed || status === 'failed';
|
|
401
|
+
return publicPaymentResult(state, status, result.diagnostic, {
|
|
402
|
+
commandFailed: failed,
|
|
403
|
+
failureCode: failed ? walletFailureCode(walletInvocation.stdout, walletInvocation.stderr, walletInvocation.exitCode) : undefined,
|
|
404
|
+
userVisible: ['pending_confirmation', 'completed'].includes(status)
|
|
405
|
+
? userVisibleWalletOutput(walletInvocation.stdout, walletInvocation.stderr)
|
|
406
|
+
: null
|
|
407
|
+
});
|
|
337
408
|
}
|
package/index.mjs
CHANGED