@xiashe/sdk 0.1.30 → 0.1.31
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 +59 -19
- package/index.mjs +1 -1
- package/package.json +1 -1
package/alipayWallet.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
2
|
import { execFile } from 'node:child_process';
|
|
3
3
|
import { chmod, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
4
4
|
import os from 'node:os';
|
|
@@ -8,11 +8,18 @@ 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|交易(?:流水)
|
|
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
13
|
const OFFICIAL_MEDIA_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.webp']);
|
|
14
14
|
const MAX_OFFICIAL_MEDIA_BYTES = 8 * 1024 * 1024;
|
|
15
|
-
|
|
15
|
+
// The buyer CLI documents an AI Pay query number as 32 digits with `8282`
|
|
16
|
+
// in positions 11–14. Merchant out_trade_no values must never be accepted
|
|
17
|
+
// here: they cannot query a buyer-side 402 transaction.
|
|
18
|
+
const OUT_SHAKE_NO_PATTERN = /^\d{10}8282\d{18}$/;
|
|
19
|
+
const WALLET_SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,191}$/;
|
|
20
|
+
const OFFICIAL_WALLET_COMMAND = 'alipay-bot';
|
|
21
|
+
const REQUIRED_BUYER_PAY_OPTIONS = ['402-buyer-pay', '--session-id', '--file', '--resource-url', '--intent-summary'];
|
|
22
|
+
const REQUIRED_STATUS_OPTIONS = ['402-query-payment-status', '--out-shake-no', '--trade-no', '--resource-url'];
|
|
16
23
|
const SAFE_OFFICIAL_FAILURE_CODES = new Set([
|
|
17
24
|
'TRADE_PROGRESS_FAILED',
|
|
18
25
|
'PAYMENT_NEEDED_REJECTED',
|
|
@@ -226,6 +233,22 @@ function normalizeIntentSummary(value) {
|
|
|
226
233
|
return summary;
|
|
227
234
|
}
|
|
228
235
|
|
|
236
|
+
/**
|
|
237
|
+
* The official buyer CLI binds its payment intent to the active Agent
|
|
238
|
+
* conversation. A generated UUID is not a valid substitute: it prevents the
|
|
239
|
+
* wallet from correlating a later confirmation/status query with this task.
|
|
240
|
+
*/
|
|
241
|
+
function normalizeWalletSessionId(value) {
|
|
242
|
+
const sessionId = String(value || '').trim();
|
|
243
|
+
if (!sessionId) {
|
|
244
|
+
throw new AlipayWalletError('ALIPAY_SESSION_ID_REQUIRED', '付款桥接缺少当前 Agent 的业务会话 ID;未请求资源或调用钱包。');
|
|
245
|
+
}
|
|
246
|
+
if (!WALLET_SESSION_ID_PATTERN.test(sessionId)) {
|
|
247
|
+
throw new AlipayWalletError('ALIPAY_SESSION_ID_INVALID', '付款桥接的业务会话 ID 格式无效;未请求资源或调用钱包。');
|
|
248
|
+
}
|
|
249
|
+
return sessionId;
|
|
250
|
+
}
|
|
251
|
+
|
|
229
252
|
function stateDirectory(options = {}) {
|
|
230
253
|
return path.resolve(options.stateDir || process.env.XIASHE_ALIPAY_PAYMENT_STATE_DIR || DEFAULT_STATE_DIR);
|
|
231
254
|
}
|
|
@@ -297,7 +320,7 @@ function normalizedRunnerResult(result) {
|
|
|
297
320
|
}
|
|
298
321
|
|
|
299
322
|
async function runOfficialCli(args, options = {}) {
|
|
300
|
-
const command = safeText(options.command || process.env.XIASHE_ALIPAY_BOT_COMMAND ||
|
|
323
|
+
const command = safeText(options.command || process.env.XIASHE_ALIPAY_BOT_COMMAND || OFFICIAL_WALLET_COMMAND, 200);
|
|
301
324
|
if (!command || /\s/.test(command)) {
|
|
302
325
|
throw new AlipayWalletError('ALIPAY_BOT_COMMAND_INVALID', '支付宝钱包命令配置无效。');
|
|
303
326
|
}
|
|
@@ -336,12 +359,17 @@ function walletStateFromOutput(stdout, stderr) {
|
|
|
336
359
|
}
|
|
337
360
|
|
|
338
361
|
function queryCredentialFromOutput(output) {
|
|
339
|
-
//
|
|
340
|
-
//
|
|
362
|
+
// Current official CLI output labels the preferred status credential as
|
|
363
|
+
// “订单号” or “查询单号”; it is not the merchant's out_trade_no. Older
|
|
364
|
+
// wallets used camelCase/snake_case fields, which remain supported here.
|
|
365
|
+
// Match only the documented, fixed AI Pay shape and keep it in private
|
|
366
|
+
// state—never return it in bridge output.
|
|
367
|
+
const labeledOutShakeNo = output.match(/(?:订单号|查询单号)\s*(?:["::=]\s*)?["']?(\d{32})/u)?.[1] || null;
|
|
341
368
|
const tradeNo = output.match(/(?:["']?trade[\s_-]*no["']?|交易(?:流水)?号)\s*["::=]\s*["']?([A-Za-z0-9._-]{8,128})/i)?.[1] || null;
|
|
342
|
-
const
|
|
369
|
+
const structuredOutShakeNo = output.match(/(?:["']?out[\s_-]*shake[\s_-]*no["']?|外部(?:摇一摇)?编号)\s*["::=]\s*["']?(\d{32})/i)?.[1] || null;
|
|
370
|
+
const outShakeNo = labeledOutShakeNo || structuredOutShakeNo;
|
|
343
371
|
// An outShakeNo is the preferred 402 status-query credential, but only its
|
|
344
|
-
// documented 32-
|
|
372
|
+
// documented 32-digit AI Pay form may select that query path.
|
|
345
373
|
const validOutShakeNo = outShakeNo && OUT_SHAKE_NO_PATTERN.test(outShakeNo);
|
|
346
374
|
return validOutShakeNo
|
|
347
375
|
? { type: 'out_shake_no', value: outShakeNo }
|
|
@@ -732,17 +760,28 @@ export async function checkAlipayWallet(options = {}) {
|
|
|
732
760
|
|
|
733
761
|
/** Checks that the official buyer CLI is present, without querying wallet state. */
|
|
734
762
|
export async function getAlipayWalletBridgeInfo(options = {}) {
|
|
735
|
-
const
|
|
736
|
-
const
|
|
763
|
+
const version = await runOfficialCli(['--version'], options);
|
|
764
|
+
const buyerPayHelp = await runOfficialCli(['402-buyer-pay', '--help'], options);
|
|
765
|
+
const statusHelp = await runOfficialCli(['402-query-payment-status', '--help'], options);
|
|
766
|
+
const runtimeVersion = safeRuntimeVersion(version.stdout, version.stderr);
|
|
767
|
+
const buyerPayOutput = `${buyerPayHelp.stdout}\n${buyerPayHelp.stderr}`;
|
|
768
|
+
const statusOutput = `${statusHelp.stdout}\n${statusHelp.stderr}`;
|
|
769
|
+
const buyerPaySupported = REQUIRED_BUYER_PAY_OPTIONS.every((option) => buyerPayOutput.includes(option));
|
|
770
|
+
const statusSupported = REQUIRED_STATUS_OPTIONS.every((option) => statusOutput.includes(option));
|
|
771
|
+
const contractValid = buyerPaySupported && statusSupported;
|
|
737
772
|
return {
|
|
738
|
-
ok:
|
|
739
|
-
state: 'runtime_available',
|
|
773
|
+
ok: contractValid,
|
|
774
|
+
state: contractValid ? 'runtime_available' : 'runtime_incompatible',
|
|
740
775
|
provider: 'alipay-agent-payment',
|
|
741
|
-
message:
|
|
776
|
+
message: contractValid
|
|
777
|
+
? '已找到支付宝官方钱包程序,并确认当前买方支付/查询命令参数可用;尚未检查钱包授权或交易资格。'
|
|
778
|
+
: '支付宝官方钱包程序缺少当前 402 买方支付或查询命令参数;未创建交易。请用官方安装器更新钱包运行时后重试。',
|
|
742
779
|
walletStateChecked: false,
|
|
743
780
|
transactionCreationVerified: false,
|
|
781
|
+
paymentCommandsValidated: contractValid,
|
|
744
782
|
...(runtimeVersion ? { runtimeVersion } : {}),
|
|
745
|
-
|
|
783
|
+
...(contractValid ? {} : { failureCode: 'wallet_runtime_contract_invalid' }),
|
|
784
|
+
diagnostic: safeDiagnostic(`${version.stdout}\n${version.stderr}\n${buyerPayHelp.stdout}\n${buyerPayHelp.stderr}\n${statusHelp.stdout}\n${statusHelp.stderr}`)
|
|
746
785
|
};
|
|
747
786
|
}
|
|
748
787
|
|
|
@@ -758,6 +797,7 @@ export async function payAlipay402Resource(input, options = {}) {
|
|
|
758
797
|
}
|
|
759
798
|
const resourceUrl = normalizeResourceUrl(input?.resourceUrl);
|
|
760
799
|
const intentSummary = normalizeIntentSummary(input?.intentSummary);
|
|
800
|
+
const sessionId = normalizeWalletSessionId(input?.sessionId);
|
|
761
801
|
const resource = await fetchPaymentNeeded(resourceUrl, options);
|
|
762
802
|
if (resource.alreadyPaid) {
|
|
763
803
|
const state = (await readPaymentState(resourceUrl, options)) || {
|
|
@@ -784,7 +824,7 @@ export async function payAlipay402Resource(input, options = {}) {
|
|
|
784
824
|
version: 1,
|
|
785
825
|
resourceUrl,
|
|
786
826
|
resourceDigest: digest(resourceUrl),
|
|
787
|
-
sessionId
|
|
827
|
+
sessionId,
|
|
788
828
|
paymentNeededPath: paths.paymentNeededPath,
|
|
789
829
|
queryCredential: null,
|
|
790
830
|
merchantResource: {
|
|
@@ -800,9 +840,9 @@ export async function payAlipay402Resource(input, options = {}) {
|
|
|
800
840
|
const walletInvocation = await runOfficialCli([
|
|
801
841
|
'402-buyer-pay',
|
|
802
842
|
// The official buyer CLI accepts this long form of -s/-f/-r. The file
|
|
803
|
-
// name is intentionally private and random
|
|
804
|
-
// content, original resource URL, and
|
|
805
|
-
// define the payment request.
|
|
843
|
+
// name is intentionally private and random. The exact Payment-Needed
|
|
844
|
+
// content, original resource URL, and active Agent business session are
|
|
845
|
+
// what define the payment request.
|
|
806
846
|
'--session-id', state.sessionId,
|
|
807
847
|
'--file', state.paymentNeededPath,
|
|
808
848
|
'--resource-url', resourceUrl,
|
|
@@ -889,7 +929,7 @@ export async function queryAlipay402Resource(input, options = {}) {
|
|
|
889
929
|
};
|
|
890
930
|
const queryArg = queryCredential.type === 'out_shake_no'
|
|
891
931
|
? ['--out-shake-no', queryCredential.value]
|
|
892
|
-
: ['-
|
|
932
|
+
: ['--trade-no', queryCredential.value];
|
|
893
933
|
const walletInvocation = await runOfficialCli([
|
|
894
934
|
'402-query-payment-status',
|
|
895
935
|
...queryArg,
|
package/index.mjs
CHANGED