@xiashe/sdk 0.1.22 → 0.1.23
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 +104 -2
- package/index.mjs +2 -1
- package/package.json +1 -1
package/alipayWallet.mjs
CHANGED
|
@@ -480,7 +480,7 @@ async function fetchPaymentNeeded(resourceUrl, options = {}) {
|
|
|
480
480
|
if (!paymentNeeded) {
|
|
481
481
|
throw new AlipayWalletError('ALIPAY_RESOURCE_PAYMENT_NEEDED_MISSING', '支付资源未返回有效的支付宝付款请求。');
|
|
482
482
|
}
|
|
483
|
-
return { paymentNeeded };
|
|
483
|
+
return { paymentNeeded, responseStatus: response.status };
|
|
484
484
|
} catch (error) {
|
|
485
485
|
if (error instanceof AlipayWalletError) throw error;
|
|
486
486
|
if (error?.name === 'AbortError') {
|
|
@@ -492,6 +492,96 @@ async function fetchPaymentNeeded(resourceUrl, options = {}) {
|
|
|
492
492
|
}
|
|
493
493
|
}
|
|
494
494
|
|
|
495
|
+
/**
|
|
496
|
+
* Validate only the public shape of a 402 bill in memory. This is deliberately
|
|
497
|
+
* not a wallet call and never returns the bill, its signature, or any payment
|
|
498
|
+
* credentials to the caller.
|
|
499
|
+
*/
|
|
500
|
+
function validatePaymentNeededShape(paymentNeeded) {
|
|
501
|
+
let bill;
|
|
502
|
+
try {
|
|
503
|
+
bill = JSON.parse(Buffer.from(String(paymentNeeded), 'base64url').toString('utf8'));
|
|
504
|
+
} catch {
|
|
505
|
+
return {
|
|
506
|
+
ok: false,
|
|
507
|
+
checks: [{ id: 'payment_needed_encoding', ok: false }]
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
const protocol = bill?.protocol && typeof bill.protocol === 'object' ? bill.protocol : {};
|
|
511
|
+
const method = bill?.method && typeof bill.method === 'object' ? bill.method : {};
|
|
512
|
+
const sellerId = safeText(protocol.seller_unique_id, 80);
|
|
513
|
+
const methodSellerId = safeText(method.seller_id, 80);
|
|
514
|
+
const amount = safeText(protocol.amount, 32);
|
|
515
|
+
const payBefore = safeText(protocol.pay_before, 80);
|
|
516
|
+
const checks = [
|
|
517
|
+
{
|
|
518
|
+
id: 'payment_needed_encoding',
|
|
519
|
+
ok: true
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
id: 'protocol_fields',
|
|
523
|
+
ok: Boolean(
|
|
524
|
+
safeText(protocol.out_trade_no, 200) &&
|
|
525
|
+
/^\d+(?:\.\d{1,2})?$/.test(amount) && Number(amount) > 0 &&
|
|
526
|
+
safeText(protocol.currency, 16).toUpperCase() === 'CNY' &&
|
|
527
|
+
safeText(protocol.resource_id, 1_000) &&
|
|
528
|
+
payBefore && Number.isFinite(Date.parse(payBefore)) &&
|
|
529
|
+
safeText(protocol.seller_signature, 8_000) &&
|
|
530
|
+
safeText(protocol.seller_sign_type, 32).toUpperCase() === 'RSA2' &&
|
|
531
|
+
/^2088\d{8,}$/.test(sellerId)
|
|
532
|
+
)
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
id: 'merchant_fields',
|
|
536
|
+
ok: Boolean(
|
|
537
|
+
safeText(method.seller_name, 160) &&
|
|
538
|
+
/^2088\d{8,}$/.test(methodSellerId) &&
|
|
539
|
+
safeText(method.seller_app_id, 80) &&
|
|
540
|
+
safeText(method.goods_name, 160) &&
|
|
541
|
+
safeText(method.seller_unique_id_key, 80) === 'seller_id' &&
|
|
542
|
+
safeText(method.service_id, 160)
|
|
543
|
+
)
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
id: 'seller_identity_consistency',
|
|
547
|
+
ok: Boolean(sellerId && sellerId === methodSellerId)
|
|
548
|
+
}
|
|
549
|
+
];
|
|
550
|
+
return { ok: checks.every((check) => check.ok), checks };
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Read-only seller-resource preflight. It proves that this exact resource
|
|
555
|
+
* produces a structurally valid 402 bill, but never opens the wallet or
|
|
556
|
+
* attempts to create a trade.
|
|
557
|
+
*/
|
|
558
|
+
export async function preflightAlipay402Resource(input, options = {}) {
|
|
559
|
+
const resourceUrl = normalizeResourceUrl(input?.resourceUrl);
|
|
560
|
+
const resource = await fetchPaymentNeeded(resourceUrl, options);
|
|
561
|
+
if (resource.alreadyPaid) {
|
|
562
|
+
return {
|
|
563
|
+
ok: true,
|
|
564
|
+
status: 'resource_already_available',
|
|
565
|
+
provider: 'alipay-agent-payment',
|
|
566
|
+
walletInvoked: false,
|
|
567
|
+
message: '该资源已可访问,无需再次创建付款账单。',
|
|
568
|
+
checks: [{ id: 'resource_access', ok: true }]
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
const validation = validatePaymentNeededShape(resource.paymentNeeded);
|
|
572
|
+
return {
|
|
573
|
+
ok: validation.ok,
|
|
574
|
+
status: validation.ok ? 'payment_needed_ready' : 'payment_needed_invalid',
|
|
575
|
+
provider: 'alipay-agent-payment',
|
|
576
|
+
walletInvoked: false,
|
|
577
|
+
resourceStatus: resource.responseStatus,
|
|
578
|
+
message: validation.ok
|
|
579
|
+
? '虾舍已为该资源签发结构有效的支付宝 402 账单;尚未调用钱包或创建交易。'
|
|
580
|
+
: '虾舍资源返回了 402,但账单结构不符合支付宝 AI收要求;未调用钱包。',
|
|
581
|
+
checks: validation.checks
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
495
585
|
function publicPaymentResult(state, status, diagnostic, options = {}) {
|
|
496
586
|
const commandFailed = options.commandFailed === true;
|
|
497
587
|
const failureCode = options.failureCode || 'official_cli_nonzero_exit';
|
|
@@ -505,6 +595,7 @@ function publicPaymentResult(state, status, diagnostic, options = {}) {
|
|
|
505
595
|
walletInvoked: true,
|
|
506
596
|
message: failed ? paymentFailureMessage(failureCode) : paymentMessage(status),
|
|
507
597
|
diagnostic,
|
|
598
|
+
...(options.merchantResource ? { merchantResource: options.merchantResource } : {}),
|
|
508
599
|
...(failed && diagnosis ? { userVisible: paymentFailureUserVisible(diagnosis), diagnosis } : {}),
|
|
509
600
|
...(!failed && options.userVisible ? { userVisible: options.userVisible } : {}),
|
|
510
601
|
...(options.officialMedia ? { officialMedia: options.officialMedia } : {}),
|
|
@@ -566,6 +657,11 @@ export async function payAlipay402Resource(input, options = {}) {
|
|
|
566
657
|
return publicPaymentResult(state, 'completed', 'resource_already_available');
|
|
567
658
|
}
|
|
568
659
|
|
|
660
|
+
const billValidation = validatePaymentNeededShape(resource.paymentNeeded);
|
|
661
|
+
if (!billValidation.ok) {
|
|
662
|
+
throw new AlipayWalletError('ALIPAY_PAYMENT_NEEDED_INVALID', '虾舍返回的支付宝付款账单结构无效;未调用钱包或创建交易。');
|
|
663
|
+
}
|
|
664
|
+
|
|
569
665
|
const paths = statePaths(resourceUrl, options);
|
|
570
666
|
await ensurePrivateDirectory(paths.directory);
|
|
571
667
|
await writePrivateFile(paths.paymentNeededPath, resource.paymentNeeded);
|
|
@@ -576,6 +672,10 @@ export async function payAlipay402Resource(input, options = {}) {
|
|
|
576
672
|
sessionId: randomUUID(),
|
|
577
673
|
paymentNeededPath: paths.paymentNeededPath,
|
|
578
674
|
queryCredential: null,
|
|
675
|
+
merchantResource: {
|
|
676
|
+
paymentNeededReceived: true,
|
|
677
|
+
billShapeValidated: true
|
|
678
|
+
},
|
|
579
679
|
status: 'started',
|
|
580
680
|
createdAt: new Date().toISOString(),
|
|
581
681
|
updatedAt: new Date().toISOString()
|
|
@@ -618,7 +718,8 @@ export async function payAlipay402Resource(input, options = {}) {
|
|
|
618
718
|
userVisible: ['pending_confirmation', 'completed'].includes(status)
|
|
619
719
|
? userVisibleWalletOutput(walletInvocation.stdout, walletInvocation.stderr)
|
|
620
720
|
: null,
|
|
621
|
-
officialMedia
|
|
721
|
+
officialMedia,
|
|
722
|
+
merchantResource: state.merchantResource
|
|
622
723
|
});
|
|
623
724
|
}
|
|
624
725
|
|
|
@@ -701,6 +802,7 @@ export async function diagnoseAlipay402Resource(input, options = {}) {
|
|
|
701
802
|
walletInvoked: state.status !== 'started',
|
|
702
803
|
queryAvailable: Boolean(state.queryCredential?.value),
|
|
703
804
|
diagnostic: state.lastWalletDiagnostic || 'payment_state_recorded',
|
|
805
|
+
merchantResource: state.merchantResource || null,
|
|
704
806
|
...(state.lastFailureCode ? { failureCode: state.lastFailureCode } : {}),
|
|
705
807
|
...(state.lastDiagnosis ? {
|
|
706
808
|
diagnosis: state.lastDiagnosis,
|
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.
|
|
9
|
+
export const VERSION = '0.1.23';
|
|
10
10
|
|
|
11
11
|
export const DEFAULTS = {
|
|
12
12
|
cn: {
|
|
@@ -1362,6 +1362,7 @@ export {
|
|
|
1362
1362
|
AlipayWalletError,
|
|
1363
1363
|
checkAlipayWallet,
|
|
1364
1364
|
getAlipayWalletBridgeInfo,
|
|
1365
|
+
preflightAlipay402Resource,
|
|
1365
1366
|
payAlipay402Resource,
|
|
1366
1367
|
queryAlipay402Resource,
|
|
1367
1368
|
diagnoseAlipay402Resource
|