@alphawebai/hs-prism 0.1.0-rc.30 → 0.1.0-rc.32

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.
@@ -1,7 +1,7 @@
1
1
  export declare const ALPHAWEB_STRIPE_WEBHOOK_EVENTS: readonly ["charge.dispute.closed", "charge.dispute.created", "charge.dispute.funds_reinstated", "charge.dispute.funds_withdrawn", "charge.dispute.updated", "mandate.updated", "payment_intent.amount_capturable_updated", "payment_intent.canceled", "payment_intent.payment_failed", "payment_intent.processing", "payment_intent.requires_action", "payment_intent.succeeded", "payment_method.attached", "payment_method.automatically_updated", "payment_method.detached", "payment_method.updated", "refund.created", "refund.failed", "refund.updated", "setup_intent.canceled", "setup_intent.requires_action", "setup_intent.setup_failed", "setup_intent.succeeded"];
2
2
  export declare const ALPHAWEB_PAYPAL_WEBHOOK_EVENTS: readonly ["CHECKOUT.ORDER.APPROVED", "CHECKOUT.ORDER.COMPLETED", "CHECKOUT.ORDER.PROCESSED", "CHECKOUT.PAYMENT-APPROVAL.REVERSED", "CUSTOMER.DISPUTE.CREATED", "CUSTOMER.DISPUTE.RESOLVED", "CUSTOMER.DISPUTE.UPDATED", "PAYMENT.AUTHORIZATION.CREATED", "PAYMENT.AUTHORIZATION.VOIDED", "PAYMENT.CAPTURE.COMPLETED", "PAYMENT.CAPTURE.DECLINED", "PAYMENT.CAPTURE.PENDING", "PAYMENT.CAPTURE.REFUNDED", "PAYMENT.CAPTURE.REVERSED", "PAYMENT.REFUND.FAILED", "PAYMENT.REFUND.PENDING", "RISK.DISPUTE.CREATED", "VAULT.PAYMENT-TOKEN.CREATED", "VAULT.PAYMENT-TOKEN.DELETED", "VAULT.PAYMENT-TOKEN.DELETION-INITIATED"];
