@alphawebai/hs-prism 0.1.0-rc.39 → 0.1.0-rc.41

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.
@@ -3,4 +3,5 @@ export type { AlphaWebPrismCompatibilityManifest } from './manifest';
3
3
  export { PrismOperationError, normalizePrismError } from './error';
4
4
  export { assertSafeProviderMoney, fromProviderMinorAmount, getCurrencyRule, toProviderMinorAmount, } from './money';
5
5
  export type { CurrencyRule } from './money';
6
+ export { paypalQuotesMatch } from './paypal-approved-order';
6
7
  export type * from './types';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toProviderMinorAmount = exports.getCurrencyRule = exports.fromProviderMinorAmount = exports.assertSafeProviderMoney = exports.normalizePrismError = exports.PrismOperationError = exports.ALPHAWEB_PRISM_COMPATIBILITY_MANIFEST = void 0;
3
+ exports.paypalQuotesMatch = exports.toProviderMinorAmount = exports.getCurrencyRule = exports.fromProviderMinorAmount = exports.assertSafeProviderMoney = exports.normalizePrismError = exports.PrismOperationError = exports.ALPHAWEB_PRISM_COMPATIBILITY_MANIFEST = void 0;
4
4
  var manifest_1 = require("./manifest");
5
5
  Object.defineProperty(exports, "ALPHAWEB_PRISM_COMPATIBILITY_MANIFEST", { enumerable: true, get: function () { return manifest_1.ALPHAWEB_PRISM_COMPATIBILITY_MANIFEST; } });
6
6
  var error_1 = require("./error");
