@rovela-ai/sdk 0.24.1 → 0.25.1

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.
Files changed (128) hide show
  1. package/dist/admin/api/index.d.ts +1 -0
  2. package/dist/admin/api/index.d.ts.map +1 -1
  3. package/dist/admin/api/index.js +2 -0
  4. package/dist/admin/api/index.js.map +1 -1
  5. package/dist/admin/api/payment-providers.d.ts +69 -0
  6. package/dist/admin/api/payment-providers.d.ts.map +1 -0
  7. package/dist/admin/api/payment-providers.js +206 -0
  8. package/dist/admin/api/payment-providers.js.map +1 -0
  9. package/dist/admin/api/settings.d.ts.map +1 -1
  10. package/dist/admin/api/settings.js +4 -1
  11. package/dist/admin/api/settings.js.map +1 -1
  12. package/dist/admin/components/OrderDetails.d.ts.map +1 -1
  13. package/dist/admin/components/OrderDetails.js +11 -2
  14. package/dist/admin/components/OrderDetails.js.map +1 -1
  15. package/dist/admin/components/PaymentProviderPanels.d.ts +2 -0
  16. package/dist/admin/components/PaymentProviderPanels.d.ts.map +1 -0
  17. package/dist/admin/components/PaymentProviderPanels.js +265 -0
  18. package/dist/admin/components/PaymentProviderPanels.js.map +1 -0
  19. package/dist/admin/components/PaymentSettings.d.ts.map +1 -1
  20. package/dist/admin/components/PaymentSettings.js +7 -20
  21. package/dist/admin/components/PaymentSettings.js.map +1 -1
  22. package/dist/admin/components/emails/EmailSendingPanel.d.ts.map +1 -1
  23. package/dist/admin/components/emails/EmailSendingPanel.js +2 -2
  24. package/dist/admin/components/emails/EmailSendingPanel.js.map +1 -1
  25. package/dist/admin/components/index.d.ts +1 -0
  26. package/dist/admin/components/index.d.ts.map +1 -1
  27. package/dist/admin/components/index.js +1 -0
  28. package/dist/admin/components/index.js.map +1 -1
  29. package/dist/admin/integrations/registry.d.ts.map +1 -1
  30. package/dist/admin/integrations/registry.js +5 -73
  31. package/dist/admin/integrations/registry.js.map +1 -1
  32. package/dist/cart/store.d.ts +4 -1
  33. package/dist/cart/store.d.ts.map +1 -1
  34. package/dist/cart/store.js +189 -161
  35. package/dist/cart/store.js.map +1 -1
  36. package/dist/checkout/api/index.d.ts +6 -0
  37. package/dist/checkout/api/index.d.ts.map +1 -1
  38. package/dist/checkout/api/index.js +6 -0
  39. package/dist/checkout/api/index.js.map +1 -1
  40. package/dist/checkout/api/manual.d.ts.map +1 -1
  41. package/dist/checkout/api/manual.js +5 -1
  42. package/dist/checkout/api/manual.js.map +1 -1
  43. package/dist/checkout/api/payment-methods.d.ts +23 -6
  44. package/dist/checkout/api/payment-methods.d.ts.map +1 -1
  45. package/dist/checkout/api/payment-methods.js +67 -8
  46. package/dist/checkout/api/payment-methods.js.map +1 -1
  47. package/dist/checkout/api/paypal-capture.d.ts +38 -0
  48. package/dist/checkout/api/paypal-capture.d.ts.map +1 -0
  49. package/dist/checkout/api/paypal-capture.js +126 -0
  50. package/dist/checkout/api/paypal-capture.js.map +1 -0
  51. package/dist/checkout/api/paypal-webhook.d.ts +24 -0
  52. package/dist/checkout/api/paypal-webhook.d.ts.map +1 -0
  53. package/dist/checkout/api/paypal-webhook.js +122 -0
  54. package/dist/checkout/api/paypal-webhook.js.map +1 -0
  55. package/dist/checkout/api/paypal.d.ts +30 -0
  56. package/dist/checkout/api/paypal.d.ts.map +1 -0
  57. package/dist/checkout/api/paypal.js +70 -0
  58. package/dist/checkout/api/paypal.js.map +1 -0
  59. package/dist/checkout/api/up2pay-ipn.d.ts +28 -0
  60. package/dist/checkout/api/up2pay-ipn.d.ts.map +1 -0
  61. package/dist/checkout/api/up2pay-ipn.js +88 -0
  62. package/dist/checkout/api/up2pay-ipn.js.map +1 -0
  63. package/dist/checkout/api/up2pay.d.ts +34 -0
  64. package/dist/checkout/api/up2pay.d.ts.map +1 -0
  65. package/dist/checkout/api/up2pay.js +82 -0
  66. package/dist/checkout/api/up2pay.js.map +1 -0
  67. package/dist/checkout/components/CheckoutFlow.d.ts.map +1 -1
  68. package/dist/checkout/components/CheckoutFlow.js +64 -9
  69. package/dist/checkout/components/CheckoutFlow.js.map +1 -1
  70. package/dist/checkout/components/PayPalCheckoutButtons.d.ts +32 -0
  71. package/dist/checkout/components/PayPalCheckoutButtons.d.ts.map +1 -0
  72. package/dist/checkout/components/PayPalCheckoutButtons.js +116 -0
  73. package/dist/checkout/components/PayPalCheckoutButtons.js.map +1 -0
  74. package/dist/checkout/components/index.d.ts +2 -0
  75. package/dist/checkout/components/index.d.ts.map +1 -1
  76. package/dist/checkout/components/index.js +1 -0
  77. package/dist/checkout/components/index.js.map +1 -1
  78. package/dist/checkout/components/payment-logos.d.ts +21 -0
  79. package/dist/checkout/components/payment-logos.d.ts.map +1 -0
  80. package/dist/checkout/components/payment-logos.js +21 -0
  81. package/dist/checkout/components/payment-logos.js.map +1 -0
  82. package/dist/checkout/hooks/useCheckout.d.ts.map +1 -1
  83. package/dist/checkout/hooks/useCheckout.js +160 -0
  84. package/dist/checkout/hooks/useCheckout.js.map +1 -1
  85. package/dist/checkout/server/confirm-order.d.ts +54 -0
  86. package/dist/checkout/server/confirm-order.d.ts.map +1 -0
  87. package/dist/checkout/server/confirm-order.js +147 -0
  88. package/dist/checkout/server/confirm-order.js.map +1 -0
  89. package/dist/checkout/server/external-checkout.d.ts +68 -0
  90. package/dist/checkout/server/external-checkout.d.ts.map +1 -0
  91. package/dist/checkout/server/external-checkout.js +187 -0
  92. package/dist/checkout/server/external-checkout.js.map +1 -0
  93. package/dist/checkout/server/paypal.d.ts +127 -0
  94. package/dist/checkout/server/paypal.d.ts.map +1 -0
  95. package/dist/checkout/server/paypal.js +274 -0
  96. package/dist/checkout/server/paypal.js.map +1 -0
  97. package/dist/checkout/server/up2pay.d.ts +152 -0
  98. package/dist/checkout/server/up2pay.d.ts.map +1 -0
  99. package/dist/checkout/server/up2pay.js +287 -0
  100. package/dist/checkout/server/up2pay.js.map +1 -0
  101. package/dist/checkout/types.d.ts +26 -1
  102. package/dist/checkout/types.d.ts.map +1 -1
  103. package/dist/core/db/queries.d.ts +68 -39
  104. package/dist/core/db/queries.d.ts.map +1 -1
  105. package/dist/core/db/queries.js +96 -0
  106. package/dist/core/db/queries.js.map +1 -1
  107. package/dist/core/db/schema.d.ts +210 -5
  108. package/dist/core/db/schema.d.ts.map +1 -1
  109. package/dist/core/db/schema.js +33 -0
  110. package/dist/core/db/schema.js.map +1 -1
  111. package/dist/core/i18n/messages/en.d.ts.map +1 -1
  112. package/dist/core/i18n/messages/en.js +6 -0
  113. package/dist/core/i18n/messages/en.js.map +1 -1
  114. package/dist/core/i18n/messages/fr.d.ts.map +1 -1
  115. package/dist/core/i18n/messages/fr.js +6 -0
  116. package/dist/core/i18n/messages/fr.js.map +1 -1
  117. package/docs/changelog/epoch-12-payment-providers.md +152 -0
  118. package/package.json +26 -2
  119. package/templates/store-template/.claude/skills/build-feature/SKILL.md +2 -1
  120. package/templates/store-template/app/api/admin/payments/paypal/route.ts +10 -0
  121. package/templates/store-template/app/api/admin/payments/up2pay/route.ts +10 -0
  122. package/templates/store-template/app/api/checkout/paypal/capture/route.ts +8 -0
  123. package/templates/store-template/app/api/checkout/paypal/return/route.ts +9 -0
  124. package/templates/store-template/app/api/checkout/paypal/route.ts +9 -0
  125. package/templates/store-template/app/api/checkout/paypal/webhook/route.ts +9 -0
  126. package/templates/store-template/app/api/checkout/up2pay/ipn/route.ts +8 -0
  127. package/templates/store-template/app/api/checkout/up2pay/route.ts +9 -0
  128. package/templates/store-template/package.json +2 -2
