@xiashe/sdk 0.1.21 → 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 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.21';
9
+ export const VERSION = '0.1.23';
10
10
 
11
11
  export const DEFAULTS = {
12
12
  cn: {
@@ -1362,6 +1362,8 @@ export {
1362
1362
  AlipayWalletError,
1363
1363
  checkAlipayWallet,
1364
1364
  getAlipayWalletBridgeInfo,
1365
+ preflightAlipay402Resource,
1365
1366
  payAlipay402Resource,
1366
- queryAlipay402Resource
1367
+ queryAlipay402Resource,
1368
+ diagnoseAlipay402Resource
1367
1369
  } from './alipayWallet.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiashe/sdk",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "type": "module",
5
5
  "main": "index.mjs",
6
6
  "exports": {