vona-module-pay-paypal 5.0.2 → 5.0.3
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.
|
@@ -12,10 +12,12 @@ export interface IPayProviderPaypalClientOptions extends IPayProviderClientOptio
|
|
|
12
12
|
export interface IPayProviderOptionsPaypal extends IDecoratorPayProviderOptions<IPayProviderPaypalClientRecord, IPayProviderPaypalClientOptions> {
|
|
13
13
|
}
|
|
14
14
|
export declare class PayProviderPaypal extends BeanBase implements IPayProviderExecute<IPayProviderPaypalClientOptions> {
|
|
15
|
-
startPayment(
|
|
16
|
-
queryPayment(
|
|
17
|
-
createRefund(
|
|
15
|
+
startPayment(input: IPayProviderPaymentInput, clientOptions: IPayProviderPaypalClientOptions): Promise<IPayProviderPaymentSnapshot>;
|
|
16
|
+
queryPayment(input: IPayProviderPaymentInput, clientOptions: IPayProviderPaypalClientOptions): Promise<IPayProviderPaymentSnapshot>;
|
|
17
|
+
createRefund(input: IPayProviderRefundInput, clientOptions: IPayProviderPaypalClientOptions): Promise<IPayProviderRefundSnapshot>;
|
|
18
|
+
queryRefund(_input: IPayProviderRefundInput, _clientOptions: IPayProviderPaypalClientOptions): Promise<IPayProviderRefundSnapshot>;
|
|
18
19
|
verifyWebhook(_input: IPayProviderWebhookInput, _clientOptions: IPayProviderPaypalClientOptions): Promise<IPayProviderVerifiedWebhook>;
|
|
20
|
+
private _createClient;
|
|
19
21
|
}
|
|
20
22
|
declare module 'vona-module-a-pay' {
|
|
21
23
|
interface IPayProviderRecord {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payProvider.paypal.d.ts","sourceRoot":"","sources":["../../src/bean/payProvider.paypal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,4BAA4B,EAC5B,yBAAyB,EACzB,wBAAwB,EACxB,mBAAmB,EACnB,wBAAwB,EACxB,2BAA2B,EAC3B,uBAAuB,EACvB,0BAA0B,EAC1B,2BAA2B,EAC3B,wBAAwB,EACzB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"payProvider.paypal.d.ts","sourceRoot":"","sources":["../../src/bean/payProvider.paypal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,4BAA4B,EAC5B,yBAAyB,EACzB,wBAAwB,EACxB,mBAAmB,EACnB,wBAAwB,EACxB,2BAA2B,EAC3B,uBAAuB,EACvB,0BAA0B,EAC1B,2BAA2B,EAC3B,wBAAwB,EACzB,MAAM,mBAAmB,CAAC;AAU3B,OAAO,EAAE,QAAQ,EAAU,MAAM,MAAM,CAAC;AAGxC,MAAM,WAAW,8BAA+B,SAAQ,wBAAwB;CAAG;AAEnF,MAAM,WAAW,+BAAgC,SAAQ,yBAAyB;IAChF,gBAAgB,EAAE;QAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IACrF,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAID,MAAM,WAAW,yBAA0B,SAAQ,4BAA4B,CAC7E,8BAA8B,EAC9B,+BAA+B,CAChC;CAAG;AAEJ,qBAuBa,iBACX,SAAQ,QACR,YAAW,mBAAmB,CAAC,+BAA+B,CAAC;IAEzD,YAAY,CAChB,KAAK,EAAE,wBAAwB,EAC/B,aAAa,EAAE,+BAA+B,GAC7C,OAAO,CAAC,2BAA2B,CAAC;IAkCjC,YAAY,CAChB,KAAK,EAAE,wBAAwB,EAC/B,aAAa,EAAE,+BAA+B,GAC7C,OAAO,CAAC,2BAA2B,CAAC;IA4BjC,YAAY,CAChB,KAAK,EAAE,uBAAuB,EAC9B,aAAa,EAAE,+BAA+B,GAC7C,OAAO,CAAC,0BAA0B,CAAC;IAehC,WAAW,CACf,MAAM,EAAE,uBAAuB,EAC/B,cAAc,EAAE,+BAA+B,GAC9C,OAAO,CAAC,0BAA0B,CAAC;IAIhC,aAAa,CACjB,MAAM,EAAE,wBAAwB,EAChC,cAAc,EAAE,+BAA+B,GAC9C,OAAO,CAAC,2BAA2B,CAAC;IAIvC,OAAO,CAAC,aAAa;CActB;AAgBD,OAAO,QAAQ,mBAAmB,CAAC;IACjC,UAAiB,kBAAkB;QACjC,mBAAmB,EAAE,yBAAyB,CAAC;KAChD;CACF"}
|
package/dist/index.js
CHANGED
|
@@ -1,21 +1,131 @@
|
|
|
1
1
|
import { useApp, BeanInfo, BeanBase, BeanScopeBase } from 'vona';
|
|
2
2
|
import { __decorate } from 'tslib';
|
|
3
|
+
import { OrdersController, CheckoutPaymentIntent, OrderStatus, PaymentsController, Client, Environment } from '@cabloy/paypal-server-sdk';
|
|
3
4
|
import { PayProvider } from 'vona-module-a-pay';
|
|
4
5
|
import { Scope } from 'vona-module-a-bean';
|
|
5
6
|
|
|
6
7
|
const app = useApp();
|
|
7
8
|
let PayProviderPaypal = class PayProviderPaypal extends BeanBase {
|
|
8
|
-
async startPayment(
|
|
9
|
-
this.
|
|
9
|
+
async startPayment(input, clientOptions) {
|
|
10
|
+
if (input.providerOrderId) return await this.queryPayment(input, clientOptions);
|
|
11
|
+
if (!input.returnUrl || !input.cancelUrl) {
|
|
12
|
+
this.app.throw(422, 'PayPal payment requires return and cancel URLs');
|
|
13
|
+
}
|
|
14
|
+
const orders = new OrdersController(this._createClient(clientOptions));
|
|
15
|
+
const response = await orders.createOrder({
|
|
16
|
+
paypalRequestId: input.idempotencyKey,
|
|
17
|
+
body: {
|
|
18
|
+
intent: CheckoutPaymentIntent.Capture,
|
|
19
|
+
applicationContext: {
|
|
20
|
+
returnUrl: input.returnUrl,
|
|
21
|
+
cancelUrl: input.cancelUrl
|
|
22
|
+
},
|
|
23
|
+
purchaseUnits: [{
|
|
24
|
+
customId: String(input.paymentSessionId),
|
|
25
|
+
invoiceId: input.businessReference,
|
|
26
|
+
amount: {
|
|
27
|
+
currencyCode: input.currency,
|
|
28
|
+
value: formatMinorAmount(input.amountMinor, input.currency)
|
|
29
|
+
}
|
|
30
|
+
}]
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
const order = response.result;
|
|
34
|
+
const approvalUrl = order?.links?.find(item => item.rel === 'approve')?.href;
|
|
35
|
+
if (!order?.id || !approvalUrl) this.app.throw(502, 'PayPal did not return an approval order');
|
|
36
|
+
return {
|
|
37
|
+
state: 'requires_action',
|
|
38
|
+
providerPaymentId: order.id,
|
|
39
|
+
providerOrderId: order.id,
|
|
40
|
+
nextAction: {
|
|
41
|
+
kind: 'redirect',
|
|
42
|
+
url: approvalUrl
|
|
43
|
+
}
|
|
44
|
+
};
|
|
10
45
|
}
|
|
11
|
-
async queryPayment(
|
|
12
|
-
|
|
46
|
+
async queryPayment(input, clientOptions) {
|
|
47
|
+
if (!input.providerOrderId) return {
|
|
48
|
+
state: 'requires_action'
|
|
49
|
+
};
|
|
50
|
+
const orders = new OrdersController(this._createClient(clientOptions));
|
|
51
|
+
const response = await orders.getOrder({
|
|
52
|
+
id: input.providerOrderId
|
|
53
|
+
});
|
|
54
|
+
const order = response.result;
|
|
55
|
+
if (!order?.id) this.app.throw(502, 'PayPal order query returned no order');
|
|
56
|
+
const capture = order.purchaseUnits?.[0]?.payments?.captures?.[0];
|
|
57
|
+
if (capture?.status === 'COMPLETED') {
|
|
58
|
+
return {
|
|
59
|
+
state: 'succeeded',
|
|
60
|
+
providerPaymentId: order.id,
|
|
61
|
+
providerOrderId: order.id,
|
|
62
|
+
providerCaptureId: capture.id,
|
|
63
|
+
nextAction: {
|
|
64
|
+
kind: 'completed'
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (capture?.status === 'FAILED' || capture?.status === 'DECLINED') {
|
|
69
|
+
return {
|
|
70
|
+
state: 'failed',
|
|
71
|
+
providerPaymentId: order.id,
|
|
72
|
+
providerOrderId: order.id
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (order.status === OrderStatus.Voided) {
|
|
76
|
+
return {
|
|
77
|
+
state: 'cancelled',
|
|
78
|
+
providerPaymentId: order.id,
|
|
79
|
+
providerOrderId: order.id
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (capture?.status === 'PENDING') {
|
|
83
|
+
return {
|
|
84
|
+
state: 'processing',
|
|
85
|
+
providerPaymentId: order.id,
|
|
86
|
+
providerOrderId: order.id
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
state: 'requires_action',
|
|
91
|
+
providerPaymentId: order.id,
|
|
92
|
+
providerOrderId: order.id
|
|
93
|
+
};
|
|
13
94
|
}
|
|
14
|
-
async createRefund(
|
|
15
|
-
|
|
95
|
+
async createRefund(input, clientOptions) {
|
|
96
|
+
const payments = new PaymentsController(this._createClient(clientOptions));
|
|
97
|
+
const response = await payments.refundCapturedPayment({
|
|
98
|
+
captureId: input.providerCaptureId,
|
|
99
|
+
paypalRequestId: input.idempotencyKey,
|
|
100
|
+
body: {
|
|
101
|
+
amount: {
|
|
102
|
+
currencyCode: input.currency,
|
|
103
|
+
value: formatMinorAmount(input.amountMinor, input.currency)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
return mapRefund(response.result?.status, response.result?.id);
|
|
108
|
+
}
|
|
109
|
+
async queryRefund(_input, _clientOptions) {
|
|
110
|
+
this.app.throw(501, 'PayPal refund query requires a persisted provider refund identifier');
|
|
16
111
|
}
|
|
17
112
|
async verifyWebhook(_input, _clientOptions) {
|
|
18
|
-
this.app.throw(501, 'PayPal webhook verification
|
|
113
|
+
this.app.throw(501, 'PayPal webhook verification requires configured webhook verification');
|
|
114
|
+
}
|
|
115
|
+
_createClient(clientOptions) {
|
|
116
|
+
const {
|
|
117
|
+
clientId,
|
|
118
|
+
clientSecret
|
|
119
|
+
} = clientOptions.secretCredential;
|
|
120
|
+
if (!clientId || !clientSecret) this.app.throw(503, 'PayPal client credentials are not configured');
|
|
121
|
+
return new Client({
|
|
122
|
+
clientCredentialsAuthCredentials: {
|
|
123
|
+
oAuthClientId: clientId,
|
|
124
|
+
oAuthClientSecret: clientSecret
|
|
125
|
+
},
|
|
126
|
+
timeout: 0,
|
|
127
|
+
environment: clientOptions.environment === 'live' ? Environment.Production : Environment.Sandbox
|
|
128
|
+
});
|
|
19
129
|
}
|
|
20
130
|
};
|
|
21
131
|
PayProviderPaypal = __decorate([PayProvider({
|
|
@@ -43,6 +153,30 @@ PayProviderPaypal = __decorate([PayProvider({
|
|
|
43
153
|
}), BeanInfo({
|
|
44
154
|
module: "pay-paypal"
|
|
45
155
|
})], PayProviderPaypal);
|
|
156
|
+
function formatMinorAmount(amountMinor, currency) {
|
|
157
|
+
if (!Number.isSafeInteger(amountMinor) || amountMinor < 0 || currency !== 'USD') {
|
|
158
|
+
throw new Error('PayPal supports only nonnegative USD minor-unit amounts');
|
|
159
|
+
}
|
|
160
|
+
return `${Math.floor(amountMinor / 100)}.${String(amountMinor % 100).padStart(2, '0')}`;
|
|
161
|
+
}
|
|
162
|
+
function mapRefund(status, providerRefundId) {
|
|
163
|
+
if (status === 'COMPLETED') return {
|
|
164
|
+
state: 'succeeded',
|
|
165
|
+
providerRefundId
|
|
166
|
+
};
|
|
167
|
+
if (status === 'FAILED') return {
|
|
168
|
+
state: 'failed',
|
|
169
|
+
providerRefundId
|
|
170
|
+
};
|
|
171
|
+
if (status === 'CANCELLED') return {
|
|
172
|
+
state: 'cancelled',
|
|
173
|
+
providerRefundId
|
|
174
|
+
};
|
|
175
|
+
return {
|
|
176
|
+
state: 'pending',
|
|
177
|
+
providerRefundId
|
|
178
|
+
};
|
|
179
|
+
}
|
|
46
180
|
|
|
47
181
|
let ScopeModulePayPaypal = class ScopeModulePayPaypal extends BeanScopeBase {};
|
|
48
182
|
ScopeModulePayPaypal = __decorate([Scope(), BeanInfo({
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/bean/payProvider.paypal.ts","../src/.metadata/index.ts"],"sourcesContent":[null,null],"names":["app","useApp","PayProviderPaypal","BeanBase","startPayment","
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/bean/payProvider.paypal.ts","../src/.metadata/index.ts"],"sourcesContent":[null,null],"names":["app","useApp","PayProviderPaypal","BeanBase","startPayment","input","clientOptions","providerOrderId","queryPayment","returnUrl","cancelUrl","throw","orders","OrdersController","_createClient","response","createOrder","paypalRequestId","idempotencyKey","body","intent","CheckoutPaymentIntent","Capture","applicationContext","purchaseUnits","customId","String","paymentSessionId","invoiceId","businessReference","amount","currencyCode","currency","value","formatMinorAmount","amountMinor","order","result","approvalUrl","links","find","item","rel","href","id","state","providerPaymentId","nextAction","kind","url","getOrder","capture","payments","captures","status","providerCaptureId","OrderStatus","Voided","createRefund","PaymentsController","refundCapturedPayment","captureId","mapRefund","queryRefund","_input","_clientOptions","verifyWebhook","clientId","clientSecret","secretCredential","Client","clientCredentialsAuthCredentials","oAuthClientId","oAuthClientSecret","timeout","environment","Environment","Production","Sandbox","__decorate","PayProvider","base","capabilities","redirectCheckout","embeddedCheckout","automaticCapture","manualCapture","refunds","partialRefunds","webhooks","clients","default","meta","env","PAYPAL_CLIENT_ID","PAYPAL_CLIENT_SECRET","webhookId","PAYPAL_WEBHOOK_ID","__z_BeanInfo","module","Number","isSafeInteger","Error","Math","floor","padStart","providerRefundId","ScopeModulePayPaypal","BeanScopeBase","Scope"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona-module-pay-paypal",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Vona Module"
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"postpack": "clean-package restore"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"
|
|
33
|
+
"@cabloy/paypal-server-sdk": "2.2.0",
|
|
34
|
+
"vona-module-a-pay": "5.0.3"
|
|
34
35
|
},
|
|
35
36
|
"title": "pay-paypal",
|
|
36
37
|
"vonaModule": {
|
|
@@ -39,5 +40,5 @@
|
|
|
39
40
|
"a-vona": "5.0.0"
|
|
40
41
|
}
|
|
41
42
|
},
|
|
42
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "e86b8ee51b837417b7b28b2c67862877d4092371"
|
|
43
44
|
}
|
|
@@ -11,6 +11,14 @@ import type {
|
|
|
11
11
|
IPayProviderWebhookInput,
|
|
12
12
|
} from 'vona-module-a-pay';
|
|
13
13
|
|
|
14
|
+
import {
|
|
15
|
+
CheckoutPaymentIntent,
|
|
16
|
+
Client,
|
|
17
|
+
Environment,
|
|
18
|
+
OrdersController,
|
|
19
|
+
OrderStatus,
|
|
20
|
+
PaymentsController,
|
|
21
|
+
} from '@cabloy/paypal-server-sdk';
|
|
14
22
|
import { BeanBase, useApp } from 'vona';
|
|
15
23
|
import { PayProvider } from 'vona-module-a-pay';
|
|
16
24
|
|
|
@@ -56,32 +64,133 @@ export class PayProviderPaypal
|
|
|
56
64
|
implements IPayProviderExecute<IPayProviderPaypalClientOptions>
|
|
57
65
|
{
|
|
58
66
|
async startPayment(
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
input: IPayProviderPaymentInput,
|
|
68
|
+
clientOptions: IPayProviderPaypalClientOptions,
|
|
61
69
|
): Promise<IPayProviderPaymentSnapshot> {
|
|
62
|
-
this.
|
|
70
|
+
if (input.providerOrderId) return await this.queryPayment(input, clientOptions);
|
|
71
|
+
if (!input.returnUrl || !input.cancelUrl) {
|
|
72
|
+
this.app.throw(422, 'PayPal payment requires return and cancel URLs');
|
|
73
|
+
}
|
|
74
|
+
const orders = new OrdersController(this._createClient(clientOptions));
|
|
75
|
+
const response = await orders.createOrder({
|
|
76
|
+
paypalRequestId: input.idempotencyKey,
|
|
77
|
+
body: {
|
|
78
|
+
intent: CheckoutPaymentIntent.Capture,
|
|
79
|
+
applicationContext: { returnUrl: input.returnUrl, cancelUrl: input.cancelUrl },
|
|
80
|
+
purchaseUnits: [
|
|
81
|
+
{
|
|
82
|
+
customId: String(input.paymentSessionId),
|
|
83
|
+
invoiceId: input.businessReference,
|
|
84
|
+
amount: {
|
|
85
|
+
currencyCode: input.currency,
|
|
86
|
+
value: formatMinorAmount(input.amountMinor, input.currency),
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
} as never);
|
|
92
|
+
const order = response.result;
|
|
93
|
+
const approvalUrl = order?.links?.find(item => item.rel === 'approve')?.href;
|
|
94
|
+
if (!order?.id || !approvalUrl) this.app.throw(502, 'PayPal did not return an approval order');
|
|
95
|
+
return {
|
|
96
|
+
state: 'requires_action',
|
|
97
|
+
providerPaymentId: order.id,
|
|
98
|
+
providerOrderId: order.id,
|
|
99
|
+
nextAction: { kind: 'redirect', url: approvalUrl },
|
|
100
|
+
};
|
|
63
101
|
}
|
|
64
102
|
|
|
65
103
|
async queryPayment(
|
|
66
|
-
|
|
67
|
-
|
|
104
|
+
input: IPayProviderPaymentInput,
|
|
105
|
+
clientOptions: IPayProviderPaypalClientOptions,
|
|
68
106
|
): Promise<IPayProviderPaymentSnapshot> {
|
|
69
|
-
|
|
107
|
+
if (!input.providerOrderId) return { state: 'requires_action' };
|
|
108
|
+
const orders = new OrdersController(this._createClient(clientOptions));
|
|
109
|
+
const response = await orders.getOrder({ id: input.providerOrderId });
|
|
110
|
+
const order = response.result;
|
|
111
|
+
if (!order?.id) this.app.throw(502, 'PayPal order query returned no order');
|
|
112
|
+
const capture = order.purchaseUnits?.[0]?.payments?.captures?.[0];
|
|
113
|
+
if (capture?.status === 'COMPLETED') {
|
|
114
|
+
return {
|
|
115
|
+
state: 'succeeded',
|
|
116
|
+
providerPaymentId: order.id,
|
|
117
|
+
providerOrderId: order.id,
|
|
118
|
+
providerCaptureId: capture.id,
|
|
119
|
+
nextAction: { kind: 'completed' },
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (capture?.status === 'FAILED' || capture?.status === 'DECLINED') {
|
|
123
|
+
return { state: 'failed', providerPaymentId: order.id, providerOrderId: order.id };
|
|
124
|
+
}
|
|
125
|
+
if (order.status === OrderStatus.Voided) {
|
|
126
|
+
return { state: 'cancelled', providerPaymentId: order.id, providerOrderId: order.id };
|
|
127
|
+
}
|
|
128
|
+
if (capture?.status === 'PENDING') {
|
|
129
|
+
return { state: 'processing', providerPaymentId: order.id, providerOrderId: order.id };
|
|
130
|
+
}
|
|
131
|
+
return { state: 'requires_action', providerPaymentId: order.id, providerOrderId: order.id };
|
|
70
132
|
}
|
|
71
133
|
|
|
72
134
|
async createRefund(
|
|
135
|
+
input: IPayProviderRefundInput,
|
|
136
|
+
clientOptions: IPayProviderPaypalClientOptions,
|
|
137
|
+
): Promise<IPayProviderRefundSnapshot> {
|
|
138
|
+
const payments = new PaymentsController(this._createClient(clientOptions));
|
|
139
|
+
const response = await payments.refundCapturedPayment({
|
|
140
|
+
captureId: input.providerCaptureId,
|
|
141
|
+
paypalRequestId: input.idempotencyKey,
|
|
142
|
+
body: {
|
|
143
|
+
amount: {
|
|
144
|
+
currencyCode: input.currency,
|
|
145
|
+
value: formatMinorAmount(input.amountMinor, input.currency),
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
} as never);
|
|
149
|
+
return mapRefund(response.result?.status, response.result?.id);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async queryRefund(
|
|
73
153
|
_input: IPayProviderRefundInput,
|
|
74
154
|
_clientOptions: IPayProviderPaypalClientOptions,
|
|
75
155
|
): Promise<IPayProviderRefundSnapshot> {
|
|
76
|
-
this.app.throw(501, 'PayPal provider
|
|
156
|
+
this.app.throw(501, 'PayPal refund query requires a persisted provider refund identifier');
|
|
77
157
|
}
|
|
78
158
|
|
|
79
159
|
async verifyWebhook(
|
|
80
160
|
_input: IPayProviderWebhookInput,
|
|
81
161
|
_clientOptions: IPayProviderPaypalClientOptions,
|
|
82
162
|
): Promise<IPayProviderVerifiedWebhook> {
|
|
83
|
-
this.app.throw(501, 'PayPal webhook verification
|
|
163
|
+
this.app.throw(501, 'PayPal webhook verification requires configured webhook verification');
|
|
84
164
|
}
|
|
165
|
+
|
|
166
|
+
private _createClient(clientOptions: IPayProviderPaypalClientOptions) {
|
|
167
|
+
const { clientId, clientSecret } = clientOptions.secretCredential;
|
|
168
|
+
if (!clientId || !clientSecret)
|
|
169
|
+
this.app.throw(503, 'PayPal client credentials are not configured');
|
|
170
|
+
return new Client({
|
|
171
|
+
clientCredentialsAuthCredentials: {
|
|
172
|
+
oAuthClientId: clientId,
|
|
173
|
+
oAuthClientSecret: clientSecret,
|
|
174
|
+
},
|
|
175
|
+
timeout: 0,
|
|
176
|
+
environment:
|
|
177
|
+
clientOptions.environment === 'live' ? Environment.Production : Environment.Sandbox,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function formatMinorAmount(amountMinor: number, currency: string) {
|
|
183
|
+
if (!Number.isSafeInteger(amountMinor) || amountMinor < 0 || currency !== 'USD') {
|
|
184
|
+
throw new Error('PayPal supports only nonnegative USD minor-unit amounts');
|
|
185
|
+
}
|
|
186
|
+
return `${Math.floor(amountMinor / 100)}.${String(amountMinor % 100).padStart(2, '0')}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function mapRefund(status?: string, providerRefundId?: string): IPayProviderRefundSnapshot {
|
|
190
|
+
if (status === 'COMPLETED') return { state: 'succeeded', providerRefundId };
|
|
191
|
+
if (status === 'FAILED') return { state: 'failed', providerRefundId };
|
|
192
|
+
if (status === 'CANCELLED') return { state: 'cancelled', providerRefundId };
|
|
193
|
+
return { state: 'pending', providerRefundId };
|
|
85
194
|
}
|
|
86
195
|
|
|
87
196
|
declare module 'vona-module-a-pay' {
|