@@ -11,4 +11,6 @@ Object.defineProperty(exports, "assertSafeProviderMoney", { enumerable: true, ge
11
11
  Object.defineProperty(exports, "fromProviderMinorAmount", { enumerable: true, get: function () { return money_1.fromProviderMinorAmount; } });
12
12
  Object.defineProperty(exports, "getCurrencyRule", { enumerable: true, get: function () { return money_1.getCurrencyRule; } });
13
13
  Object.defineProperty(exports, "toProviderMinorAmount", { enumerable: true, get: function () { return money_1.toProviderMinorAmount; } });
14
+ var paypal_approved_order_1 = require("./paypal-approved-order");
15
+ Object.defineProperty(exports, "paypalQuotesMatch", { enumerable: true, get: function () { return paypal_approved_order_1.paypalQuotesMatch; } });
14
16
  //# sourceMappingURL=contracts.js.map
@@ -2,6 +2,9 @@ export interface PrismOperationErrorShape {
2
2
  category: 'configuration' | 'authentication' | 'validation' | 'customer_action' | 'declined' | 'rate_limited' | 'network' | 'provider_server' | 'conflict' | 'unknown';
3
3
  providerCode?: string;
4
4
  providerRequestId?: string;
5
+ providerHttpStatus?: number;
6
+ /** Provider issue identifiers only; never descriptions, field values, or raw responses. */
7
+ providerIssueCodes?: readonly string[];
5
8
  retryable: boolean;
6
9
  outcome: 'definitely_not_applied' | 'applied' | 'ambiguous';
7
10
  safeMessage: string;
@@ -101,6 +101,9 @@ function normalizePrismError(error) {
101
101
  category,
102
102
  ...(code ? { providerCode: code } : {}),
103
103
  ...(requestId ? { providerRequestId: requestId } : {}),
104
+ ...(Number.isInteger(status) && status >= 100 && status <= 599
105
+ ? { providerHttpStatus: status }
106
+ : {}),
104
107
  retryable,
105
108
  outcome,
106
109
  safeMessage,
@@ -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.39";
4
+ readonly release: "0.1.0-rc.41";
5
5
  readonly upstreamCommit: "b07af7d9dac271de729548e74217ddcf895c602b";
6
6
  readonly medusaRange: ">=2.17.2 <2.18.0";
7
7
  readonly stripeApiVersion: "2026-06-24.dahlia";
@@ -34,6 +34,7 @@ export declare const ALPHAWEB_PRISM_COMPATIBILITY_MANIFEST: Readonly<{
34
34
  };
35
35
  readonly paypal: {
36
36
  readonly browserSession: "paypal_checkout";
37
+ readonly approvedOrderCheckout: "v1";
37
38
  readonly oneTimePayment: true;
38
39
  readonly futureUse: true;
39
40
  readonly repeatPayment: true;
@@ -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.39',
52
+ release: '0.1.0-rc.41',
53
53
  upstreamCommit: 'b07af7d9dac271de729548e74217ddcf895c602b',
54
54
  medusaRange: '>=2.17.2 <2.18.0',
55
55
  stripeApiVersion: '2026-06-24.dahlia',
@@ -82,6 +82,7 @@ exports.ALPHAWEB_PRISM_COMPATIBILITY_MANIFEST = Object.freeze({
82
82
  },
83
83
  paypal: {
84
84
  browserSession: 'paypal_checkout',
85
+ approvedOrderCheckout: 'v1',
85
86
  oneTimePayment: true,
86
87
  futureUse: true,
87
88
  repeatPayment: true,
@@ -0,0 +1,49 @@
1
+ import { PrismOperationError } from './error';
2
+ import type { PrismPayPalApprovedOrder, PrismPayPalConnectorOptions, PrismPayPalOrderOwner, PrismPayPalOrderQuote } from './types';
3
+ export declare function approvedOrderError(code: string, outcome?: 'ambiguous' | 'definitely_not_applied'): PrismOperationError;
4
+ export declare function paypalQuotePayload(quote: PrismPayPalOrderQuote): {
5
+ items?: {
6
+ billing_plan?: {
7
+ name: string;
8
+ billing_cycles: {
9
+ tenure_type: string;
10
+ pricing_scheme: {
11
+ price: {
12
+ currency_code: string;
13
+ value: string;
14
+ };
15
+ pricing_model: string;
16
+ };
17
+ frequency: {
18
+ interval_unit: string;
19
+ interval_count: number;
20
+ };
21
+ total_cycles: number;
22
+ sequence: number;
23
+ }[];
24
+ } | undefined;
25
+ tax?: {
26
+ currency_code: string;
27
+ value: string;
28
+ } | undefined;
29
+ category?: "DIGITAL_GOODS" | "PHYSICAL_GOODS" | "DONATION" | undefined;
30
+ sku?: string | undefined;
31
+ name: string;
32
+ quantity: string;
33
+ unit_amount: {
34
+ currency_code: string;
35
+ value: string;
36
+ };
37
+ }[] | undefined;
38
+ amount: {
39
+ breakdown?: Record<string, {
40
+ currency_code: string;
41
+ value: string;
42
+ }> | undefined;
43
+ currency_code: string;
44
+ value: string;
45
+ };
46
+ };
47
+ export declare function paypalQuotesMatch(left: PrismPayPalOrderQuote, right: PrismPayPalOrderQuote): boolean;
48
+ export declare function assertRecurringDisclosurePreserved(previous: PrismPayPalOrderQuote, next: PrismPayPalOrderQuote): void;
49
+ export declare function readApprovedOrder(payload: unknown, owner: PrismPayPalOrderOwner, options: Readonly<PrismPayPalConnectorOptions>): PrismPayPalApprovedOrder;
@@ -0,0 +1,326 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.approvedOrderError = approvedOrderError;
4
+ exports.paypalQuotePayload = paypalQuotePayload;
5
+ exports.paypalQuotesMatch = paypalQuotesMatch;
6
+ exports.assertRecurringDisclosurePreserved = assertRecurringDisclosurePreserved;
7
+ exports.readApprovedOrder = readApprovedOrder;
8
+ const node_util_1 = require("node:util");
9
+ const error_1 = require("./error");
10
+ const money_1 = require("./money");
11
+ const breakdownFields = {
12
+ itemTotalMinor: 'item_total',
13
+ taxTotalMinor: 'tax_total',
14
+ shippingMinor: 'shipping',
15
+ handlingMinor: 'handling',
16
+ insuranceMinor: 'insurance',
17
+ shippingDiscountMinor: 'shipping_discount',
18
+ discountMinor: 'discount',
19
+ };
20
+ function record(value) {
21
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
22
+ ? value
23
+ : {};
24
+ }
25
+ function text(value) {
26
+ return typeof value === 'string' ? value : '';
27
+ }
28
+ function approvedOrderError(code, outcome = 'ambiguous') {
29
+ return new error_1.PrismOperationError({
30
+ category: 'conflict',
31
+ providerCode: code,
32
+ retryable: false,
33
+ outcome,
34
+ safeMessage: 'The PayPal order must be verified before payment can continue.',
35
+ });
36
+ }
37
+ function moneyFrom(value, currency) {
38
+ const amount = record(value);
39
+ const code = text(amount.currency_code).toLowerCase();
40
+ if (!code || (currency && currency !== code) || typeof amount.value !== 'string') {
41
+ throw approvedOrderError('PRISM_PAYPAL_ORDER_MONEY');
42
+ }
43
+ return {
44
+ currency: code,
45
+ amountMinor: (0, money_1.toProviderMinorAmount)({ medusaAmount: amount.value, currency: code }).toString(),
46
+ };
47
+ }
48
+ function providerMoney(amountMinor, currency) {
49
+ return {
50
+ currency_code: currency.toUpperCase(),
51
+ value: (0, money_1.fromProviderMinorAmount)({ amountMinor, currency }),
52
+ };
53
+ }
54
+ function minor(value, currency) {
55
+ (0, money_1.fromProviderMinorAmount)({ amountMinor: value, currency });
56
+ return BigInt(value);
57
+ }
58
+ function recurringPlan(agreement, currency) {
59
+ const limits = { day: 365, week: 52, month: 12, year: 1 };
60
+ const money = (0, money_1.assertSafeProviderMoney)(agreement.recurringMoney);
61
+ if (money.currency !== currency ||
62
+ !agreement.planName.trim() ||
63
+ agreement.planName.length > 127 ||
64
+ !Number.isSafeInteger(agreement.intervalCount) ||
65
+ agreement.intervalCount < 1 ||
66
+ !(agreement.intervalUnit in limits) ||
67
+ agreement.intervalCount > limits[agreement.intervalUnit] ||
68
+ (agreement.totalPayments !== null &&
69
+ (!Number.isSafeInteger(agreement.totalPayments) ||
70
+ agreement.totalPayments < 2 ||
71
+ agreement.totalPayments > 999))) {
72
+ throw approvedOrderError('PRISM_PAYPAL_RECURRING_AGREEMENT', 'definitely_not_applied');
73
+ }
74
+ return {
75
+ name: agreement.planName,
76
+ billing_cycles: [
77
+ {
78
+ tenure_type: 'REGULAR',
79
+ pricing_scheme: {
80
+ price: providerMoney(agreement.recurringMoney.amountMinor, currency),
81
+ pricing_model: 'FIXED',
82
+ },
83
+ frequency: {
84
+ interval_unit: agreement.intervalUnit.toUpperCase(),
85
+ interval_count: agreement.intervalCount,
86
+ },
87
+ total_cycles: agreement.totalPayments ?? 0,
88
+ sequence: 1,
89
+ },
90
+ ],
91
+ };
92
+ }
93
+ function recurringFrom(value, currency) {
94
+ const plan = record(value);
95
+ const cycles = Array.isArray(plan.billing_cycles) ? plan.billing_cycles : [];
96
+ const cycle = record(cycles[0]);
97
+ const frequency = record(cycle.frequency);
98
+ const pricing = record(cycle.pricing_scheme);
99
+ const unit = text(frequency.interval_unit).toLowerCase();
100
+ if (cycles.length !== 1 ||
101
+ cycle.tenure_type !== 'REGULAR' ||
102
+ cycle.sequence !== 1 ||
103
+ pricing.pricing_model !== 'FIXED' ||
104
+ (unit !== 'day' && unit !== 'week' && unit !== 'month' && unit !== 'year')) {
105
+ throw approvedOrderError('PRISM_PAYPAL_RECURRING_AGREEMENT');
106
+ }
107
+ const agreement = {
108
+ planName: text(plan.name),
109
+ intervalUnit: unit,
110
+ intervalCount: Number(frequency.interval_count),
111
+ totalPayments: cycle.total_cycles === 0 ? null : Number(cycle.total_cycles),
112
+ recurringMoney: moneyFrom(pricing.price, currency),
113
+ };
114
+ recurringPlan(agreement, currency);
115
+ return agreement;
116
+ }
117
+ function buildQuotePayload(quote) {
118
+ const currency = (0, money_1.assertSafeProviderMoney)(quote.money).currency;
119
+ const breakdown = {};
120
+ let total = 0n;
121
+ for (const [key, field] of Object.entries(breakdownFields)) {
122
+ const value = quote.breakdown?.[key];
123
+ if (value === undefined)
124
+ continue;
125
+ const amount = minor(value, currency);
126
+ total += field === 'discount' || field === 'shipping_discount' ? -amount : amount;
127
+ breakdown[field] = providerMoney(value, currency);
128
+ }
129
+ if (quote.breakdown && total !== BigInt(quote.money.amountMinor)) {
130
+ throw approvedOrderError('PRISM_PAYPAL_QUOTE_TOTAL', 'definitely_not_applied');
131
+ }
132
+ let itemTotal = 0n;
133
+ let taxTotal = 0n;
134
+ const items = quote.items?.map((item) => {
135
+ if (!item.name.trim() ||
136
+ item.name.length > 127 ||
137
+ (item.sku !== undefined && (!item.sku || item.sku.length > 127)) ||
138
+ (item.category !== undefined &&
139
+ !['DIGITAL_GOODS', 'PHYSICAL_GOODS', 'DONATION'].includes(item.category)) ||
140
+ !Number.isSafeInteger(item.quantity) ||
141
+ item.quantity < 1) {
142
+ throw approvedOrderError('PRISM_PAYPAL_QUOTE_ITEM', 'definitely_not_applied');
143
+ }
144
+ itemTotal += minor(item.unitAmountMinor, currency) * BigInt(item.quantity);
145
+ taxTotal += minor(item.taxMinor ?? '0', currency) * BigInt(item.quantity);
146
+ return {
147
+ name: item.name,
148
+ quantity: String(item.quantity),
149
+ unit_amount: providerMoney(item.unitAmountMinor, currency),
150
+ ...(item.sku ? { sku: item.sku } : {}),
151
+ ...(item.category ? { category: item.category } : {}),
152
+ ...(item.taxMinor === undefined ? {} : { tax: providerMoney(item.taxMinor, currency) }),
153
+ ...(item.recurringAgreement
154
+ ? { billing_plan: recurringPlan(item.recurringAgreement, currency) }
155
+ : {}),
156
+ };
157
+ });
158
+ if (items &&
159
+ (!items.length ||
160
+ quote.breakdown?.itemTotalMinor === undefined ||
161
+ itemTotal !== BigInt(quote.breakdown.itemTotalMinor) ||
162
+ (quote.items?.some((item) => item.taxMinor !== undefined) &&
163
+ (quote.breakdown.taxTotalMinor === undefined ||
164
+ taxTotal !== BigInt(quote.breakdown.taxTotalMinor))))) {
165
+ throw approvedOrderError('PRISM_PAYPAL_QUOTE_ITEMS_TOTAL', 'definitely_not_applied');
166
+ }
167
+ return {
168
+ amount: {
169
+ ...providerMoney(quote.money.amountMinor, currency),
170
+ ...(quote.breakdown ? { breakdown } : {}),
171
+ },
172
+ ...(items ? { items } : {}),
173
+ };
174
+ }
175
+ function paypalQuotePayload(quote) {
176
+ try {
177
+ return buildQuotePayload(quote);
178
+ }
179
+ catch (error) {
180
+ if (error instanceof error_1.PrismOperationError)
181
+ throw error;
182
+ throw approvedOrderError('PRISM_PAYPAL_QUOTE_INVALID', 'definitely_not_applied');
183
+ }
184
+ }
185
+ function paypalQuotesMatch(left, right) {
186
+ return (0, node_util_1.isDeepStrictEqual)(paypalQuotePayload(left), paypalQuotePayload(right));
187
+ }
188
+ function assertRecurringDisclosurePreserved(previous, next) {
189
+ const agreements = (quote) => quote.items
190
+ ?.filter((item) => item.recurringAgreement)
191
+ .map((item) => ({ quantity: item.quantity, agreement: item.recurringAgreement })) ?? [];
192
+ if (!(0, node_util_1.isDeepStrictEqual)(agreements(previous), agreements(next))) {
193
+ throw approvedOrderError('PRISM_PAYPAL_RECURRING_AGREEMENT_CHANGED', 'definitely_not_applied');
194
+ }
195
+ }
196
+ function readApprovedOrder(payload, owner, options) {
197
+ const order = record(payload);
198
+ const units = Array.isArray(order.purchase_units) ? order.purchase_units : [];
199
+ const unit = record(units[0]);
200
+ const payee = record(unit.payee);
201
+ if (owner.credentialBindingId !== options.credentialBindingId ||
202
+ owner.providerAccountId !== options.providerAccountId ||
203
+ owner.livemode !== (options.environment === 'live') ||
204
+ order.id !== owner.connectorPaymentId ||
205
+ order.intent !== 'CAPTURE' ||
206
+ units.length !== 1 ||
207
+ unit.reference_id !== owner.rootOperationId ||
208
+ unit.invoice_id !== owner.rootOperationId ||
209
+ (owner.expectedCustomId !== undefined && unit.custom_id !== owner.expectedCustomId) ||
210
+ (options.payerId !== undefined && payee.merchant_id !== options.payerId)) {
211
+ throw approvedOrderError('PRISM_PAYPAL_ORDER_OWNER');
212
+ }
213
+ const money = moneyFrom(unit.amount);
214
+ const suppliedBreakdown = record(unit.amount).breakdown;
215
+ const rawBreakdown = record(suppliedBreakdown);
216
+ if ((suppliedBreakdown !== undefined &&
217
+ (suppliedBreakdown === null ||
218
+ typeof suppliedBreakdown !== 'object' ||
219
+ Array.isArray(suppliedBreakdown))) ||
220
+ Object.keys(rawBreakdown).some((field) => !Object.values(breakdownFields).some((known) => known === field)) ||
221
+ (unit.items !== undefined && !Array.isArray(unit.items))) {
222
+ throw approvedOrderError('PRISM_PAYPAL_ORDER_RESPONSE');
223
+ }
224
+ const breakdown = {};
225
+ for (const [key, field] of Object.entries(breakdownFields)) {
226
+ if (rawBreakdown[field] !== undefined)
227
+ breakdown[key] = moneyFrom(rawBreakdown[field], money.currency).amountMinor;
228
+ }
229
+ const items = Array.isArray(unit.items)
230
+ ? unit.items.map((value) => {
231
+ const item = record(value);
232
+ return {
233
+ name: text(item.name),
234
+ quantity: Number(item.quantity),
235
+ unitAmountMinor: moneyFrom(item.unit_amount, money.currency).amountMinor,
236
+ ...(item.sku === undefined ? {} : { sku: text(item.sku) }),
237
+ ...(item.category === undefined
238
+ ? {}
239
+ : { category: text(item.category) }),
240
+ ...(item.tax === undefined
241
+ ? {}
242
+ : { taxMinor: moneyFrom(item.tax, money.currency).amountMinor }),
243
+ ...(item.billing_plan === undefined
244
+ ? {}
245
+ : { recurringAgreement: recurringFrom(item.billing_plan, money.currency) }),
246
+ };
247
+ })
248
+ : undefined;
249
+ const quote = {
250
+ money,
251
+ ...(Object.keys(rawBreakdown).length ? { breakdown } : {}),
252
+ ...(items ? { items } : {}),
253
+ };
254
+ paypalQuotePayload(quote);
255
+ const payments = record(unit.payments);
256
+ if ((unit.payments !== undefined &&
257
+ (unit.payments === null ||
258
+ typeof unit.payments !== 'object' ||
259
+ Array.isArray(unit.payments))) ||
260
+ (payments.captures !== undefined && !Array.isArray(payments.captures)) ||
261
+ (payments.authorizations !== undefined && !Array.isArray(payments.authorizations))) {
262
+ throw approvedOrderError('PRISM_PAYPAL_ORDER_RESPONSE');
263
+ }
264
+ const captures = Array.isArray(payments.captures) ? payments.captures : [];
265
+ const authorizations = Array.isArray(payments.authorizations) ? payments.authorizations : [];
266
+ if (captures.length > 1)
267
+ throw approvedOrderError('PRISM_PAYPAL_MULTIPLE_CAPTURES');
268
+ const statuses = [
269
+ 'CREATED',
270
+ 'SAVED',
271
+ 'PAYER_ACTION_REQUIRED',
272
+ 'APPROVED',
273
+ 'VOIDED',
274
+ 'COMPLETED',
275
+ ];
276
+ const orderStatus = statuses.find((status) => status === order.status) ?? 'UNKNOWN';
277
+ let capture;
278
+ if (captures.length) {
279
+ const data = record(captures[0]);
280
+ const captureMoney = moneyFrom(data.amount, money.currency);
281
+ if (!text(data.id) ||
282
+ (data.invoice_id !== undefined && data.invoice_id !== owner.rootOperationId) ||
283
+ captureMoney.amountMinor !== money.amountMinor) {
284
+ throw approvedOrderError('PRISM_PAYPAL_CAPTURE_MISMATCH');
285
+ }
286
+ const captureStatuses = {
287
+ COMPLETED: 'captured',
288
+ PENDING: 'pending',
289
+ DECLINED: 'failed',
290
+ FAILED: 'failed',
291
+ DENIED: 'failed',
292
+ REFUNDED: 'refunded',
293
+ PARTIALLY_REFUNDED: 'partially_refunded',
294
+ };
295
+ capture = {
296
+ connectorCaptureId: text(data.id),
297
+ money: captureMoney,
298
+ status: captureStatuses[data.status] ?? 'unknown',
299
+ };
300
+ }
301
+ return {
302
+ connectorPaymentId: owner.connectorPaymentId,
303
+ rootOperationId: owner.rootOperationId,
304
+ quote,
305
+ orderStatus,
306
+ hasAuthorization: authorizations.length > 0,
307
+ hasCapture: captures.length > 0,
308
+ ...(capture
309
+ ? {
310
+ capture,
311
+ connectorMetadata: JSON.stringify({
312
+ authorize_id: null,
313
+ capture_id: capture.connectorCaptureId,
314
+ incremental_authorization_id: null,
315
+ psync_flow: 'CAPTURE',
316
+ next_action: null,
317
+ order_id: null,
318
+ }),
319
+ }
320
+ : {}),
321
+ ...(typeof order.update_time === 'string' && Number.isFinite(Date.parse(order.update_time))
322
+ ? { providerUpdatedAt: order.update_time }
323
+ : {}),
324
+ };
325
+ }
326
+ //# sourceMappingURL=paypal-approved-order.js.map
@@ -1,5 +1,5 @@
1
1
  import { types } from '../payments/generated/proto';
2
- import type { PrismAuthorizeApprovedPaymentInput, PrismCancelInput, PrismCaptureInput, PrismCreateWebhookEndpointInput, PrismDeleteWebhookEndpointInput, PrismDisputeResult, PrismEstablishReusableReferenceInput, PrismFindPaymentInput, PrismFindRefundInput, PrismMerchantOperation, PrismPayPalConnectorOptions, PrismPayPalConnectorV1, PrismPayPalOrderLifecycle, PrismPayPalRuntimeVerification, PrismPayPalRuntimeVerificationInput, PrismPaymentResult, PrismRefundInput, PrismRefundResult, PrismRetrieveDisputeInput, PrismRetrieveOrderLifecycleInput, PrismRetrievePaymentInput, PrismRetrieveRefundInput, PrismRetrieveWebhookEndpointInput, PrismRetryPaymentInput, PrismReusableReferenceDerivation, PrismUpdateWebhookEndpointInput, PrismWebhookEndpoint, PrismWebhookEndpointCreated, PrismWebhookParseInput, PrismWebhookParseResult } from './types';
2
+ import type { PrismAuthorizeApprovedPaymentInput, PrismCancelInput, PrismCaptureInput, PrismCreateWebhookEndpointInput, PrismDeleteWebhookEndpointInput, PrismDisputeResult, PrismEstablishReusableReferenceInput, PrismFindPaymentInput, PrismFindRefundInput, PrismMerchantOperation, PrismPayPalConnectorOptions, PrismPayPalConnectorV1, PrismPayPalApprovedOrder, PrismPayPalCaptureApprovedOrderInput, PrismPayPalOrderOwner, PrismPayPalUpdateApprovedOrderInput, PrismPayPalOrderLifecycle, PrismPayPalRuntimeVerification, PrismPayPalRuntimeVerificationInput, PrismPaymentResult, PrismRefundInput, PrismRefundResult, PrismRetrieveDisputeInput, PrismRetrieveOrderLifecycleInput, PrismRetrievePaymentInput, PrismRetrieveRefundInput, PrismRetrieveWebhookEndpointInput, PrismRetryPaymentInput, PrismReusableReferenceDerivation, PrismUpdateWebhookEndpointInput, PrismWebhookEndpoint, PrismWebhookEndpointCreated, PrismWebhookParseInput, PrismWebhookParseResult } from './types';
3
3
  interface MerchantAuthenticationPort {
4
4
  createServerAuthenticationToken(input: types.IMerchantAuthenticationServiceCreateServerAuthenticationTokenRequest): Promise<types.MerchantAuthenticationServiceCreateServerAuthenticationTokenResponse>;
5
5
  }
@@ -32,9 +32,16 @@ export declare class PrismPayPalConnector implements PrismPayPalConnectorV1 {
32
32
  private validateOptions;
33
33
  private assertOperation;
34
34
  private connectorState;
35
+ private paypalResponse;
35
36
  private paypalRequest;
36
37
  private retrievePayPalVaultState;
37
38
  createPayment(input: PrismMerchantOperation): Promise<PrismPaymentResult>;
39
+ createAdjustableOrder(input: PrismMerchantOperation): Promise<PrismPaymentResult>;
40
+ private createPayPalOrder;
41
+ private assertOrderOwner;
42
+ retrieveApprovedOrder(input: PrismPayPalOrderOwner): Promise<PrismPayPalApprovedOrder>;
43
+ updateApprovedOrder(input: PrismPayPalUpdateApprovedOrderInput): Promise<PrismPayPalApprovedOrder>;
44
+ captureApprovedOrder(input: PrismPayPalCaptureApprovedOrderInput): Promise<PrismPayPalApprovedOrder>;
38
45
  authorizeApprovedPayment(input: PrismAuthorizeApprovedPaymentInput): Promise<PrismPaymentResult>;
39
46
  repeatPayment(input: PrismMerchantOperation): Promise<PrismPaymentResult>;
40
47
  retryPayment(input: PrismRetryPaymentInput): Promise<PrismPaymentResult>;
@@ -8,6 +8,14 @@ const proto_1 = require("../payments/generated/proto");
8
8
  const error_1 = require("./error");
9
9
  const manifest_1 = require("./manifest");
10
10
  const money_1 = require("./money");
11
+ const paypal_approved_order_1 = require("./paypal-approved-order");
12
+ function safeProviderIdentifier(value) {
13
+ if (typeof value !== 'string' || value.length === 0 || value.length > 127)
14
+ return undefined;
15
+ return [...value].every((character) => (character >= 'a' && character <= 'z') ||
16
+ (character >= 'A' && character <= 'Z') ||
17
+ (character >= '0' && character <= '9') || character === '_' || character === '-') ? value : undefined;
18
+ }
11
19
  const PAYPAL_API_BASE = {
12
20
  test: 'https://api-m.sandbox.paypal.com',
13
21
  live: 'https://api-m.paypal.com',
@@ -253,11 +261,13 @@ function connectorMetadataFrom(response) {
253
261
  }
254
262
  function failureFrom(response) {
255
263
  const error = recordOf(recordOf(response).error);
256
- const code = stringOf(error.code);
264
+ const code = safeProviderIdentifier(recordOf(error.connectorDetails).code) ??
265
+ safeProviderIdentifier(error.code);
257
266
  if (!code && Object.keys(error).length === 0)
258
267
  return undefined;
259
268
  return {
260
- category: 'unknown',
269
+ category: code === 'INSTRUMENT_DECLINED' ? 'declined' :
270
+ code === 'PAYER_ACTION_REQUIRED' ? 'customer_action' : 'unknown',
261
271
  ...(code ? { providerCode: code } : {}),
262
272
  retryable: false,
263
273
  };
@@ -363,6 +373,10 @@ function paymentResult(response, money, options, fallbackConnectorPaymentId, app
363
373
  const connectorPaymentId = connectorPaymentIdFrom(response) ?? fallbackConnectorPaymentId ?? null;
364
374
  const connectorMetadata = connectorMetadataFrom(response);
365
375
  const failure = failureFrom(response);
376
+ const responseData = recordOf(response);
377
+ const providerRequestId = safeProviderIdentifier(Object.entries(recordOf(responseData.responseHeaders))
378
+ .find(([key]) => key.toLowerCase() === 'paypal-debug-id')?.[1]);
379
+ const providerHttpStatus = Number(responseData.statusCode);
366
380
  const providerReference = connectorPaymentId
367
381
  ? reusableReferenceFrom(response, options, connectorPaymentId)
368
382
  : undefined;
@@ -376,6 +390,9 @@ function paymentResult(response, money, options, fallbackConnectorPaymentId, app
376
390
  ...(connectorMetadata ? { connectorMetadata } : {}),
377
391
  ...(reusableReference ? { reusableReference } : {}),
378
392
  ...(failure ? { failure } : {}),
393
+ ...(providerRequestId ? { providerRequestId } : {}),
394
+ ...(Number.isInteger(providerHttpStatus) && providerHttpStatus >= 100 && providerHttpStatus <= 599
395
+ ? { providerHttpStatus } : {}),
379
396
  };
380
397
  }
381
398
  function refundStatus(status) {
@@ -757,7 +774,7 @@ class PrismPayPalConnector {
757
774
  },
758
775
  };
759
776
  }
760
- async paypalRequest(method, path, body, idempotencyKey) {
777
+ async paypalResponse(method, path, body, idempotencyKey) {
761
778
  const state = await this.connectorState();
762
779
  const accessToken = readSecret(state.accessToken?.token);
763
780
  if (!accessToken) {
@@ -776,7 +793,7 @@ class PrismPayPalConnector {
776
793
  ...(body === undefined ? {} : { body: JSON.stringify(body) }),
777
794
  signal: controller.signal,
778
795
  });
779
- const requestId = response.headers.get('paypal-debug-id') ?? undefined;
796
+ const requestId = safeProviderIdentifier(response.headers.get('paypal-debug-id'));
780
797
  const text = await response.text();
781
798
  let payload = null;
782
799
  if (text.length > 0) {
@@ -789,22 +806,37 @@ class PrismPayPalConnector {
789
806
  }
790
807
  if (!response.ok) {
791
808
  const data = recordOf(payload);
809
+ const issues = (Array.isArray(data.details) ? data.details : [])
810
+ .map((detail) => safeProviderIdentifier(recordOf(detail).issue))
811
+ .filter((issue) => issue !== undefined)
812
+ .slice(0, 10);
813
+ const providerCode = safeProviderIdentifier(data.name);
814
+ const providerRequestId = requestId ?? safeProviderIdentifier(data.debug_id);
792
815
  throw new error_1.PrismOperationError({
793
816
  category: response.status === 401 || response.status === 403
794
817
  ? 'authentication'
795
- : response.status === 429
796
- ? 'rate_limited'
797
- : response.status >= 500
798
- ? 'provider_server'
799
- : 'validation',
800
- ...(stringOf(data.name) ? { providerCode: stringOf(data.name) } : {}),
801
- ...(requestId ? { providerRequestId: requestId } : {}),
802
- retryable: response.status === 429 || response.status >= 500,
803
- outcome: response.status >= 500 ? 'ambiguous' : 'definitely_not_applied',
818
+ : response.status === 409
819
+ ? 'conflict'
820
+ : response.status === 429
821
+ ? 'rate_limited'
822
+ : response.status >= 500
823
+ ? 'provider_server'
824
+ : 'validation',
825
+ ...(providerCode ? { providerCode } : {}),
826
+ ...(providerRequestId ? { providerRequestId } : {}),
827
+ providerHttpStatus: response.status,
828
+ ...(issues.length ? { providerIssueCodes: [...new Set(issues)] } : {}),
829
+ retryable: response.status === 409 || response.status === 429 || response.status >= 500,
830
+ outcome: response.status === 409 || response.status >= 500
831
+ ? 'ambiguous' : 'definitely_not_applied',
804
832
  safeMessage: 'PayPal rejected the payment operation.',
805
833
  });
806
834
  }
807
- return payload;
835
+ return {
836
+ payload,
837
+ providerHttpStatus: response.status,
838
+ ...(requestId ? { providerRequestId: requestId } : {}),
839
+ };
808
840
  }
809
841
  catch (error) {
810
842
  throw (0, error_1.normalizePrismError)(error);
@@ -813,11 +845,23 @@ class PrismPayPalConnector {
813
845
  clearTimeout(timeout);
814
846
  }
815
847
  }
848
+ async paypalRequest(method, path, body, idempotencyKey) {
849
+ return (await this.paypalResponse(method, path, body, idempotencyKey)).payload;
850
+ }
816
851
  async retrievePayPalVaultState(connectorPaymentId) {
817
852
  const payload = await this.paypalRequest('GET', `/v2/checkout/orders/${encodeURIComponent(connectorPaymentId)}?fields=payment_source`);
818
853
  return paypalVaultStateFromOrder(payload, this.options, connectorPaymentId);
819
854
  }
820
855
  async createPayment(input) {
856
+ return this.createPayPalOrder(input, true);
857
+ }
858
+ async createAdjustableOrder(input) {
859
+ if (input.metadata.alphaweb_capture_method === 'manual') {
860
+ throw validationError('An adjustable PayPal order must use CAPTURE intent.', 'PRISM_PAYPAL_ORDER_INTENT');
861
+ }
862
+ return this.createPayPalOrder(input, false);
863
+ }
864
+ async createPayPalOrder(input, commit) {
821
865
  this.assertOperation(input);
822
866
  if (input.methodSelection.kind !== 'interactive_dynamic' || input.reusableReference) {
823
867
  throw validationError('Interactive payment input is invalid.', 'PRISM_INTERACTIVE_INPUT');
@@ -833,7 +877,10 @@ class PrismPayPalConnector {
833
877
  const order = await this.dependencies.payments.createOrder({
834
878
  merchantOrderId: input.operationId,
835
879
  amount: moneyMessage(input.money),
836
- metadata: secret(JSON.stringify(operationMetadata(input))),
880
+ metadata: secret(JSON.stringify({
881
+ ...operationMetadata(input),
882
+ ...(!commit ? { alphaweb_paypal_commit: false } : {}),
883
+ })),
837
884
  state,
838
885
  testMode: this.options.environment === 'test',
839
886
  paymentMethodType: proto_1.types.PaymentMethodType.PAY_PAL,
@@ -849,6 +896,18 @@ class PrismPayPalConnector {
849
896
  });
850
897
  const orderId = stringOf(order.connectorOrderId);
851
898
  if (!orderId) {
899
+ if (!commit) {
900
+ const result = paymentResult(order, input.money, this.options);
901
+ throw new error_1.PrismOperationError({
902
+ category: 'unknown',
903
+ providerCode: result.failure?.providerCode ?? 'PRISM_PAYPAL_ORDER_CREATE_UNCONFIRMED',
904
+ ...(result.providerRequestId ? { providerRequestId: result.providerRequestId } : {}),
905
+ ...(result.providerHttpStatus ? { providerHttpStatus: result.providerHttpStatus } : {}),
906
+ outcome: 'ambiguous',
907
+ retryable: false,
908
+ safeMessage: 'The PayPal order creation result must be verified before continuing.',
909
+ });
910
+ }
852
911
  throw validationError('Prism did not return a PayPal order ID.', 'PRISM_PAYPAL_ORDER');
853
912
  }
854
913
  return {
@@ -863,6 +922,7 @@ class PrismPayPalConnector {
863
922
  clientId: this.options.clientId,
864
923
  environment: this.options.environment === 'test' ? 'sandbox' : 'production',
865
924
  savePayment: setupFutureUsage,
925
+ ...(!commit ? { commit: false } : {}),
866
926
  },
867
927
  };
868
928
  }
@@ -870,6 +930,147 @@ class PrismPayPalConnector {
870
930
  throw (0, error_1.normalizePrismError)(error);
871
931
  }
872
932
  }
933
+ assertOrderOwner(input) {
934
+ assertIdentifier(input.rootOperationId, 'rootOperationId');
935
+ assertIdentifier(input.connectorPaymentId, 'connectorPaymentId');
936
+ if (input.credentialBindingId !== this.options.credentialBindingId ||
937
+ input.providerAccountId !== this.options.providerAccountId ||
938
+ input.livemode !== (this.options.environment === 'live')) {
939
+ throw (0, paypal_approved_order_1.approvedOrderError)('PRISM_PAYPAL_ORDER_OWNER', 'definitely_not_applied');
940
+ }
941
+ }
942
+ async retrieveApprovedOrder(input) {
943
+ this.assertOrderOwner(input);
944
+ const response = await this.paypalResponse('GET', `/v2/checkout/orders/${encodeURIComponent(input.connectorPaymentId)}`);
945
+ try {
946
+ return {
947
+ ...(0, paypal_approved_order_1.readApprovedOrder)(response.payload, input, this.options),
948
+ ...(response.providerRequestId ? { providerRequestId: response.providerRequestId } : {}),
949
+ };
950
+ }
951
+ catch (error) {
952
+ const normalized = error instanceof error_1.PrismOperationError
953
+ ? error : (0, paypal_approved_order_1.approvedOrderError)('PRISM_PAYPAL_ORDER_RESPONSE');
954
+ throw new error_1.PrismOperationError({
955
+ ...normalized.shape,
956
+ outcome: 'ambiguous',
957
+ providerHttpStatus: response.providerHttpStatus,
958
+ ...(response.providerRequestId ? { providerRequestId: response.providerRequestId } : {}),
959
+ });
960
+ }
961
+ }
962
+ async updateApprovedOrder(input) {
963
+ this.assertOrderOwner(input);
964
+ const payload = (0, paypal_approved_order_1.paypalQuotePayload)(input.quote);
965
+ (0, paypal_approved_order_1.assertRecurringDisclosurePreserved)(input.expectedQuote, input.quote);
966
+ const current = await this.retrieveApprovedOrder(input);
967
+ if (current.hasCapture || current.hasAuthorization ||
968
+ (current.orderStatus !== 'CREATED' && current.orderStatus !== 'APPROVED')) {
969
+ throw (0, paypal_approved_order_1.approvedOrderError)('PRISM_PAYPAL_ORDER_NOT_EDITABLE', 'definitely_not_applied');
970
+ }
971
+ if ((0, paypal_approved_order_1.paypalQuotesMatch)(current.quote, input.quote))
972
+ return current;
973
+ if (!(0, paypal_approved_order_1.paypalQuotesMatch)(current.quote, input.expectedQuote) ||
974
+ current.quote.money.currency !== input.quote.money.currency.toLowerCase()) {
975
+ throw (0, paypal_approved_order_1.approvedOrderError)('PRISM_PAYPAL_ORDER_QUOTE_CHANGED', 'definitely_not_applied');
976
+ }
977
+ const base = `/purchase_units/@reference_id=='${input.rootOperationId}'`;
978
+ const patches = [
979
+ { op: 'replace', path: `${base}/amount`, value: payload.amount },
980
+ ];
981
+ if (payload.items)
982
+ patches.push({ op: 'add', path: `${base}/items`, value: payload.items });
983
+ else if (current.quote.items)
984
+ patches.push({ op: 'remove', path: `${base}/items` });
985
+ // PATCH has no promised provider idempotency. The caller owns serialization;
986
+ // after an uncertain result it must retrieve this order before any next quote.
987
+ const response = await this.paypalResponse('PATCH', `/v2/checkout/orders/${encodeURIComponent(input.connectorPaymentId)}`, patches);
988
+ let updated;
989
+ try {
990
+ updated = await this.retrieveApprovedOrder(input);
991
+ }
992
+ catch (error) {
993
+ // A failed read after an acknowledged PATCH is not a rejected PATCH.
994
+ throw new error_1.PrismOperationError({
995
+ ...(0, error_1.normalizePrismError)(error).shape,
996
+ outcome: 'ambiguous',
997
+ ...(response.providerRequestId ? { providerRequestId: response.providerRequestId } : {}),
998
+ });
999
+ }
1000
+ if (!(0, paypal_approved_order_1.paypalQuotesMatch)(updated.quote, input.quote) || updated.hasCapture || updated.hasAuthorization) {
1001
+ throw new error_1.PrismOperationError({
1002
+ ...(0, paypal_approved_order_1.approvedOrderError)('PRISM_PAYPAL_ORDER_UPDATE_UNCONFIRMED').shape,
1003
+ ...(response.providerRequestId ? { providerRequestId: response.providerRequestId } : {}),
1004
+ });
1005
+ }
1006
+ return {
1007
+ ...updated,
1008
+ ...(response.providerRequestId ? { providerRequestId: response.providerRequestId } : {}),
1009
+ };
1010
+ }
1011
+ async captureApprovedOrder(input) {
1012
+ this.assertOrderOwner(input);
1013
+ assertIdentifier(input.idempotencyKey, 'idempotencyKey');
1014
+ const current = await this.retrieveApprovedOrder(input);
1015
+ if (!(0, paypal_approved_order_1.paypalQuotesMatch)(current.quote, input.quote)) {
1016
+ throw (0, paypal_approved_order_1.approvedOrderError)('PRISM_PAYPAL_ORDER_QUOTE_CHANGED', 'definitely_not_applied');
1017
+ }
1018
+ // A pending capture also owns the payment. Never create a second capture.
1019
+ if (current.hasCapture)
1020
+ return current;
1021
+ if (current.hasAuthorization || current.orderStatus !== 'APPROVED') {
1022
+ throw (0, paypal_approved_order_1.approvedOrderError)('PRISM_PAYPAL_ORDER_NOT_APPROVED', 'definitely_not_applied');
1023
+ }
1024
+ const providerKey = (0, node_crypto_1.createHash)('sha256')
1025
+ .update(JSON.stringify([input.credentialBindingId, input.connectorPaymentId, input.idempotencyKey]))
1026
+ .digest('hex')
1027
+ .slice(0, 32);
1028
+ const result = await this.authorizeApprovedPayment({
1029
+ connectorPaymentId: input.connectorPaymentId,
1030
+ operationId: input.rootOperationId,
1031
+ idempotencyKey: providerKey,
1032
+ money: input.quote.money,
1033
+ captureMethod: 'automatic',
1034
+ });
1035
+ let captured;
1036
+ try {
1037
+ captured = await this.retrieveApprovedOrder(input);
1038
+ }
1039
+ catch (error) {
1040
+ // Capture may already have succeeded; a later GET failure cannot undo it.
1041
+ throw new error_1.PrismOperationError({
1042
+ ...(0, error_1.normalizePrismError)(error).shape,
1043
+ outcome: 'ambiguous',
1044
+ ...(result.providerRequestId ? { providerRequestId: result.providerRequestId } : {}),
1045
+ });
1046
+ }
1047
+ if (!(0, paypal_approved_order_1.paypalQuotesMatch)(captured.quote, input.quote)) {
1048
+ throw (0, paypal_approved_order_1.approvedOrderError)('PRISM_PAYPAL_ORDER_QUOTE_CHANGED');
1049
+ }
1050
+ if (!captured.capture) {
1051
+ if (result.status === 'failed' || result.status === 'requires_payment_method' ||
1052
+ result.status === 'requires_customer_action') {
1053
+ const category = result.status === 'requires_customer_action'
1054
+ ? 'customer_action' : result.failure?.category ?? 'unknown';
1055
+ throw new error_1.PrismOperationError({
1056
+ category: category === 'invalid_payment_method' ? 'validation' : category,
1057
+ providerCode: result.failure?.providerCode ?? 'PRISM_PAYPAL_CAPTURE_NOT_COMPLETED',
1058
+ ...(result.providerRequestId ? { providerRequestId: result.providerRequestId } : {}),
1059
+ ...(result.providerHttpStatus ? { providerHttpStatus: result.providerHttpStatus } : {}),
1060
+ retryable: false,
1061
+ outcome: category === 'unknown' ? 'ambiguous' : 'definitely_not_applied',
1062
+ safeMessage: result.status === 'requires_customer_action'
1063
+ ? 'PayPal requires customer action to complete this payment.'
1064
+ : 'PayPal did not complete this payment.',
1065
+ });
1066
+ }
1067
+ throw (0, paypal_approved_order_1.approvedOrderError)('PRISM_PAYPAL_CAPTURE_UNCONFIRMED');
1068
+ }
1069
+ return {
1070
+ ...captured,
1071
+ ...(result.providerRequestId ? { providerRequestId: result.providerRequestId } : {}),
1072
+ };
1073
+ }
873
1074
  async authorizeApprovedPayment(input) {
874
1075
  assertIdentifier(input.operationId, 'operationId');
875
1076
  assertIdentifier(input.idempotencyKey, 'idempotencyKey');
@@ -5,7 +5,7 @@ export type PrismEnvironment = 'test' | 'live';
5
5
  * payment contract a Stripe/PayPal-only union.
6
6
  */
7
7
  export type PrismConnectorId = string;
8
- export type PrismOperationKind = 'checkout' | 'post_purchase' | 'subscription_renewal' | 'refund' | 'cancel';
8
+ export type PrismOperationKind = 'checkout' | 'combined_checkout' | 'post_purchase' | 'subscription_renewal' | 'refund' | 'cancel';
9
9
  export interface PrismMoney {
10
10
  amountMinor: string;
11
11
  currency: string;
@@ -58,6 +58,8 @@ export interface PrismPayPalBrowserSession {
58
58
  clientId: string;
59
59
  environment: 'sandbox' | 'production';
60
60
  savePayment: boolean;
61
+ /** False for an approved order whose final total is selected before capture. */
62
+ commit?: boolean;
61
63
  }
62
64
  /**
63
65
  * Declaration-merging extension point for future qualified connectors.
@@ -117,6 +119,7 @@ export interface PrismPaymentResult {
117
119
  connectorMetadata?: string;
118
120
  reusableReference?: PrismReusablePaymentReference;
119
121
  providerRequestId?: string;
122
+ providerHttpStatus?: number;
120
123
  failure?: PrismPaymentFailure;
121
124
  }
122
125
  export interface PrismRefundResult {
@@ -334,9 +337,69 @@ export interface PrismPayPalOrderLifecycle {
334
337
  export interface PrismRetrieveOrderLifecycleInput {
335
338
  connectorPaymentId: string;
336
339
  }
340
+ export interface PrismPayPalOrderItem {
341
+ name: string;
342
+ sku?: string;
343
+ category?: 'DIGITAL_GOODS' | 'PHYSICAL_GOODS' | 'DONATION';
344
+ quantity: number;
345
+ unitAmountMinor: string;
346
+ taxMinor?: string;
347
+ /** Existing merchant-managed recurring disclosure; never inferred from an OTO total. */
348
+ recurringAgreement?: PrismRecurringAgreement;
349
+ }
350
+ export interface PrismPayPalOrderQuote {
351
+ money: PrismMoney;
352
+ breakdown?: {
353
+ itemTotalMinor?: string;
354
+ taxTotalMinor?: string;
355
+ shippingMinor?: string;
356
+ handlingMinor?: string;
357
+ insuranceMinor?: string;
358
+ shippingDiscountMinor?: string;
359
+ discountMinor?: string;
360
+ };
361
+ items?: PrismPayPalOrderItem[];
362
+ }
363
+ export interface PrismPayPalOrderOwner {
364
+ connectorPaymentId: string;
365
+ /** The original order-creation operation, never the later adjustment/capture. */
366
+ rootOperationId: string;
367
+ credentialBindingId: string;
368
+ providerAccountId: string;
369
+ livemode: boolean;
370
+ expectedCustomId?: string;
371
+ }
372
+ export interface PrismPayPalApprovedOrder extends PrismPayPalOrderLifecycle {
373
+ connectorPaymentId: string;
374
+ rootOperationId: string;
375
+ quote: PrismPayPalOrderQuote;
376
+ providerUpdatedAt?: string;
377
+ providerRequestId?: string;
378
+ /** Native Prism metadata, retained for refunds even after a lost capture response. */
379
+ connectorMetadata?: string;
380
+ capture?: {
381
+ connectorCaptureId: string;
382
+ status: 'pending' | 'captured' | 'failed' | 'refunded' | 'partially_refunded' | 'unknown';
383
+ money: PrismMoney;
384
+ };
385
+ }
386
+ export interface PrismPayPalUpdateApprovedOrderInput extends PrismPayPalOrderOwner {
387
+ expectedQuote: PrismPayPalOrderQuote;
388
+ quote: PrismPayPalOrderQuote;
389
+ }
390
+ export interface PrismPayPalCaptureApprovedOrderInput extends PrismPayPalOrderOwner {
391
+ quote: PrismPayPalOrderQuote;
392
+ /** Persist once for the final capture; must not change when a claim is renewed. */
393
+ idempotencyKey: string;
394
+ }
337
395
  export interface PrismPayPalConnectorV1 extends PrismConnectorV1 {
338
396
  authorizeApprovedPayment(input: PrismAuthorizeApprovedPaymentInput): Promise<PrismPaymentResult>;
339
397
  retrieveOrderLifecycle?(input: PrismRetrieveOrderLifecycleInput): Promise<PrismPayPalOrderLifecycle>;
398
+ createAdjustableOrder?(input: PrismMerchantOperation): Promise<PrismPaymentResult>;
399
+ retrieveApprovedOrder?(input: PrismPayPalOrderOwner): Promise<PrismPayPalApprovedOrder>;
400
+ /** Caller serializes decisions and reconciles an uncertain PATCH before another mutation. */
401
+ updateApprovedOrder?(input: PrismPayPalUpdateApprovedOrderInput): Promise<PrismPayPalApprovedOrder>;
402
+ captureApprovedOrder?(input: PrismPayPalCaptureApprovedOrderInput): Promise<PrismPayPalApprovedOrder>;
340
403
  }
341
404
  export interface PrismStripeConnectorOptions {
342
405
  environment: PrismEnvironment;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alphawebai/hs-prism",
3
- "version": "0.1.0-rc.39",
3
+ "version": "0.1.0-rc.41",
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": {