@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 +74 -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/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,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:
|
|
218
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
302
|
-
return publicPaymentResult(state,
|
|
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
|
|
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
|
-
|
|
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 (
|
|
383
|
+
if (status === 'completed') {
|
|
333
384
|
const { paymentNeededPath } = statePaths(resourceUrl, options);
|
|
334
385
|
await rm(paymentNeededPath, { force: true });
|
|
335
386
|
}
|
|
336
|
-
return publicPaymentResult(state,
|
|
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