@xiashe/sdk 0.1.16 → 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 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
- const failed = /支付失败|交易失败|payment\s+failed/i.test(output);
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';
@@ -221,6 +244,9 @@ function paymentFailureMessage(failureCode) {
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
@@ -6,7 +6,7 @@ import path from 'node:path';
6
6
  import { ConvexHttpClient } from 'convex/browser';
7
7
  import { anyApi as api } from 'convex/server';
8
8
 
9
- export const VERSION = '0.1.16';
9
+ export const VERSION = '0.1.17';
10
10
 
11
11
  export const DEFAULTS = {
12
12
  cn: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiashe/sdk",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "type": "module",
5
5
  "main": "index.mjs",
6
6
  "exports": {