@alphawebai/hs-prism 0.1.0-rc.41 → 0.1.0-rc.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/alphaweb/contracts.d.ts +0 -1
- package/dist/src/alphaweb/contracts.js +1 -3
- package/dist/src/alphaweb/error.d.ts +0 -3
- package/dist/src/alphaweb/error.js +0 -3
- package/dist/src/alphaweb/manifest.d.ts +1 -2
- package/dist/src/alphaweb/manifest.js +1 -2
- package/dist/src/alphaweb/paypal-connector.d.ts +1 -8
- package/dist/src/alphaweb/paypal-connector.js +15 -216
- package/dist/src/alphaweb/types.d.ts +1 -64
- package/dist/src/payments/generated/libconnector_service_ffi.so +0 -0
- package/package.json +1 -1
- package/dist/src/alphaweb/paypal-approved-order.d.ts +0 -49
- package/dist/src/alphaweb/paypal-approved-order.js +0 -326
|
@@ -3,5 +3,4 @@ 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';
|
|
7
6
|
export type * from './types';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
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,6 +11,4 @@ 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; } });
|
|
16
14
|
//# sourceMappingURL=contracts.js.map
|
|
@@ -2,9 +2,6 @@ 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[];
|
|
8
5
|
retryable: boolean;
|
|
9
6
|
outcome: 'definitely_not_applied' | 'applied' | 'ambiguous';
|
|
10
7
|
safeMessage: string;
|
|
@@ -101,9 +101,6 @@ 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
|
-
: {}),
|
|
107
104
|
retryable,
|
|
108
105
|
outcome,
|
|
109
106
|
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.
|
|
4
|
+
readonly release: "0.1.0-rc.43";
|
|
5
5
|
readonly upstreamCommit: "b07af7d9dac271de729548e74217ddcf895c602b";
|
|
6
6
|
readonly medusaRange: ">=2.17.2 <2.18.0";
|
|
7
7
|
readonly stripeApiVersion: "2026-06-24.dahlia";
|
|
@@ -34,7 +34,6 @@ export declare const ALPHAWEB_PRISM_COMPATIBILITY_MANIFEST: Readonly<{
|
|
|
34
34
|
};
|
|
35
35
|
readonly paypal: {
|
|
36
36
|
readonly browserSession: "paypal_checkout";
|
|
37
|
-
readonly approvedOrderCheckout: "v1";
|
|
38
37
|
readonly oneTimePayment: true;
|
|
39
38
|
readonly futureUse: true;
|
|
40
39
|
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.
|
|
52
|
+
release: '0.1.0-rc.43',
|
|
53
53
|
upstreamCommit: 'b07af7d9dac271de729548e74217ddcf895c602b',
|
|
54
54
|
medusaRange: '>=2.17.2 <2.18.0',
|
|
55
55
|
stripeApiVersion: '2026-06-24.dahlia',
|
|
@@ -82,7 +82,6 @@ exports.ALPHAWEB_PRISM_COMPATIBILITY_MANIFEST = Object.freeze({
|
|
|
82
82
|
},
|
|
83
83
|
paypal: {
|
|
84
84
|
browserSession: 'paypal_checkout',
|
|
85
|
-
approvedOrderCheckout: 'v1',
|
|
86
85
|
oneTimePayment: true,
|
|
87
86
|
futureUse: true,
|
|
88
87
|
repeatPayment: true,
|
|
@@ -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,
|
|
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';
|
|
3
3
|
interface MerchantAuthenticationPort {
|
|
4
4
|
createServerAuthenticationToken(input: types.IMerchantAuthenticationServiceCreateServerAuthenticationTokenRequest): Promise<types.MerchantAuthenticationServiceCreateServerAuthenticationTokenResponse>;
|
|
5
5
|
}
|
|
@@ -32,16 +32,9 @@ export declare class PrismPayPalConnector implements PrismPayPalConnectorV1 {
|
|
|
32
32
|
private validateOptions;
|
|
33
33
|
private assertOperation;
|
|
34
34
|
private connectorState;
|
|
35
|
-
private paypalResponse;
|
|
36
35
|
private paypalRequest;
|
|
37
36
|
private retrievePayPalVaultState;
|
|
38
37
|
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>;
|
|
45
38
|
authorizeApprovedPayment(input: PrismAuthorizeApprovedPaymentInput): Promise<PrismPaymentResult>;
|
|
46
39
|
repeatPayment(input: PrismMerchantOperation): Promise<PrismPaymentResult>;
|
|
47
40
|
retryPayment(input: PrismRetryPaymentInput): Promise<PrismPaymentResult>;
|
|
@@ -8,14 +8,6 @@ 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
|
-
}
|
|
19
11
|
const PAYPAL_API_BASE = {
|
|
20
12
|
test: 'https://api-m.sandbox.paypal.com',
|
|
21
13
|
live: 'https://api-m.paypal.com',
|
|
@@ -261,13 +253,11 @@ function connectorMetadataFrom(response) {
|
|
|
261
253
|
}
|
|
262
254
|
function failureFrom(response) {
|
|
263
255
|
const error = recordOf(recordOf(response).error);
|
|
264
|
-
const code =
|
|
265
|
-
safeProviderIdentifier(error.code);
|
|
256
|
+
const code = stringOf(error.code);
|
|
266
257
|
if (!code && Object.keys(error).length === 0)
|
|
267
258
|
return undefined;
|
|
268
259
|
return {
|
|
269
|
-
category:
|
|
270
|
-
code === 'PAYER_ACTION_REQUIRED' ? 'customer_action' : 'unknown',
|
|
260
|
+
category: 'unknown',
|
|
271
261
|
...(code ? { providerCode: code } : {}),
|
|
272
262
|
retryable: false,
|
|
273
263
|
};
|
|
@@ -373,10 +363,6 @@ function paymentResult(response, money, options, fallbackConnectorPaymentId, app
|
|
|
373
363
|
const connectorPaymentId = connectorPaymentIdFrom(response) ?? fallbackConnectorPaymentId ?? null;
|
|
374
364
|
const connectorMetadata = connectorMetadataFrom(response);
|
|
375
365
|
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);
|
|
380
366
|
const providerReference = connectorPaymentId
|
|
381
367
|
? reusableReferenceFrom(response, options, connectorPaymentId)
|
|
382
368
|
: undefined;
|
|
@@ -390,9 +376,6 @@ function paymentResult(response, money, options, fallbackConnectorPaymentId, app
|
|
|
390
376
|
...(connectorMetadata ? { connectorMetadata } : {}),
|
|
391
377
|
...(reusableReference ? { reusableReference } : {}),
|
|
392
378
|
...(failure ? { failure } : {}),
|
|
393
|
-
...(providerRequestId ? { providerRequestId } : {}),
|
|
394
|
-
...(Number.isInteger(providerHttpStatus) && providerHttpStatus >= 100 && providerHttpStatus <= 599
|
|
395
|
-
? { providerHttpStatus } : {}),
|
|
396
379
|
};
|
|
397
380
|
}
|
|
398
381
|
function refundStatus(status) {
|
|
@@ -774,7 +757,7 @@ class PrismPayPalConnector {
|
|
|
774
757
|
},
|
|
775
758
|
};
|
|
776
759
|
}
|
|
777
|
-
async
|
|
760
|
+
async paypalRequest(method, path, body, idempotencyKey) {
|
|
778
761
|
const state = await this.connectorState();
|
|
779
762
|
const accessToken = readSecret(state.accessToken?.token);
|
|
780
763
|
if (!accessToken) {
|
|
@@ -793,7 +776,7 @@ class PrismPayPalConnector {
|
|
|
793
776
|
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
794
777
|
signal: controller.signal,
|
|
795
778
|
});
|
|
796
|
-
const requestId =
|
|
779
|
+
const requestId = response.headers.get('paypal-debug-id') ?? undefined;
|
|
797
780
|
const text = await response.text();
|
|
798
781
|
let payload = null;
|
|
799
782
|
if (text.length > 0) {
|
|
@@ -806,37 +789,22 @@ class PrismPayPalConnector {
|
|
|
806
789
|
}
|
|
807
790
|
if (!response.ok) {
|
|
808
791
|
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);
|
|
815
792
|
throw new error_1.PrismOperationError({
|
|
816
793
|
category: response.status === 401 || response.status === 403
|
|
817
794
|
? 'authentication'
|
|
818
|
-
: response.status ===
|
|
819
|
-
? '
|
|
820
|
-
: response.status
|
|
821
|
-
? '
|
|
822
|
-
:
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
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',
|
|
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',
|
|
832
804
|
safeMessage: 'PayPal rejected the payment operation.',
|
|
833
805
|
});
|
|
834
806
|
}
|
|
835
|
-
return
|
|
836
|
-
payload,
|
|
837
|
-
providerHttpStatus: response.status,
|
|
838
|
-
...(requestId ? { providerRequestId: requestId } : {}),
|
|
839
|
-
};
|
|
807
|
+
return payload;
|
|
840
808
|
}
|
|
841
809
|
catch (error) {
|
|
842
810
|
throw (0, error_1.normalizePrismError)(error);
|
|
@@ -845,23 +813,11 @@ class PrismPayPalConnector {
|
|
|
845
813
|
clearTimeout(timeout);
|
|
846
814
|
}
|
|
847
815
|
}
|
|
848
|
-
async paypalRequest(method, path, body, idempotencyKey) {
|
|
849
|
-
return (await this.paypalResponse(method, path, body, idempotencyKey)).payload;
|
|
850
|
-
}
|
|
851
816
|
async retrievePayPalVaultState(connectorPaymentId) {
|
|
852
817
|
const payload = await this.paypalRequest('GET', `/v2/checkout/orders/${encodeURIComponent(connectorPaymentId)}?fields=payment_source`);
|
|
853
818
|
return paypalVaultStateFromOrder(payload, this.options, connectorPaymentId);
|
|
854
819
|
}
|
|
855
820
|
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) {
|
|
865
821
|
this.assertOperation(input);
|
|
866
822
|
if (input.methodSelection.kind !== 'interactive_dynamic' || input.reusableReference) {
|
|
867
823
|
throw validationError('Interactive payment input is invalid.', 'PRISM_INTERACTIVE_INPUT');
|
|
@@ -877,10 +833,7 @@ class PrismPayPalConnector {
|
|
|
877
833
|
const order = await this.dependencies.payments.createOrder({
|
|
878
834
|
merchantOrderId: input.operationId,
|
|
879
835
|
amount: moneyMessage(input.money),
|
|
880
|
-
metadata: secret(JSON.stringify(
|
|
881
|
-
...operationMetadata(input),
|
|
882
|
-
...(!commit ? { alphaweb_paypal_commit: false } : {}),
|
|
883
|
-
})),
|
|
836
|
+
metadata: secret(JSON.stringify(operationMetadata(input))),
|
|
884
837
|
state,
|
|
885
838
|
testMode: this.options.environment === 'test',
|
|
886
839
|
paymentMethodType: proto_1.types.PaymentMethodType.PAY_PAL,
|
|
@@ -896,18 +849,6 @@ class PrismPayPalConnector {
|
|
|
896
849
|
});
|
|
897
850
|
const orderId = stringOf(order.connectorOrderId);
|
|
898
851
|
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
|
-
}
|
|
911
852
|
throw validationError('Prism did not return a PayPal order ID.', 'PRISM_PAYPAL_ORDER');
|
|
912
853
|
}
|
|
913
854
|
return {
|
|
@@ -922,7 +863,6 @@ class PrismPayPalConnector {
|
|
|
922
863
|
clientId: this.options.clientId,
|
|
923
864
|
environment: this.options.environment === 'test' ? 'sandbox' : 'production',
|
|
924
865
|
savePayment: setupFutureUsage,
|
|
925
|
-
...(!commit ? { commit: false } : {}),
|
|
926
866
|
},
|
|
927
867
|
};
|
|
928
868
|
}
|
|
@@ -930,147 +870,6 @@ class PrismPayPalConnector {
|
|
|
930
870
|
throw (0, error_1.normalizePrismError)(error);
|
|
931
871
|
}
|
|
932
872
|
}
|
|
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
|
-
}
|
|
1074
873
|
async authorizeApprovedPayment(input) {
|
|
1075
874
|
assertIdentifier(input.operationId, 'operationId');
|
|
1076
875
|
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' | '
|
|
8
|
+
export type PrismOperationKind = 'checkout' | 'post_purchase' | 'subscription_renewal' | 'refund' | 'cancel';
|
|
9
9
|
export interface PrismMoney {
|
|
10
10
|
amountMinor: string;
|
|
11
11
|
currency: string;
|
|
@@ -58,8 +58,6 @@ 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;
|
|
63
61
|
}
|
|
64
62
|
/**
|
|
65
63
|
* Declaration-merging extension point for future qualified connectors.
|
|
@@ -119,7 +117,6 @@ export interface PrismPaymentResult {
|
|
|
119
117
|
connectorMetadata?: string;
|
|
120
118
|
reusableReference?: PrismReusablePaymentReference;
|
|
121
119
|
providerRequestId?: string;
|
|
122
|
-
providerHttpStatus?: number;
|
|
123
120
|
failure?: PrismPaymentFailure;
|
|
124
121
|
}
|
|
125
122
|
export interface PrismRefundResult {
|
|
@@ -337,69 +334,9 @@ export interface PrismPayPalOrderLifecycle {
|
|
|
337
334
|
export interface PrismRetrieveOrderLifecycleInput {
|
|
338
335
|
connectorPaymentId: string;
|
|
339
336
|
}
|
|
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
|
-
}
|
|
395
337
|
export interface PrismPayPalConnectorV1 extends PrismConnectorV1 {
|
|
396
338
|
authorizeApprovedPayment(input: PrismAuthorizeApprovedPaymentInput): Promise<PrismPaymentResult>;
|
|
397
339
|
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>;
|
|
403
340
|
}
|
|
404
341
|
export interface PrismStripeConnectorOptions {
|
|
405
342
|
environment: PrismEnvironment;
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,49 +0,0 @@
|
|
|
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;
|
|
@@ -1,326 +0,0 @@
|
|
|
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
|