3
3
  export declare const ALPHAWEB_PRISM_COMPATIBILITY_MANIFEST: Readonly<{
4
- readonly release: "0.1.0-rc.30";
4
+ readonly release: "0.1.0-rc.32";
5
5
  readonly upstreamCommit: "b07af7d9dac271de729548e74217ddcf895c602b";
6
6
  readonly medusaRange: ">=2.17.2 <2.18.0";
7
7
  readonly stripeApiVersion: "2026-06-24.dahlia";
@@ -49,7 +49,7 @@ exports.ALPHAWEB_PAYPAL_WEBHOOK_EVENTS = Object.freeze([
49
49
  'VAULT.PAYMENT-TOKEN.DELETION-INITIATED',
50
50
  ]);
51
51
  exports.ALPHAWEB_PRISM_COMPATIBILITY_MANIFEST = Object.freeze({
52
- release: '0.1.0-rc.30',
52
+ release: '0.1.0-rc.32',
53
53
  upstreamCommit: 'b07af7d9dac271de729548e74217ddcf895c602b',
54
54
  medusaRange: '>=2.17.2 <2.18.0',
55
55
  stripeApiVersion: '2026-06-24.dahlia',
@@ -349,6 +349,9 @@ function paypalWebhookObjectKind(type) {
349
349
  }
350
350
  if (type.startsWith('PAYMENT.AUTHORIZATION.'))
351
351
  return 'authorization';
352
+ if (type === 'PAYMENT.CAPTURE.REFUNDED' || type === 'PAYMENT.CAPTURE.REVERSED') {
353
+ return 'refund';
354
+ }
352
355
  if (type.startsWith('PAYMENT.CAPTURE.'))
353
356
  return 'capture';
354
357
  if (type.startsWith('PAYMENT.REFUND.'))
@@ -396,8 +399,49 @@ function paypalWebhookPurchaseUnit(resource) {
396
399
  return {};
397
400
  return recordOf(resource.purchase_units[0]);
398
401
  }
399
- function paypalWebhookMoney(resource, purchaseUnit) {
400
- const amount = recordOf(resource.amount ?? purchaseUnit.amount);
402
+ function paypalWebhookLinkedId(resource, collection) {
403
+ if (!Array.isArray(resource.links))
404
+ return null;
405
+ for (const linkValue of resource.links) {
406
+ const link = recordOf(linkValue);
407
+ const relationship = stringOf(link.rel);
408
+ if (collection === 'orders' && relationship !== 'up')
409
+ continue;
410
+ if (collection === 'captures' && relationship !== 'up' && relationship !== 'capture')
411
+ continue;
412
+ const href = stringOf(link.href);
413
+ if (!href)
414
+ continue;
415
+ try {
416
+ const segments = new URL(href).pathname.split('/').filter(Boolean);
417
+ const collectionIndex = segments.findIndex((segment) => segment === collection);
418
+ const id = collectionIndex >= 0 ? segments[collectionIndex + 1] : undefined;
419
+ if (id)
420
+ return id;
421
+ }
422
+ catch {
423
+ // An invalid provider link is not authoritative correlation evidence.
424
+ }
425
+ }
426
+ return null;
427
+ }
428
+ function paypalDisputeTransactionInfo(resource) {
429
+ if (!Array.isArray(resource.disputed_transactions))
430
+ return [];
431
+ return resource.disputed_transactions.map((transactionValue) => {
432
+ const transaction = recordOf(transactionValue);
433
+ const transactionInfo = recordOf(transaction.transaction_info);
434
+ return Object.keys(transactionInfo).length > 0 ? transactionInfo : transaction;
435
+ });
436
+ }
437
+ function consistentString(values) {
438
+ if (values.length === 0 || values.some((value) => value === null))
439
+ return null;
440
+ const unique = new Set(values);
441
+ return unique.size === 1 ? [...unique][0] : null;
442
+ }
443
+ function paypalWebhookMoney(resource, purchaseUnit, objectKind) {
444
+ const amount = recordOf(objectKind === 'dispute' ? resource.dispute_amount : resource.amount ?? purchaseUnit.amount);
401
445
  const currency = stringOf(amount.currency_code)?.toLowerCase();
402
446
  const value = stringOf(amount.value);
403
447
  if (!currency || !value)
@@ -412,6 +456,58 @@ function paypalWebhookMoney(resource, purchaseUnit) {
412
456
  return null;
413
457
  }
414
458
  }
459
+ function paypalWebhookRefundStatus(type, resource) {
460
+ if (type === 'PAYMENT.CAPTURE.REFUNDED' || type === 'PAYMENT.CAPTURE.REVERSED') {
461
+ return 'succeeded';
462
+ }
463
+ if (type === 'PAYMENT.REFUND.FAILED')
464
+ return 'failed';
465
+ if (type === 'PAYMENT.REFUND.PENDING')
466
+ return 'pending';
467
+ const status = stringOf(resource.status)?.toUpperCase();
468
+ if (status === 'COMPLETED' || status === 'REFUNDED' || status === 'REVERSED') {
469
+ return 'succeeded';
470
+ }
471
+ if (status === 'PENDING')
472
+ return 'pending';
473
+ if (status === 'FAILED' || status === 'DECLINED' || status === 'DENIED')
474
+ return 'failed';
475
+ if (status === 'CANCELED' || status === 'CANCELLED')
476
+ return 'canceled';
477
+ return 'unknown';
478
+ }
479
+ function paypalWebhookSafeData(type, resource, disputeTransactions, parentCaptureId) {
480
+ const safeData = Object.fromEntries(['status', 'reason'].flatMap((key) => {
481
+ const value = resource[key];
482
+ return value === null || ['string', 'number', 'boolean'].includes(typeof value)
483
+ ? [[key, value]]
484
+ : [];
485
+ }));
486
+ if (parentCaptureId)
487
+ safeData.parent_capture_id = parentCaptureId;
488
+ if (type === 'PAYMENT.CAPTURE.REFUNDED')
489
+ safeData.refund_semantics = 'merchant_refund';
490
+ if (type === 'PAYMENT.CAPTURE.REVERSED')
491
+ safeData.refund_semantics = 'provider_reversal';
492
+ if (type.includes('DISPUTE')) {
493
+ const rawStatus = stringOf(resource.status)?.toUpperCase();
494
+ safeData.dispute_status =
495
+ type === 'CUSTOMER.DISPUTE.RESOLVED' || rawStatus === 'RESOLVED' ? 'closed' : 'opened';
496
+ const lifeCycleStage = stringOf(resource.dispute_life_cycle_stage);
497
+ if (lifeCycleStage)
498
+ safeData.dispute_life_cycle_stage = lifeCycleStage;
499
+ const outcomeCode = stringOf(recordOf(resource.dispute_outcome).outcome_code);
500
+ if (outcomeCode)
501
+ safeData.outcome_code = outcomeCode;
502
+ const sellerTransactionId = consistentString(disputeTransactions.map((transaction) => stringOf(transaction.seller_transaction_id)));
503
+ if (sellerTransactionId)
504
+ safeData.paypal_seller_transaction_id = sellerTransactionId;
505
+ const buyerTransactionId = consistentString(disputeTransactions.map((transaction) => stringOf(transaction.buyer_transaction_id)));
506
+ if (buyerTransactionId)
507
+ safeData.paypal_buyer_transaction_id = buyerTransactionId;
508
+ }
509
+ return safeData;
510
+ }
415
511
  function normalizePayPalWebhook(input, binding) {
416
512
  let parsed;
417
513
  try {
@@ -427,33 +523,24 @@ function normalizePayPalWebhook(input, binding) {
427
523
  const resourceVersion = stringOf(event.resource_version);
428
524
  const resource = recordOf(event.resource);
429
525
  const purchaseUnit = paypalWebhookPurchaseUnit(resource);
430
- const objectId = stringOf(resource.id);
526
+ const objectKind = paypalWebhookObjectKind(type ?? '');
527
+ const objectId = objectKind === 'dispute' ? stringOf(resource.dispute_id) : stringOf(resource.id);
431
528
  if (!providerEventId || !type || !createdAt || !objectId) {
432
529
  throw validationError('The verified PayPal event identity is incomplete.', 'PRISM_WEBHOOK_IDENTITY');
433
530
  }
434
531
  const related = recordOf(recordOf(resource.supplementary_data).related_ids);
435
- const linkedOrderId = Array.isArray(resource.links)
436
- ? resource.links.map(recordOf).flatMap((link) => {
437
- if (stringOf(link.rel) !== 'up')
438
- return [];
439
- const href = stringOf(link.href);
440
- if (!href)
441
- return [];
442
- try {
443
- const segments = new URL(href).pathname.split('/').filter(Boolean);
444
- const orderIndex = segments.findIndex((segment) => segment === 'orders');
445
- return orderIndex >= 0 && segments[orderIndex + 1] ? [segments[orderIndex + 1]] : [];
446
- }
447
- catch {
448
- return [];
449
- }
450
- })[0] ?? null
451
- : null;
452
- const invoiceId = stringOf(resource.invoice_id) ?? stringOf(purchaseUnit.invoice_id);
453
- const medusaPaymentSessionId = stringOf(resource.custom_id) ?? stringOf(purchaseUnit.custom_id);
532
+ const linkedOrderId = paypalWebhookLinkedId(resource, 'orders');
533
+ const linkedCaptureId = paypalWebhookLinkedId(resource, 'captures');
534
+ const parentCaptureId = stringOf(related.capture_id) ?? stringOf(resource.capture_id) ?? linkedCaptureId;
535
+ const disputeTransactions = paypalDisputeTransactionInfo(resource);
536
+ const invoiceId = objectKind === 'dispute'
537
+ ? consistentString(disputeTransactions.map((transaction) => stringOf(transaction.invoice_number)))
538
+ : stringOf(resource.invoice_id) ?? stringOf(purchaseUnit.invoice_id);
539
+ const medusaPaymentSessionId = objectKind === 'dispute'
540
+ ? consistentString(disputeTransactions.map((transaction) => stringOf(transaction.custom)))
541
+ : stringOf(resource.custom_id) ?? stringOf(purchaseUnit.custom_id);
454
542
  const connectorPaymentId = stringOf(related.order_id) ??
455
543
  linkedOrderId ??
456
- stringOf(resource.invoice_id) ??
457
544
  (type.startsWith('CHECKOUT.ORDER.') ? objectId : null);
458
545
  const bodyDigest = (0, node_crypto_1.createHash)('sha256').update(input.rawBody).digest('hex');
459
546
  const envelope = {
@@ -465,7 +552,7 @@ function normalizePayPalWebhook(input, binding) {
465
552
  apiVersion: resourceVersion,
466
553
  type,
467
554
  createdAt,
468
- objectKind: paypalWebhookObjectKind(type),
555
+ objectKind,
469
556
  objectId,
470
557
  connectorPaymentId,
471
558
  paymentIntentId: null,
@@ -481,12 +568,12 @@ function normalizePayPalWebhook(input, binding) {
481
568
  },
482
569
  };
483
570
  }
484
- const money = paypalWebhookMoney(resource, purchaseUnit);
571
+ const money = paypalWebhookMoney(resource, purchaseUnit, objectKind);
485
572
  const paymentSourceVault = recordOf(recordOf(recordOf(recordOf(resource.payment_source).paypal).attributes).vault);
486
573
  const vaultDetails = type.startsWith('VAULT.PAYMENT-TOKEN.')
487
574
  ? resource
488
575
  : paymentSourceVault;
489
- const merchantOperationId = invoiceId ?? connectorPaymentId;
576
+ const merchantOperationId = invoiceId;
490
577
  const normalized = {
491
578
  ...envelope,
492
579
  customerId: stringOf(recordOf(vaultDetails.customer).id) ??
@@ -497,24 +584,39 @@ function normalizePayPalWebhook(input, binding) {
497
584
  medusaPaymentSessionId,
498
585
  status: paypalWebhookStatus(type, resource),
499
586
  money,
500
- refunds: type.startsWith('PAYMENT.REFUND.') && money
587
+ refunds: objectKind === 'refund' && money && parentCaptureId
501
588
  ? [
502
589
  {
503
590
  refundId: objectId,
504
- chargeId: stringOf(related.capture_id),
591
+ chargeId: parentCaptureId,
505
592
  paymentIntentId: null,
506
- status: type === 'PAYMENT.REFUND.FAILED' ? 'failed' : 'pending',
593
+ status: paypalWebhookRefundStatus(type, resource),
507
594
  money,
508
595
  },
509
596
  ]
510
597
  : [],
511
- safeData: Object.fromEntries(['status', 'reason', 'outcome_code', 'dispute_amount'].flatMap((key) => {
512
- const value = resource[key];
513
- return value === null || ['string', 'number', 'boolean'].includes(typeof value)
514
- ? [[key, value]]
515
- : [];
516
- })),
598
+ safeData: paypalWebhookSafeData(type, resource, disputeTransactions, parentCaptureId),
517
599
  };
600
+ if (objectKind === 'refund' && (!money || !parentCaptureId)) {
601
+ return {
602
+ status: 'normalization_failed',
603
+ envelope,
604
+ error: {
605
+ code: 'WEBHOOK_OBJECT_INVALID',
606
+ safeMessage: 'The PayPal refund event is missing money or its parent capture identity.',
607
+ },
608
+ };
609
+ }
610
+ if (objectKind === 'dispute' && !money) {
611
+ return {
612
+ status: 'normalization_failed',
613
+ envelope,
614
+ error: {
615
+ code: 'WEBHOOK_OBJECT_INVALID',
616
+ safeMessage: 'The PayPal dispute event is missing dispute amount or currency.',
617
+ },
618
+ };
619
+ }
518
620
  return { status: 'normalized', event: normalized };
519
621
  }
520
622
  class PrismPayPalConnector {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alphawebai/hs-prism",
3
- "version": "0.1.0-rc.30",
3
+ "version": "0.1.0-rc.32",
4
4
  "description": "Hyperswitch Payments SDK — Node.js client for connector integrations via UniFFI FFI",
5
5
  "main": "dist/src/index.js",
6
6
  "exports": {