@xiashe/sdk 0.1.12 → 0.1.13

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
@@ -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/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}.payment-needed`),
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 { stdout: String(result?.stdout || ''), stderr: String(result?.stderr || '') };
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,27 @@ 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 (/余额不足|insufficient\s+balance/i.test(output)) return 'insufficient_balance';
200
+ if (/已过期|过期|expired/i.test(output)) return 'payment_expired';
201
+ if (/钱包.*(?:未开启|未授权)|wallet.*(?:not\s+enabled|not\s+authorized)/i.test(output)) return 'wallet_not_ready';
202
+ if (/买家.*(?:不匹配|校验失败)|buyer.*(?:mismatch|identity)/i.test(output)) return 'buyer_identity_rejected';
203
+ if (/账单.*(?:无效|签名)|签名.*(?:无效|失败)|bill.*(?:invalid|signature)/i.test(output)) return 'payment_needed_rejected';
204
+ if (/network|网络|timeout|超时/i.test(output)) return 'wallet_network_error';
205
+ return Number.isInteger(exitCode) && exitCode !== 0 ? 'official_cli_nonzero_exit' : 'official_cli_unclassified';
206
+ }
207
+
172
208
  function paymentMessage(status) {
173
209
  if (status === 'completed') return '支付宝钱包显示本次付款已完成;正在由服务端确认权益。';
174
210
  if (status === 'pending_confirmation') return '支付宝已发起本次付款确认;请打开支付宝 App 完成确认,然后运行 status。';
@@ -207,15 +243,20 @@ async function fetchPaymentNeeded(resourceUrl, options = {}) {
207
243
  }
208
244
  }
209
245
 
210
- function publicPaymentResult(state, status, diagnostic) {
246
+ function publicPaymentResult(state, status, diagnostic, options = {}) {
247
+ const commandFailed = options.commandFailed === true;
211
248
  return {
212
- ok: status !== 'failed',
249
+ ok: status !== 'failed' && status !== 'wallet_command_failed',
213
250
  status,
214
251
  provider: 'alipay-agent-payment',
215
252
  attemptId: `alipay-402:${state.resourceDigest.slice(0, 16)}`,
216
253
  walletInvoked: true,
217
- message: paymentMessage(status),
218
- diagnostic
254
+ message: status === 'wallet_command_failed'
255
+ ? '支付宝官方钱包没有生成可确认的付款状态;未授予付费 Skill 权益。'
256
+ : paymentMessage(status),
257
+ diagnostic,
258
+ ...(options.userVisible ? { userVisible: options.userVisible } : {}),
259
+ ...(commandFailed ? { failureCode: options.failureCode || 'official_cli_nonzero_exit' } : {})
219
260
  };
220
261
  }
221
262
 
@@ -286,20 +327,28 @@ export async function payAlipay402Resource(input, options = {}) {
286
327
  };
287
328
  await writePaymentState(state, options);
288
329
 
289
- const { stdout, stderr } = await runOfficialCli([
330
+ const walletInvocation = await runOfficialCli([
290
331
  '402-buyer-pay',
291
332
  '--session-id', state.sessionId,
292
333
  '--file', state.paymentNeededPath,
293
334
  '--resource-url', resourceUrl,
294
335
  '--intent-summary', intentSummary
295
336
  ], options);
296
- const result = paymentStatusFromOutput(stdout, stderr);
297
- state.status = result.status;
337
+ const result = paymentStatusFromOutput(walletInvocation.stdout, walletInvocation.stderr);
338
+ const commandFailed = walletInvocation.exitCode !== 0 && result.status === 'unknown';
339
+ const status = commandFailed ? 'wallet_command_failed' : result.status;
340
+ state.status = status;
298
341
  state.queryCredential = result.credential;
299
342
  state.updatedAt = new Date().toISOString();
300
343
  await writePaymentState(state, options);
301
- if (result.status === 'completed') await rm(paths.paymentNeededPath, { force: true });
302
- return publicPaymentResult(state, result.status, result.diagnostic);
344
+ if (status === 'completed') await rm(paths.paymentNeededPath, { force: true });
345
+ return publicPaymentResult(state, status, result.diagnostic, {
346
+ commandFailed,
347
+ failureCode: commandFailed ? walletFailureCode(walletInvocation.stdout, walletInvocation.stderr, walletInvocation.exitCode) : undefined,
348
+ userVisible: ['pending_confirmation', 'completed', 'failed'].includes(status)
349
+ ? userVisibleWalletOutput(walletInvocation.stdout, walletInvocation.stderr)
350
+ : null
351
+ });
303
352
  }
304
353
 
305
354
  /** Queries an existing local payment attempt using the official query credential. */
@@ -319,19 +368,27 @@ export async function queryAlipay402Resource(input, options = {}) {
319
368
  const queryArg = state.queryCredential.type === 'out_shake_no'
320
369
  ? ['--out-shake-no', state.queryCredential.value]
321
370
  : ['-t', state.queryCredential.value];
322
- const { stdout, stderr } = await runOfficialCli([
371
+ const walletInvocation = await runOfficialCli([
323
372
  '402-query-payment-status',
324
373
  ...queryArg,
325
374
  '--resource-url', resourceUrl
326
375
  ], options);
327
- const result = paymentStatusFromOutput(stdout, stderr);
328
- state.status = result.status;
376
+ const result = paymentStatusFromOutput(walletInvocation.stdout, walletInvocation.stderr);
377
+ const commandFailed = walletInvocation.exitCode !== 0 && result.status === 'unknown';
378
+ const status = commandFailed ? 'wallet_command_failed' : result.status;
379
+ state.status = status;
329
380
  state.queryCredential = result.credential || state.queryCredential;
330
381
  state.updatedAt = new Date().toISOString();
331
382
  await writePaymentState(state, options);
332
- if (result.status === 'completed') {
383
+ if (status === 'completed') {
333
384
  const { paymentNeededPath } = statePaths(resourceUrl, options);
334
385
  await rm(paymentNeededPath, { force: true });
335
386
  }
336
- return publicPaymentResult(state, result.status, result.diagnostic);
387
+ return publicPaymentResult(state, status, result.diagnostic, {
388
+ commandFailed,
389
+ failureCode: commandFailed ? walletFailureCode(walletInvocation.stdout, walletInvocation.stderr, walletInvocation.exitCode) : undefined,
390
+ userVisible: ['pending_confirmation', 'completed', 'failed'].includes(status)
391
+ ? userVisibleWalletOutput(walletInvocation.stdout, walletInvocation.stderr)
392
+ : null
393
+ });
337
394
  }
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.12';
9
+ export const VERSION = '0.1.13';
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.12",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "main": "index.mjs",
6
6
  "exports": {