@@ -0,0 +1,187 @@
1
+ /**
2
+ * @rovela/sdk/checkout/server/external-checkout
3
+ *
4
+ * Shared "start an external-provider payment" leg (PayPal, Up2pay): the
5
+ * same gates → buildQuote → pending-order sequence as the manual path
6
+ * (checkout/api/manual.ts), minus the confirmation email — external orders
7
+ * email on CONFIRMED payment (confirm-order.ts), not at creation.
8
+ *
9
+ * §55 invariants preserved verbatim: session-resolved identity, the ONE
10
+ * pricing engine in the ONE mode, PricingError → 4xx, idempotent reference
11
+ * through the shared payment-intent column. Discount redemptions + purchase
12
+ * analytics record at CREATE time — deliberate parity with the manual path
13
+ * (a pending order that is never paid keeps its redemption, exactly like an
14
+ * unpaid COD order; consistency beats a second bookkeeping model).
15
+ */
16
+ import { randomUUID } from 'crypto';
17
+ import { NextResponse } from 'next/server';
18
+ import { findSettings, recordDiscountRedemption } from '../../core/db/queries';
19
+ import { buildQuote, PricingError, CHECKOUT_PRICING_MODE } from './pricing';
20
+ import { resolveCheckoutCustomer } from './checkout-customer';
21
+ import { createOrderFromCheckout } from './order-service';
22
+ import { recordEvent } from '../../analytics/server';
23
+ import { isSupportedLocale } from '../../core/i18n/types';
24
+ const bad = (error, status = 400) => ({
25
+ ok: false,
26
+ response: NextResponse.json({ error, code: 'INVALID_REQUEST' }, { status }),
27
+ });
28
+ export async function preparePendingExternalOrder(input) {
29
+ const { body, provider } = input;
30
+ const settings = await findSettings();
31
+ // Trusted identity: session cookie, never body (§55).
32
+ const trustedCustomer = await resolveCheckoutCustomer();
33
+ const allowGuestCheckout = settings?.guestCheckout ?? true;
34
+ if (!allowGuestCheckout && !trustedCustomer) {
35
+ return {
36
+ ok: false,
37
+ response: NextResponse.json({
38
+ error: 'Guest checkout is disabled. Please sign in to continue.',
39
+ code: 'GUEST_CHECKOUT_DISABLED',
40
+ }, { status: 403 }),
41
+ };
42
+ }
43
+ // Inline-signed-in customers never type an email (the guest contact field
44
+ // is skipped), so the body may not carry one — the SESSION-resolved email
45
+ // is the fallback, and the better truth when present (0.25.1).
46
+ const email = body.customerEmail?.trim().toLowerCase() || trustedCustomer?.email?.trim().toLowerCase();
47
+ if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
48
+ return bad('A valid email is required');
49
+ }
50
+ // ── The Quote: the SAME engine in the SAME mode as every money path ──────
51
+ let quote;
52
+ try {
53
+ quote = await buildQuote({
54
+ items: body.items,
55
+ shippingAddress: body.shippingAddress,
56
+ selectedShipping: body.selectedShipping,
57
+ pricingMode: CHECKOUT_PRICING_MODE,
58
+ discountCode: body.discountCode ?? null,
59
+ customerId: trustedCustomer?.id ?? null,
60
+ customerGroupId: trustedCustomer?.groupId ?? null,
61
+ customerEmail: email,
62
+ }, { settings });
63
+ }
64
+ catch (err) {
65
+ if (err instanceof PricingError) {
66
+ return {
67
+ ok: false,
68
+ response: NextResponse.json({ error: err.message, code: err.code }, { status: err.status }),
69
+ };
70
+ }
71
+ throw err;
72
+ }
73
+ // ── Idempotency reference (shared payment-intent column) ─────────────────
74
+ const rawKey = typeof body.idempotencyKey === 'string' ? body.idempotencyKey : '';
75
+ const safeKey = rawKey.replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 48);
76
+ const reference = `${provider}_${safeKey || randomUUID()}`;
77
+ // ── Address snapshots (identical rules to the manual path) ───────────────
78
+ const orderShippingAddress = quote.pickupAddress
79
+ ? {
80
+ firstName: body.shippingAddress?.firstName || trustedCustomer?.name?.split(' ')[0] || '',
81
+ lastName: body.shippingAddress?.lastName ||
82
+ trustedCustomer?.name?.split(' ').slice(1).join(' ') ||
83
+ '',
84
+ ...quote.pickupAddress,
85
+ }
86
+ : quote.relayAddress
87
+ ? {
88
+ firstName: body.shippingAddress?.firstName || '',
89
+ lastName: body.shippingAddress?.lastName || '',
90
+ ...quote.relayAddress,
91
+ phone: body.shippingAddress?.phone,
92
+ }
93
+ : body.shippingAddress;
94
+ const orderBillingAddress = quote.relay && !body.billingAddress ? body.shippingAddress : body.billingAddress;
95
+ const orderLocale = isSupportedLocale(body.locale) ? body.locale : null;
96
+ const { orderId, isNew } = await createOrderFromCheckout({
97
+ locale: orderLocale,
98
+ sessionId: reference,
99
+ email,
100
+ items: quote.lines,
101
+ subtotal: quote.subtotalCents,
102
+ tax: quote.taxCents,
103
+ taxBreakdown: quote.taxBreakdown,
104
+ shipping: quote.shippingCents,
105
+ total: quote.totalCents,
106
+ currency: quote.currency,
107
+ discountCode: quote.discountCode ?? undefined,
108
+ discountAmount: quote.discountCents > 0 ? quote.discountCents : undefined,
109
+ shippingAddress: orderShippingAddress,
110
+ billingAddress: orderBillingAddress,
111
+ stripePaymentIntentId: reference,
112
+ customerId: trustedCustomer?.id,
113
+ shippingMethod: quote.shippingMethodName,
114
+ carrier: quote.carrier,
115
+ shippoRateId: quote.shippoRateId,
116
+ pickupLocationId: quote.pickupLocationId ?? undefined,
117
+ relay: quote.relay ?? undefined,
118
+ status: 'pending',
119
+ paymentProvider: provider,
120
+ paymentMethodLabel: input.providerLabel,
121
+ });
122
+ if (isNew && quote.appliedDiscounts.length > 0) {
123
+ for (const applied of quote.appliedDiscounts) {
124
+ try {
125
+ await recordDiscountRedemption({
126
+ discountId: applied.id,
127
+ orderId,
128
+ customerId: trustedCustomer?.id ?? null,
129
+ email,
130
+ amountCents: applied.amountCents,
131
+ });
132
+ }
133
+ catch (err) {
134
+ console.error(`[${provider} Checkout] redemption record failed:`, err);
135
+ }
136
+ }
137
+ }
138
+ // Server-side purchase analytics — the (order_id) partial unique index
139
+ // dedupes; recorded at create (manual-path parity, see file header).
140
+ if (isNew) {
141
+ const visitorId = body.metadata?.rovela_visitor_id;
142
+ const sessionId = body.metadata?.rovela_session_id;
143
+ if (visitorId && sessionId) {
144
+ try {
145
+ await recordEvent({
146
+ event: {
147
+ event: 'purchase',
148
+ visitor_id: visitorId,
149
+ session_id: sessionId,
150
+ order_id: orderId,
151
+ value_cents: quote.totalCents,
152
+ currency: quote.currency,
153
+ path: '/checkout/success',
154
+ },
155
+ device: null,
156
+ country: null,
157
+ });
158
+ }
159
+ catch (err) {
160
+ console.error(`[${provider} Checkout] purchase analytics failed:`, err);
161
+ }
162
+ }
163
+ }
164
+ return {
165
+ ok: true,
166
+ orderId,
167
+ isNew,
168
+ reference,
169
+ quote,
170
+ email,
171
+ orderLocale,
172
+ orderShippingAddress,
173
+ orderBillingAddress,
174
+ settings,
175
+ };
176
+ }
177
+ /**
178
+ * The absolute base URL for return/IPN links handed to a PSP. Prefers the
179
+ * deployed store's canonical env; falls back to the request origin (sandbox
180
+ * previews).
181
+ */
182
+ export function resolveStoreBaseUrl(requestOrigin) {
183
+ const envUrl = process.env.NEXT_PUBLIC_APP_URL?.trim();
184
+ const base = envUrl || requestOrigin;
185
+ return base.replace(/\/$/, '');
186
+ }
187
+ //# sourceMappingURL=external-checkout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external-checkout.js","sourceRoot":"","sources":["../../../src/checkout/server/external-checkout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,YAAY,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAA;AAC9E,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAA;AAE3E,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAA;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAGpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAwCzD,MAAM,GAAG,GAAG,CAAC,KAAa,EAAE,MAAM,GAAG,GAAG,EAA8B,EAAE,CAAC,CAAC;IACxE,EAAE,EAAE,KAAK;IACT,QAAQ,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,iBAA0B,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;CACrF,CAAC,CAAA;AAEF,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAAC,KAKjD;IACC,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAA;IAEhC,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAA;IAErC,sDAAsD;IACtD,MAAM,eAAe,GAAG,MAAM,uBAAuB,EAAE,CAAA;IAEvD,MAAM,kBAAkB,GAAG,QAAQ,EAAE,aAAa,IAAI,IAAI,CAAA;IAC1D,IAAI,CAAC,kBAAkB,IAAI,CAAC,eAAe,EAAE,CAAC;QAC5C,OAAO;YACL,EAAE,EAAE,KAAK;YACT,QAAQ,EAAE,YAAY,CAAC,IAAI,CACzB;gBACE,KAAK,EAAE,yDAAyD;gBAChE,IAAI,EAAE,yBAAkC;aACzC,EACD,EAAE,MAAM,EAAE,GAAG,EAAE,CAChB;SACF,CAAA;IACH,CAAC;IAED,0EAA0E;IAC1E,0EAA0E;IAC1E,+DAA+D;IAC/D,MAAM,KAAK,GACT,IAAI,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,eAAe,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAC1F,IAAI,CAAC,KAAK,IAAI,CAAC,4BAA4B,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACxD,OAAO,GAAG,CAAC,2BAA2B,CAAC,CAAA;IACzC,CAAC;IAED,4EAA4E;IAC5E,IAAI,KAAY,CAAA;IAChB,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,UAAU,CACtB;YACE,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;YACvC,WAAW,EAAE,qBAAqB;YAClC,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,IAAI;YACvC,UAAU,EAAE,eAAe,EAAE,EAAE,IAAI,IAAI;YACvC,eAAe,EAAE,eAAe,EAAE,OAAO,IAAI,IAAI;YACjD,aAAa,EAAE,KAAK;SACrB,EACD,EAAE,QAAQ,EAAE,CACb,CAAA;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,YAAY,EAAE,CAAC;YAChC,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,QAAQ,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;aAC5F,CAAA;QACH,CAAC;QACD,MAAM,GAAG,CAAA;IACX,CAAC;IAED,4EAA4E;IAC5E,MAAM,MAAM,GAAG,OAAO,IAAI,CAAC,cAAc,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAA;IACjF,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IAClE,MAAM,SAAS,GAAG,GAAG,QAAQ,IAAI,OAAO,IAAI,UAAU,EAAE,EAAE,CAAA;IAE1D,4EAA4E;IAC5E,MAAM,oBAAoB,GAAY,KAAK,CAAC,aAAa;QACvD,CAAC,CAAC;YACE,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,IAAI,eAAe,EAAE,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;YACxF,QAAQ,EACN,IAAI,CAAC,eAAe,EAAE,QAAQ;gBAC9B,eAAe,EAAE,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;gBACpD,EAAE;YACJ,GAAG,KAAK,CAAC,aAAa;SACvB;QACH,CAAC,CAAC,KAAK,CAAC,YAAY;YAClB,CAAC,CAAC;gBACE,SAAS,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,IAAI,EAAE;gBAChD,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,IAAI,EAAE;gBAC9C,GAAG,KAAK,CAAC,YAAY;gBACrB,KAAK,EAAE,IAAI,CAAC,eAAe,EAAE,KAAK;aACnC;YACH,CAAC,CAAE,IAAI,CAAC,eAA2B,CAAA;IAEvC,MAAM,mBAAmB,GACvB,KAAK,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAA;IAElF,MAAM,WAAW,GAAG,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;IAEvE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,uBAAuB,CAAC;QACvD,MAAM,EAAE,WAAW;QACnB,SAAS,EAAE,SAAS;QACpB,KAAK;QACL,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,QAAQ,EAAE,KAAK,CAAC,aAAa;QAC7B,GAAG,EAAE,KAAK,CAAC,QAAQ;QACnB,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,QAAQ,EAAE,KAAK,CAAC,aAAa;QAC7B,KAAK,EAAE,KAAK,CAAC,UAAU;QACvB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,YAAY,EAAE,KAAK,CAAC,YAAY,IAAI,SAAS;QAC7C,cAAc,EAAE,KAAK,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS;QACzE,eAAe,EAAE,oBAAoB;QACrC,cAAc,EAAE,mBAAmB;QACnC,qBAAqB,EAAE,SAAS;QAChC,UAAU,EAAE,eAAe,EAAE,EAAE;QAC/B,cAAc,EAAE,KAAK,CAAC,kBAAkB;QACxC,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB,IAAI,SAAS;QACrD,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,SAAS;QAC/B,MAAM,EAAE,SAAS;QACjB,eAAe,EAAE,QAAQ;QACzB,kBAAkB,EAAE,KAAK,CAAC,aAAa;KACxC,CAAC,CAAA;IAEF,IAAI,KAAK,IAAI,KAAK,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/C,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,gBAAgB,EAAE,CAAC;YAC7C,IAAI,CAAC;gBACH,MAAM,wBAAwB,CAAC;oBAC7B,UAAU,EAAE,OAAO,CAAC,EAAE;oBACtB,OAAO;oBACP,UAAU,EAAE,eAAe,EAAE,EAAE,IAAI,IAAI;oBACvC,KAAK;oBACL,WAAW,EAAE,OAAO,CAAC,WAAW;iBACjC,CAAC,CAAA;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,IAAI,QAAQ,sCAAsC,EAAE,GAAG,CAAC,CAAA;YACxE,CAAC;QACH,CAAC;IACH,CAAC;IAED,uEAAuE;IACvE,qEAAqE;IACrE,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,iBAAiB,CAAA;QAClD,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,iBAAiB,CAAA;QAClD,IAAI,SAAS,IAAI,SAAS,EAAE,CAAC;YAC3B,IAAI,CAAC;gBACH,MAAM,WAAW,CAAC;oBAChB,KAAK,EAAE;wBACL,KAAK,EAAE,UAAU;wBACjB,UAAU,EAAE,SAAS;wBACrB,UAAU,EAAE,SAAS;wBACrB,QAAQ,EAAE,OAAO;wBACjB,WAAW,EAAE,KAAK,CAAC,UAAU;wBAC7B,QAAQ,EAAE,KAAK,CAAC,QAAQ;wBACxB,IAAI,EAAE,mBAAmB;qBAC1B;oBACD,MAAM,EAAE,IAAI;oBACZ,OAAO,EAAE,IAAI;iBACd,CAAC,CAAA;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,IAAI,QAAQ,uCAAuC,EAAE,GAAG,CAAC,CAAA;YACzE,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,EAAE,EAAE,IAAI;QACR,OAAO;QACP,KAAK;QACL,SAAS;QACT,KAAK;QACL,KAAK;QACL,WAAW;QACX,oBAAoB;QACpB,mBAAmB;QACnB,QAAQ;KACT,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,aAAqB;IACvD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAI,EAAE,CAAA;IACtD,MAAM,IAAI,GAAG,MAAM,IAAI,aAAa,CAAA;IACpC,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;AAChC,CAAC"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @rovela/sdk/checkout/server/paypal
3
+ *
4
+ * PayPal (Orders v2) — hand-rolled fetch client, no PayPal npm SDK (the
5
+ * Colissimo/GSC house pattern: 4 REST endpoints don't justify a
6
+ * dependency). Server-side ONLY — PayPal's own guidance: never call the
7
+ * Orders API from the browser.
8
+ *
9
+ * Flow: create order server-priced (invoice_id = our up-front reference,
10
+ * PayPal-Request-Id idempotency) → buyer approves (Smart Buttons popup, or
11
+ * the approve-link redirect fallback) → server capture → the shared
12
+ * confirm-order helper flips the pending order to paid. The webhook is the
13
+ * async BACKSTOP (buyer closed the tab between approval and capture),
14
+ * never the primary path.
15
+ *
16
+ * SECURITY INVARIANTS (do not weaken):
17
+ * - client_secret never crosses a client boundary (server-only
18
+ * store_settings column, stripped from the admin settings GET; the
19
+ * public clientId is the only client-visible credential).
20
+ * - Amounts always come from buildQuote via the DB order — the client
21
+ * only ever passes the PayPal order id.
22
+ * - Webhook events are only trusted after verify-webhook-signature.
23
+ */
24
+ declare const PAYPAL_HOSTS: {
25
+ readonly production: "https://api-m.paypal.com";
26
+ readonly sandbox: "https://api-m.sandbox.paypal.com";
27
+ };
28
+ export type PaypalEnvironment = keyof typeof PAYPAL_HOSTS;
29
+ /**
30
+ * Normalize a stored environment value. Accepts BOTH historical vocabularies
31
+ * ('live' from the retired integrations-registry entry, 'production' from the
32
+ * retired feature card) — never migrated by sweep, healed at read time.
33
+ */
34
+ export declare function normalizePaypalEnvironment(value: string | null | undefined): PaypalEnvironment;
35
+ export interface PaypalCredentials {
36
+ clientId: string;
37
+ clientSecret: string;
38
+ environment: PaypalEnvironment;
39
+ webhookId: string | null;
40
+ }
41
+ /**
42
+ * Resolve usable credentials from the store settings row, or null when the
43
+ * provider isn't fully configured + enabled. The ONE availability gate.
44
+ */
45
+ export declare function resolvePaypalCredentials(settings: {
46
+ paypalClientId?: string | null;
47
+ paypalClientSecret?: string | null;
48
+ paypalEnvironment?: string | null;
49
+ paypalWebhookId?: string | null;
50
+ paypalEnabled?: boolean | null;
51
+ } | null): PaypalCredentials | null;
52
+ export declare class PaypalApiError extends Error {
53
+ status: number;
54
+ /** PayPal issue code when present (e.g. ORDER_ALREADY_CAPTURED). */
55
+ issue: string | null;
56
+ body: unknown;
57
+ constructor(message: string, status: number, body: unknown);
58
+ }
59
+ /**
60
+ * Credential probe used by the admin Payments panel's validate-before-persist
61
+ * (an OAuth token grant is the cheapest authenticated call). Returns an error
62
+ * message or null when the credentials are accepted.
63
+ */
64
+ export declare function validatePaypalCredentials(creds: {
65
+ clientId: string;
66
+ clientSecret: string;
67
+ environment: PaypalEnvironment;
68
+ }): Promise<string | null>;
69
+ export interface CreatePaypalOrderInput {
70
+ credentials: PaypalCredentials;
71
+ /** Our order reference (paypal_{uuid}) — round-trips via invoice_id. */
72
+ reference: string;
73
+ totalCents: number;
74
+ /** Uppercase ISO currency. */
75
+ currency: string;
76
+ storeName: string;
77
+ returnUrl: string;
78
+ cancelUrl: string;
79
+ /** Checkout-collected shipping address; null for pickup (NO_SHIPPING). */
80
+ shippingAddress?: {
81
+ firstName?: string;
82
+ lastName?: string;
83
+ line1?: string;
84
+ line2?: string | null;
85
+ city?: string;
86
+ state?: string | null;
87
+ postalCode?: string | null;
88
+ country?: string;
89
+ } | null;
90
+ locale?: string | null;
91
+ }
92
+ export interface CreatePaypalOrderResult {
93
+ paypalOrderId: string;
94
+ approveUrl: string | null;
95
+ }
96
+ export declare function createPaypalOrder(input: CreatePaypalOrderInput): Promise<CreatePaypalOrderResult>;
97
+ export interface PaypalCaptureResult {
98
+ /** 'COMPLETED' = money captured. Anything else = NOT paid (webhook may confirm later). */
99
+ status: string;
100
+ captureId: string | null;
101
+ /** Our order reference (invoice_id round trip). */
102
+ invoiceId: string | null;
103
+ amountCents: number | null;
104
+ currency: string | null;
105
+ payerEmail: string | null;
106
+ }
107
+ /**
108
+ * Capture an approved PayPal order. Idempotent: a repeat capture (retry, or
109
+ * the return-leg racing the buttons leg) resolves via ORDER_ALREADY_CAPTURED
110
+ * → the order details are fetched and returned as a success.
111
+ */
112
+ export declare function capturePaypalOrder(credentials: PaypalCredentials, paypalOrderId: string): Promise<PaypalCaptureResult>;
113
+ export interface PaypalWebhookHeaders {
114
+ transmissionId: string | null;
115
+ transmissionTime: string | null;
116
+ certUrl: string | null;
117
+ authAlgo: string | null;
118
+ transmissionSig: string | null;
119
+ }
120
+ export declare function readPaypalWebhookHeaders(headers: Headers): PaypalWebhookHeaders;
121
+ /**
122
+ * Verify a webhook delivery via PayPal's verify-webhook-signature endpoint
123
+ * (requires the merchant's webhook_id — configured in the Payments panel).
124
+ */
125
+ export declare function verifyPaypalWebhook(credentials: PaypalCredentials, webhookId: string, headers: PaypalWebhookHeaders, event: unknown): Promise<boolean>;
126
+ export {};
127
+ //# sourceMappingURL=paypal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paypal.d.ts","sourceRoot":"","sources":["../../../src/checkout/server/paypal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,274 @@
1
+ /**
2
+ * @rovela/sdk/checkout/server/paypal
3
+ *
4
+ * PayPal (Orders v2) — hand-rolled fetch client, no PayPal npm SDK (the
5
+ * Colissimo/GSC house pattern: 4 REST endpoints don't justify a
6
+ * dependency). Server-side ONLY — PayPal's own guidance: never call the
7
+ * Orders API from the browser.
8
+ *
9
+ * Flow: create order server-priced (invoice_id = our up-front reference,
10
+ * PayPal-Request-Id idempotency) → buyer approves (Smart Buttons popup, or
11
+ * the approve-link redirect fallback) → server capture → the shared
12
+ * confirm-order helper flips the pending order to paid. The webhook is the
13
+ * async BACKSTOP (buyer closed the tab between approval and capture),
14
+ * never the primary path.
15
+ *
16
+ * SECURITY INVARIANTS (do not weaken):
17
+ * - client_secret never crosses a client boundary (server-only
18
+ * store_settings column, stripped from the admin settings GET; the
19
+ * public clientId is the only client-visible credential).
20
+ * - Amounts always come from buildQuote via the DB order — the client
21
+ * only ever passes the PayPal order id.
22
+ * - Webhook events are only trusted after verify-webhook-signature.
23
+ */
24
+ // =============================================================================
25
+ // Environments + credentials
26
+ // =============================================================================
27
+ const PAYPAL_HOSTS = {
28
+ production: 'https://api-m.paypal.com',
29
+ sandbox: 'https://api-m.sandbox.paypal.com',
30
+ };
31
+ /**
32
+ * Normalize a stored environment value. Accepts BOTH historical vocabularies
33
+ * ('live' from the retired integrations-registry entry, 'production' from the
34
+ * retired feature card) — never migrated by sweep, healed at read time.
35
+ */
36
+ export function normalizePaypalEnvironment(value) {
37
+ return value === 'live' || value === 'production' ? 'production' : 'sandbox';
38
+ }
39
+ /**
40
+ * Resolve usable credentials from the store settings row, or null when the
41
+ * provider isn't fully configured + enabled. The ONE availability gate.
42
+ */
43
+ export function resolvePaypalCredentials(settings) {
44
+ if (!settings?.paypalEnabled)
45
+ return null;
46
+ const clientId = settings.paypalClientId?.trim();
47
+ const clientSecret = settings.paypalClientSecret?.trim();
48
+ if (!clientId || !clientSecret)
49
+ return null;
50
+ return {
51
+ clientId,
52
+ clientSecret,
53
+ environment: normalizePaypalEnvironment(settings.paypalEnvironment),
54
+ webhookId: settings.paypalWebhookId?.trim() || null,
55
+ };
56
+ }
57
+ // =============================================================================
58
+ // API plumbing
59
+ // =============================================================================
60
+ export class PaypalApiError extends Error {
61
+ status;
62
+ /** PayPal issue code when present (e.g. ORDER_ALREADY_CAPTURED). */
63
+ issue;
64
+ body;
65
+ constructor(message, status, body) {
66
+ super(message);
67
+ this.name = 'PaypalApiError';
68
+ this.status = status;
69
+ this.body = body;
70
+ this.issue = extractIssue(body);
71
+ }
72
+ }
73
+ function extractIssue(body) {
74
+ if (!body || typeof body !== 'object')
75
+ return null;
76
+ const b = body;
77
+ return b.details?.[0]?.issue ?? b.name ?? null;
78
+ }
79
+ /**
80
+ * OAuth token cache — per (environment, clientId), refreshed 60s early.
81
+ * Module-level: serverless instances reuse it across warm invocations.
82
+ */
83
+ const tokenCache = new Map();
84
+ async function getAccessToken(creds) {
85
+ const cacheKey = `${creds.environment}:${creds.clientId}`;
86
+ const cached = tokenCache.get(cacheKey);
87
+ if (cached && cached.expiresAt > Date.now())
88
+ return cached.token;
89
+ const res = await fetch(`${PAYPAL_HOSTS[creds.environment]}/v1/oauth2/token`, {
90
+ method: 'POST',
91
+ headers: {
92
+ Authorization: `Basic ${Buffer.from(`${creds.clientId}:${creds.clientSecret}`).toString('base64')}`,
93
+ 'Content-Type': 'application/x-www-form-urlencoded',
94
+ },
95
+ body: 'grant_type=client_credentials',
96
+ });
97
+ const data = (await res.json().catch(() => null));
98
+ if (!res.ok || !data?.access_token) {
99
+ throw new PaypalApiError('PayPal authentication failed', res.status, data);
100
+ }
101
+ tokenCache.set(cacheKey, {
102
+ token: data.access_token,
103
+ expiresAt: Date.now() + Math.max(0, (data.expires_in ?? 0) - 60) * 1000,
104
+ });
105
+ return data.access_token;
106
+ }
107
+ async function paypalRequest(creds, method, path, body, headers) {
108
+ const token = await getAccessToken(creds);
109
+ const res = await fetch(`${PAYPAL_HOSTS[creds.environment]}${path}`, {
110
+ method,
111
+ headers: {
112
+ Authorization: `Bearer ${token}`,
113
+ 'Content-Type': 'application/json',
114
+ ...headers,
115
+ },
116
+ body: body === undefined ? undefined : JSON.stringify(body),
117
+ });
118
+ const data = (await res.json().catch(() => null));
119
+ if (!res.ok) {
120
+ throw new PaypalApiError(`PayPal ${method} ${path} failed (${res.status})`, res.status, data);
121
+ }
122
+ return data;
123
+ }
124
+ /**
125
+ * Credential probe used by the admin Payments panel's validate-before-persist
126
+ * (an OAuth token grant is the cheapest authenticated call). Returns an error
127
+ * message or null when the credentials are accepted.
128
+ */
129
+ export async function validatePaypalCredentials(creds) {
130
+ try {
131
+ const res = await fetch(`${PAYPAL_HOSTS[creds.environment]}/v1/oauth2/token`, {
132
+ method: 'POST',
133
+ headers: {
134
+ Authorization: `Basic ${Buffer.from(`${creds.clientId}:${creds.clientSecret}`).toString('base64')}`,
135
+ 'Content-Type': 'application/x-www-form-urlencoded',
136
+ },
137
+ body: 'grant_type=client_credentials',
138
+ });
139
+ if (res.ok)
140
+ return null;
141
+ if (res.status === 401 || res.status === 400) {
142
+ return 'PayPal rejected these credentials. Check the Client ID, secret and environment.';
143
+ }
144
+ return `PayPal returned an unexpected error (${res.status}). Try again shortly.`;
145
+ }
146
+ catch {
147
+ return 'Could not reach PayPal to verify the credentials. Try again shortly.';
148
+ }
149
+ }
150
+ export async function createPaypalOrder(input) {
151
+ const value = (input.totalCents / 100).toFixed(2);
152
+ const address = input.shippingAddress;
153
+ const hasAddress = !!(address?.line1 && address?.city && address?.country);
154
+ const data = await paypalRequest(input.credentials, 'POST', '/v2/checkout/orders', {
155
+ intent: 'CAPTURE',
156
+ purchase_units: [
157
+ {
158
+ reference_id: 'default',
159
+ invoice_id: input.reference,
160
+ custom_id: input.reference,
161
+ amount: { currency_code: input.currency.toUpperCase(), value },
162
+ ...(hasAddress
163
+ ? {
164
+ shipping: {
165
+ name: {
166
+ full_name: `${address.firstName ?? ''} ${address.lastName ?? ''}`.trim() || undefined,
167
+ },
168
+ address: {
169
+ address_line_1: address.line1,
170
+ ...(address.line2 ? { address_line_2: address.line2 } : {}),
171
+ admin_area_2: address.city,
172
+ ...(address.state ? { admin_area_1: address.state } : {}),
173
+ ...(address.postalCode ? { postal_code: address.postalCode } : {}),
174
+ country_code: address.country.toUpperCase(),
175
+ },
176
+ },
177
+ }
178
+ : {}),
179
+ },
180
+ ],
181
+ payment_source: {
182
+ paypal: {
183
+ experience_context: {
184
+ brand_name: input.storeName.slice(0, 127),
185
+ user_action: 'PAY_NOW',
186
+ // No eChecks/pending funding — a capture must settle or fail.
187
+ payment_method_preference: 'IMMEDIATE_PAYMENT_REQUIRED',
188
+ // The buyer cannot swap the address at PayPal — ours is the one
189
+ // the quote taxed and priced shipping for.
190
+ shipping_preference: hasAddress ? 'SET_PROVIDED_ADDRESS' : 'NO_SHIPPING',
191
+ return_url: input.returnUrl,
192
+ cancel_url: input.cancelUrl,
193
+ ...(input.locale === 'fr' ? { locale: 'fr-FR' } : {}),
194
+ },
195
+ },
196
+ },
197
+ }, { 'PayPal-Request-Id': input.reference, Prefer: 'return=representation' });
198
+ if (!data.id) {
199
+ throw new PaypalApiError('PayPal order creation returned no id', 502, data);
200
+ }
201
+ const approve = data.links?.find((l) => l.rel === 'payer-action') ??
202
+ data.links?.find((l) => l.rel === 'approve');
203
+ return { paypalOrderId: data.id, approveUrl: approve?.href ?? null };
204
+ }
205
+ function extractCapture(data) {
206
+ const unit = data.purchase_units?.[0];
207
+ const capture = unit?.payments?.captures?.[0];
208
+ const amountValue = capture?.amount?.value;
209
+ return {
210
+ status: capture?.status ?? data.status ?? 'UNKNOWN',
211
+ captureId: capture?.id ?? null,
212
+ invoiceId: capture?.invoice_id ?? capture?.custom_id ?? unit?.invoice_id ?? unit?.custom_id ?? null,
213
+ amountCents: amountValue != null ? Math.round(parseFloat(amountValue) * 100) : null,
214
+ currency: capture?.amount?.currency_code ?? null,
215
+ payerEmail: data.payer?.email_address ?? null,
216
+ };
217
+ }
218
+ /**
219
+ * Capture an approved PayPal order. Idempotent: a repeat capture (retry, or
220
+ * the return-leg racing the buttons leg) resolves via ORDER_ALREADY_CAPTURED
221
+ * → the order details are fetched and returned as a success.
222
+ */
223
+ export async function capturePaypalOrder(credentials, paypalOrderId) {
224
+ try {
225
+ const data = await paypalRequest(credentials, 'POST', `/v2/checkout/orders/${encodeURIComponent(paypalOrderId)}/capture`, {}, { 'PayPal-Request-Id': `cap-${paypalOrderId}`, Prefer: 'return=representation' });
226
+ return extractCapture(data);
227
+ }
228
+ catch (err) {
229
+ if (err instanceof PaypalApiError && err.issue === 'ORDER_ALREADY_CAPTURED') {
230
+ const data = await paypalRequest(credentials, 'GET', `/v2/checkout/orders/${encodeURIComponent(paypalOrderId)}`);
231
+ return extractCapture(data);
232
+ }
233
+ throw err;
234
+ }
235
+ }
236
+ export function readPaypalWebhookHeaders(headers) {
237
+ return {
238
+ transmissionId: headers.get('paypal-transmission-id'),
239
+ transmissionTime: headers.get('paypal-transmission-time'),
240
+ certUrl: headers.get('paypal-cert-url'),
241
+ authAlgo: headers.get('paypal-auth-algo'),
242
+ transmissionSig: headers.get('paypal-transmission-sig'),
243
+ };
244
+ }
245
+ /**
246
+ * Verify a webhook delivery via PayPal's verify-webhook-signature endpoint
247
+ * (requires the merchant's webhook_id — configured in the Payments panel).
248
+ */
249
+ export async function verifyPaypalWebhook(credentials, webhookId, headers, event) {
250
+ if (!headers.transmissionId ||
251
+ !headers.transmissionTime ||
252
+ !headers.certUrl ||
253
+ !headers.authAlgo ||
254
+ !headers.transmissionSig) {
255
+ return false;
256
+ }
257
+ try {
258
+ const data = await paypalRequest(credentials, 'POST', '/v1/notifications/verify-webhook-signature', {
259
+ transmission_id: headers.transmissionId,
260
+ transmission_time: headers.transmissionTime,
261
+ cert_url: headers.certUrl,
262
+ auth_algo: headers.authAlgo,
263
+ transmission_sig: headers.transmissionSig,
264
+ webhook_id: webhookId,
265
+ webhook_event: event,
266
+ });
267
+ return data.verification_status === 'SUCCESS';
268
+ }
269
+ catch (err) {
270
+ console.error('[PayPal] webhook verification call failed:', err);
271
+ return false;
272
+ }
273
+ }
274
+ //# sourceMappingURL=paypal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paypal.js","sourceRoot":"","sources":["../../../src/checkout/server/paypal.ts"],"names":[],"mappings":"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"}