@xiashe/sdk 0.1.15 → 0.1.17
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 +49 -6
- package/index.mjs +1 -1
- package/package.json +1 -1
package/alipayWallet.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { execFile } from 'node:child_process';
|
|
3
|
-
import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { chmod, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
|
|
@@ -10,6 +10,8 @@ const MAX_OUTPUT_BYTES = 32 * 1024;
|
|
|
10
10
|
const SENSITIVE_OUTPUT = /payment[-_ ]?proof|payment[-_ ]?needed|seller_signature|runtimeToken|authorization\s*:/i;
|
|
11
11
|
const SENSITIVE_USER_OUTPUT = /payment[-_ ]?proof|payment[-_ ]?needed|seller_signature|runtimeToken|authorization\s*:|trade[_ -]?no|交易(?:流水)?号|out[_ -]?shake|client[_ -]?session|payguard|validateclientparent|agentfamily/i;
|
|
12
12
|
const DEFAULT_STATE_DIR = path.join(os.homedir(), '.xiashe', 'alipay-402');
|
|
13
|
+
const OFFICIAL_MEDIA_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.webp']);
|
|
14
|
+
const MAX_OFFICIAL_MEDIA_BYTES = 8 * 1024 * 1024;
|
|
13
15
|
|
|
14
16
|
export class AlipayWalletError extends Error {
|
|
15
17
|
constructor(code, message, options = {}) {
|
|
@@ -176,7 +178,11 @@ function paymentStatusFromOutput(stdout, stderr) {
|
|
|
176
178
|
const output = `${stdout}\n${stderr}`;
|
|
177
179
|
const completed = /支付(?:已)?成功|交易成功|支付已完成|payment\s+(?:was\s+)?(?:successful|completed)|(?:resource|资源).*(?:obtained|success|成功)/i.test(output);
|
|
178
180
|
const pending = /支付待确认|等待.*确认|待确认|支付处理中|pending(?:\s+confirmation)?|waiting.*confirm/i.test(output);
|
|
179
|
-
|
|
181
|
+
// The official CLI sometimes returns a JSON error object with exit code 0.
|
|
182
|
+
// Treat an explicit unsuccessful result as failed so callers retain the
|
|
183
|
+
// normalized failure category instead of misleadingly reporting an unknown
|
|
184
|
+
// wallet state.
|
|
185
|
+
const failed = /支付失败|交易失败|payment\s+failed|"success"\s*:\s*false|"errorCode"\s*:/i.test(output);
|
|
180
186
|
return {
|
|
181
187
|
status: completed ? 'completed' : pending ? 'pending_confirmation' : failed ? 'failed' : 'unknown',
|
|
182
188
|
credential: queryCredentialFromOutput(output),
|
|
@@ -189,14 +195,31 @@ function userVisibleWalletOutput(stdout, stderr) {
|
|
|
189
195
|
.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, '')
|
|
190
196
|
.split(/\r?\n/)
|
|
191
197
|
.map((line) => line.trim())
|
|
192
|
-
.filter((line) => line && !SENSITIVE_USER_OUTPUT.test(line))
|
|
198
|
+
.filter((line) => line && !line.startsWith('MEDIA:') && !SENSITIVE_USER_OUTPUT.test(line))
|
|
193
199
|
.slice(0, 12);
|
|
194
200
|
return lines.length ? lines.join('\n').slice(0, 1_200) : null;
|
|
195
201
|
}
|
|
196
202
|
|
|
203
|
+
/** Preserve only an image emitted by the official wallet in this invocation. */
|
|
204
|
+
async function officialWalletMedia(stdout, stderr) {
|
|
205
|
+
const mediaLine = `${stdout}\n${stderr}`.split(/\r?\n/).find((line) => line.trim().startsWith('MEDIA:'));
|
|
206
|
+
if (!mediaLine) return null;
|
|
207
|
+
const candidate = mediaLine.slice(mediaLine.indexOf('MEDIA:') + 'MEDIA:'.length).trim();
|
|
208
|
+
if (!candidate || !path.isAbsolute(candidate) || !OFFICIAL_MEDIA_EXTENSIONS.has(path.extname(candidate).toLowerCase())) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const file = await stat(candidate);
|
|
213
|
+
return file.isFile() && file.size > 0 && file.size <= MAX_OFFICIAL_MEDIA_BYTES ? candidate : null;
|
|
214
|
+
} catch {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
197
219
|
function walletFailureCode(stdout, stderr, exitCode) {
|
|
198
220
|
const output = `${stdout}\n${stderr}`;
|
|
199
221
|
if (/TRADE_PROGRESS_FAILED/i.test(output)) return 'wallet_trade_progress_failed';
|
|
222
|
+
if (/payment[_ -]?needed[_ -]?rejected|账单.*(?:被拒绝|拒绝)|bill.*(?:rejected|denied)/i.test(output)) return 'payment_needed_rejected';
|
|
200
223
|
if (/余额不足|insufficient\s+balance/i.test(output)) return 'insufficient_balance';
|
|
201
224
|
if (/已过期|过期|expired/i.test(output)) return 'payment_expired';
|
|
202
225
|
if (/钱包.*(?:未开启|未授权)|wallet.*(?:not\s+enabled|not\s+authorized)/i.test(output)) return 'wallet_not_ready';
|
|
@@ -216,11 +239,14 @@ function paymentMessage(status) {
|
|
|
216
239
|
|
|
217
240
|
function paymentFailureMessage(failureCode) {
|
|
218
241
|
if (failureCode === 'wallet_trade_progress_failed') {
|
|
219
|
-
return '支付宝钱包未能创建可确认的交易;未授予付费 Skill
|
|
242
|
+
return '支付宝钱包未能创建可确认的交易;未授予付费 Skill 权益。官方钱包没有给出可归因的原因,不能据此判断为 Agent 不受支持、账单签名错误或已扣款;请勿重复付款。';
|
|
220
243
|
}
|
|
221
244
|
if (failureCode === 'wallet_runtime_not_supported') {
|
|
222
245
|
return '当前 Agent 运行环境不支持支付宝 AI付;未授予付费 Skill 权益。';
|
|
223
246
|
}
|
|
247
|
+
if (failureCode === 'payment_needed_rejected') {
|
|
248
|
+
return '支付宝端拒绝了本次付款账单,未创建交易、未扣款。请确认该 APPID 已在支付宝完成 AI按量付费的服务注册、密钥配置、技术集成确认与线上验证前置步骤;若这些均已完成,请提交支付宝问题反馈并附上本次发生时间。';
|
|
249
|
+
}
|
|
224
250
|
return '支付宝官方钱包没有生成可确认的付款状态;未授予付费 Skill 权益。';
|
|
225
251
|
}
|
|
226
252
|
|
|
@@ -268,6 +294,10 @@ function publicPaymentResult(state, status, diagnostic, options = {}) {
|
|
|
268
294
|
message: failed ? paymentFailureMessage(failureCode) : paymentMessage(status),
|
|
269
295
|
diagnostic,
|
|
270
296
|
...(options.userVisible ? { userVisible: options.userVisible } : {}),
|
|
297
|
+
...(options.officialMedia ? { officialMedia: options.officialMedia } : {}),
|
|
298
|
+
...(status === 'pending_confirmation'
|
|
299
|
+
? { confirmation: { source: 'official_alipay_wallet', surface: 'app_or_official_media', required: true } }
|
|
300
|
+
: { confirmation: { source: 'official_alipay_wallet', required: false } }),
|
|
271
301
|
...(commandFailed ? { failureCode } : {})
|
|
272
302
|
};
|
|
273
303
|
}
|
|
@@ -341,6 +371,10 @@ export async function payAlipay402Resource(input, options = {}) {
|
|
|
341
371
|
|
|
342
372
|
const walletInvocation = await runOfficialCli([
|
|
343
373
|
'402-buyer-pay',
|
|
374
|
+
// The official buyer CLI accepts this long form of -s/-f/-r. The file
|
|
375
|
+
// name is intentionally private and random; the exact Payment-Needed
|
|
376
|
+
// content, original resource URL, and generated UUID session are what
|
|
377
|
+
// define the payment request.
|
|
344
378
|
'--session-id', state.sessionId,
|
|
345
379
|
'--file', state.paymentNeededPath,
|
|
346
380
|
'--resource-url', resourceUrl,
|
|
@@ -349,6 +383,9 @@ export async function payAlipay402Resource(input, options = {}) {
|
|
|
349
383
|
const result = paymentStatusFromOutput(walletInvocation.stdout, walletInvocation.stderr);
|
|
350
384
|
const commandFailed = walletInvocation.exitCode !== 0 && result.status === 'unknown';
|
|
351
385
|
const status = commandFailed ? 'wallet_command_failed' : result.status;
|
|
386
|
+
const officialMedia = ['pending_confirmation', 'completed'].includes(status)
|
|
387
|
+
? await officialWalletMedia(walletInvocation.stdout, walletInvocation.stderr)
|
|
388
|
+
: null;
|
|
352
389
|
state.status = status;
|
|
353
390
|
state.queryCredential = result.credential;
|
|
354
391
|
state.updatedAt = new Date().toISOString();
|
|
@@ -360,7 +397,8 @@ export async function payAlipay402Resource(input, options = {}) {
|
|
|
360
397
|
failureCode: failed ? walletFailureCode(walletInvocation.stdout, walletInvocation.stderr, walletInvocation.exitCode) : undefined,
|
|
361
398
|
userVisible: ['pending_confirmation', 'completed'].includes(status)
|
|
362
399
|
? userVisibleWalletOutput(walletInvocation.stdout, walletInvocation.stderr)
|
|
363
|
-
: null
|
|
400
|
+
: null,
|
|
401
|
+
officialMedia
|
|
364
402
|
});
|
|
365
403
|
}
|
|
366
404
|
|
|
@@ -374,6 +412,7 @@ export async function queryAlipay402Resource(input, options = {}) {
|
|
|
374
412
|
status: 'no_active_payment',
|
|
375
413
|
provider: 'alipay-agent-payment',
|
|
376
414
|
walletInvoked: false,
|
|
415
|
+
confirmation: { source: 'official_alipay_wallet', required: false },
|
|
377
416
|
message: '没有可查询的本地支付宝付款记录;请重新发起付款,不要把付款凭证粘贴到对话中。',
|
|
378
417
|
diagnostic: 'payment_state_not_found'
|
|
379
418
|
};
|
|
@@ -389,6 +428,9 @@ export async function queryAlipay402Resource(input, options = {}) {
|
|
|
389
428
|
const result = paymentStatusFromOutput(walletInvocation.stdout, walletInvocation.stderr);
|
|
390
429
|
const commandFailed = walletInvocation.exitCode !== 0 && result.status === 'unknown';
|
|
391
430
|
const status = commandFailed ? 'wallet_command_failed' : result.status;
|
|
431
|
+
const officialMedia = ['pending_confirmation', 'completed'].includes(status)
|
|
432
|
+
? await officialWalletMedia(walletInvocation.stdout, walletInvocation.stderr)
|
|
433
|
+
: null;
|
|
392
434
|
state.status = status;
|
|
393
435
|
state.queryCredential = result.credential || state.queryCredential;
|
|
394
436
|
state.updatedAt = new Date().toISOString();
|
|
@@ -403,6 +445,7 @@ export async function queryAlipay402Resource(input, options = {}) {
|
|
|
403
445
|
failureCode: failed ? walletFailureCode(walletInvocation.stdout, walletInvocation.stderr, walletInvocation.exitCode) : undefined,
|
|
404
446
|
userVisible: ['pending_confirmation', 'completed'].includes(status)
|
|
405
447
|
? userVisibleWalletOutput(walletInvocation.stdout, walletInvocation.stderr)
|
|
406
|
-
: null
|
|
448
|
+
: null,
|
|
449
|
+
officialMedia
|
|
407
450
|
});
|
|
408
451
|
}
|
package/index.mjs
CHANGED