vona-module-pay-paypal 5.0.2 → 5.0.4
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/bean/payProvider.paypal.d.ts +17 -4
- package/dist/bean/payProvider.paypal.d.ts.map +1 -1
- package/dist/index.js +451 -11
- package/dist/index.js.map +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/paypalGateway.d.ts +23 -0
- package/dist/lib/paypalGateway.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/bean/payProvider.paypal.ts +433 -13
- package/src/lib/index.ts +1 -1
- package/src/lib/paypalGateway.ts +129 -0
- package/src/types/index.ts +2 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IDecoratorPayProviderOptions, IPayProviderClientOptions, IPayProviderClientRecord, IPayProviderExecute, IPayProviderPaymentInput, IPayProviderPaymentSnapshot, IPayProviderRefundInput, IPayProviderRefundSnapshot, IPayProviderVerifiedWebhook, IPayProviderWebhookInput } from 'vona-module-a-pay';
|
|
2
2
|
import { BeanBase } from 'vona';
|
|
3
|
+
import type { IPaypalGateway } from '../lib/paypalGateway.ts';
|
|
3
4
|
export interface IPayProviderPaypalClientRecord extends IPayProviderClientRecord {
|
|
4
5
|
}
|
|
5
6
|
export interface IPayProviderPaypalClientOptions extends IPayProviderClientOptions {
|
|
@@ -8,14 +9,26 @@ export interface IPayProviderPaypalClientOptions extends IPayProviderClientOptio
|
|
|
8
9
|
clientSecret: string | undefined;
|
|
9
10
|
};
|
|
10
11
|
webhookId: string | undefined;
|
|
12
|
+
gateway?: IPaypalGateway;
|
|
11
13
|
}
|
|
12
14
|
export interface IPayProviderOptionsPaypal extends IDecoratorPayProviderOptions<IPayProviderPaypalClientRecord, IPayProviderPaypalClientOptions> {
|
|
13
15
|
}
|
|
14
16
|
export declare class PayProviderPaypal extends BeanBase implements IPayProviderExecute<IPayProviderPaypalClientOptions> {
|
|
15
|
-
startPayment(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
startPayment(input: IPayProviderPaymentInput, clientOptions: IPayProviderPaypalClientOptions): Promise<IPayProviderPaymentSnapshot>;
|
|
18
|
+
confirmPayment(input: IPayProviderPaymentInput, clientOptions: IPayProviderPaypalClientOptions): Promise<IPayProviderPaymentSnapshot>;
|
|
19
|
+
queryPayment(input: IPayProviderPaymentInput, clientOptions: IPayProviderPaypalClientOptions): Promise<IPayProviderPaymentSnapshot>;
|
|
20
|
+
createRefund(input: IPayProviderRefundInput, clientOptions: IPayProviderPaypalClientOptions): Promise<IPayProviderRefundSnapshot>;
|
|
21
|
+
queryRefund(input: IPayProviderRefundInput, clientOptions: IPayProviderPaypalClientOptions): Promise<IPayProviderRefundSnapshot>;
|
|
22
|
+
verifyWebhook(input: IPayProviderWebhookInput, clientOptions: IPayProviderPaypalClientOptions): Promise<IPayProviderVerifiedWebhook>;
|
|
23
|
+
private _mapWebhook;
|
|
24
|
+
private _inputFromOrder;
|
|
25
|
+
private _mapOrder;
|
|
26
|
+
private _mapRefund;
|
|
27
|
+
private _gateway;
|
|
28
|
+
private _gatewayOptions;
|
|
29
|
+
private _throwGatewayError;
|
|
30
|
+
private _assertReady;
|
|
31
|
+
private _assertMerchant;
|
|
19
32
|
}
|
|
20
33
|
declare module 'vona-module-a-pay' {
|
|
21
34
|
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;AAG3B,OAAO,EAAE,QAAQ,EAAU,MAAM,MAAM,CAAC;AAIxC,OAAO,KAAK,EAAE,cAAc,EAAyB,MAAM,yBAAyB,CAAC;AAIrF,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;IAC9B,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AASD,MAAM,WAAW,yBAA0B,SAAQ,4BAA4B,CAC7E,8BAA8B,EAC9B,+BAA+B,CAChC;CAAG;AAEJ,qBAwBa,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;IA0CjC,cAAc,CAClB,KAAK,EAAE,wBAAwB,EAC/B,aAAa,EAAE,+BAA+B,GAC7C,OAAO,CAAC,2BAA2B,CAAC;IAajC,YAAY,CAChB,KAAK,EAAE,wBAAwB,EAC/B,aAAa,EAAE,+BAA+B,GAC7C,OAAO,CAAC,2BAA2B,CAAC;IASjC,YAAY,CAChB,KAAK,EAAE,uBAAuB,EAC9B,aAAa,EAAE,+BAA+B,GAC7C,OAAO,CAAC,0BAA0B,CAAC;IAoBhC,WAAW,CACf,KAAK,EAAE,uBAAuB,EAC9B,aAAa,EAAE,+BAA+B,GAC7C,OAAO,CAAC,0BAA0B,CAAC;IAahC,aAAa,CACjB,KAAK,EAAE,wBAAwB,EAC/B,aAAa,EAAE,+BAA+B,GAC7C,OAAO,CAAC,2BAA2B,CAAC;YAezB,WAAW;IA+EzB,OAAO,CAAC,eAAe;IAqBvB,OAAO,CAAC,SAAS;IAkEjB,OAAO,CAAC,UAAU;IAqBlB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,eAAe;IAcvB,OAAO,CAAC,kBAAkB;IAK1B,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,eAAe;CAaxB;AAuED,OAAO,QAAQ,mBAAmB,CAAC;IACjC,UAAiB,kBAAkB;QACjC,mBAAmB,EAAE,yBAAyB,CAAC;KAChD;CACF"}
|
package/dist/index.js
CHANGED
|
@@ -1,21 +1,393 @@
|
|
|
1
|
+
import { PaymentsController, OrdersController, Client, Environment, CheckoutPaymentIntent, OrderStatus } from '@cabloy/paypal-server-sdk';
|
|
1
2
|
import { useApp, BeanInfo, BeanBase, BeanScopeBase } from 'vona';
|
|
2
3
|
import { __decorate } from 'tslib';
|
|
3
4
|
import { PayProvider } from 'vona-module-a-pay';
|
|
5
|
+
import { z } from 'zod';
|
|
4
6
|
import { Scope } from 'vona-module-a-bean';
|
|
5
7
|
|
|
8
|
+
class PaypalGatewayError extends Error {
|
|
9
|
+
constructor(status, message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.status = void 0;
|
|
12
|
+
this.status = status;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function createPaypalGateway(fetcher) {
|
|
16
|
+
return {
|
|
17
|
+
async createOrder(options, input) {
|
|
18
|
+
return (await new OrdersController(createClient(options)).createOrder(input)).result;
|
|
19
|
+
},
|
|
20
|
+
async captureOrder(options, input) {
|
|
21
|
+
return (await new OrdersController(createClient(options)).captureOrder(input)).result;
|
|
22
|
+
},
|
|
23
|
+
async getOrder(options, input) {
|
|
24
|
+
return (await new OrdersController(createClient(options)).getOrder(input)).result;
|
|
25
|
+
},
|
|
26
|
+
async refundCapturedPayment(options, input) {
|
|
27
|
+
return (await new PaymentsController(createClient(options)).refundCapturedPayment(input)).result;
|
|
28
|
+
},
|
|
29
|
+
async getRefund(options, input) {
|
|
30
|
+
return (await new PaymentsController(createClient(options)).getRefund(input)).result;
|
|
31
|
+
},
|
|
32
|
+
async verifyWebhookSignature(options, input) {
|
|
33
|
+
const transmissionId = getHeader(input.headers, 'paypal-transmission-id');
|
|
34
|
+
const transmissionTime = getHeader(input.headers, 'paypal-transmission-time');
|
|
35
|
+
const certUrl = getHeader(input.headers, 'paypal-cert-url');
|
|
36
|
+
const authAlgo = getHeader(input.headers, 'paypal-auth-algo');
|
|
37
|
+
const transmissionSig = getHeader(input.headers, 'paypal-transmission-sig');
|
|
38
|
+
if (!transmissionId || !transmissionTime || !certUrl || !authAlgo || !transmissionSig) {
|
|
39
|
+
throw new PaypalGatewayError(401, 'PayPal webhook transmission headers are invalid');
|
|
40
|
+
}
|
|
41
|
+
const apiBaseUrl = options.environment === 'sandbox' ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
|
|
42
|
+
const tokenResponse = await fetcher(`${apiBaseUrl}/v1/oauth2/token`, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: {
|
|
45
|
+
'authorization': `Basic ${Buffer.from(`${options.clientId}:${options.clientSecret}`).toString('base64')}`,
|
|
46
|
+
'content-type': 'application/x-www-form-urlencoded'
|
|
47
|
+
},
|
|
48
|
+
body: 'grant_type=client_credentials'
|
|
49
|
+
});
|
|
50
|
+
const token = asRecord$1(tokenResponse.ok ? await tokenResponse.json() : undefined);
|
|
51
|
+
const accessToken = readString$1(token.access_token);
|
|
52
|
+
if (!accessToken) {
|
|
53
|
+
throw new PaypalGatewayError(401, 'PayPal webhook verification authentication failed');
|
|
54
|
+
}
|
|
55
|
+
const response = await fetcher(`${apiBaseUrl}/v1/notifications/verify-webhook-signature`, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: {
|
|
58
|
+
'authorization': `Bearer ${accessToken}`,
|
|
59
|
+
'content-type': 'application/json'
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify({
|
|
62
|
+
auth_algo: authAlgo,
|
|
63
|
+
cert_url: certUrl,
|
|
64
|
+
transmission_id: transmissionId,
|
|
65
|
+
transmission_sig: transmissionSig,
|
|
66
|
+
transmission_time: transmissionTime,
|
|
67
|
+
webhook_id: options.webhookId,
|
|
68
|
+
webhook_event: input.body
|
|
69
|
+
})
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) throw new PaypalGatewayError(401, 'PayPal webhook verification failed');
|
|
72
|
+
const result = asRecord$1(await response.json());
|
|
73
|
+
if (readString$1(result.verification_status) !== 'SUCCESS') {
|
|
74
|
+
throw new PaypalGatewayError(401, 'PayPal webhook signature is invalid');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function createClient(options) {
|
|
80
|
+
return new Client({
|
|
81
|
+
clientCredentialsAuthCredentials: {
|
|
82
|
+
oAuthClientId: options.clientId,
|
|
83
|
+
oAuthClientSecret: options.clientSecret
|
|
84
|
+
},
|
|
85
|
+
timeout: 0,
|
|
86
|
+
environment: options.environment === 'live' ? Environment.Production : Environment.Sandbox
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function asRecord$1(value) {
|
|
90
|
+
return value && typeof value === 'object' ? value : {};
|
|
91
|
+
}
|
|
92
|
+
function readString$1(value) {
|
|
93
|
+
return typeof value === 'string' && value ? value : undefined;
|
|
94
|
+
}
|
|
95
|
+
function getHeader(headers, key) {
|
|
96
|
+
const value = headers[key];
|
|
97
|
+
return Array.isArray(value) ? value[0] : value;
|
|
98
|
+
}
|
|
99
|
+
|
|
6
100
|
const app = useApp();
|
|
101
|
+
const PaypalWebhookSchema = z.object({
|
|
102
|
+
id: z.string().min(1).max(255),
|
|
103
|
+
event_type: z.string().min(1).max(255),
|
|
104
|
+
resource: z.record(z.string(), z.unknown())
|
|
105
|
+
});
|
|
7
106
|
let PayProviderPaypal = class PayProviderPaypal extends BeanBase {
|
|
8
|
-
async startPayment(
|
|
9
|
-
this.
|
|
107
|
+
async startPayment(input, clientOptions) {
|
|
108
|
+
if (input.providerOrderId) return await this.queryPayment(input, clientOptions);
|
|
109
|
+
if (!input.returnUrl || !input.cancelUrl) {
|
|
110
|
+
this.app.throw(422, 'PayPal payment requires return and cancel URLs');
|
|
111
|
+
}
|
|
112
|
+
this._assertReady(clientOptions);
|
|
113
|
+
const order = await this._gateway(clientOptions).createOrder(this._gatewayOptions(clientOptions), {
|
|
114
|
+
paypalRequestId: input.idempotencyKey,
|
|
115
|
+
body: {
|
|
116
|
+
intent: CheckoutPaymentIntent.Capture,
|
|
117
|
+
applicationContext: {
|
|
118
|
+
returnUrl: input.returnUrl,
|
|
119
|
+
cancelUrl: input.cancelUrl
|
|
120
|
+
},
|
|
121
|
+
purchaseUnits: [{
|
|
122
|
+
customId: String(input.paymentSessionId),
|
|
123
|
+
invoiceId: input.businessReference,
|
|
124
|
+
amount: {
|
|
125
|
+
currencyCode: input.currency,
|
|
126
|
+
value: formatMinorAmount(input.amountMinor, input.currency)
|
|
127
|
+
}
|
|
128
|
+
}]
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
const orderRecord = asRecord(order);
|
|
132
|
+
const approvalUrl = readString(asArray(orderRecord.links).map(asRecord).find(item => item.rel === 'approve')?.href);
|
|
133
|
+
const orderId = readString(orderRecord.id);
|
|
134
|
+
if (!orderId || !approvalUrl) this.app.throw(502, 'PayPal did not return an approval order');
|
|
135
|
+
return {
|
|
136
|
+
state: 'requires_action',
|
|
137
|
+
providerPaymentId: orderId,
|
|
138
|
+
providerOrderId: orderId,
|
|
139
|
+
nextAction: {
|
|
140
|
+
kind: 'redirect',
|
|
141
|
+
url: approvalUrl
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async confirmPayment(input, clientOptions) {
|
|
146
|
+
this._assertReady(clientOptions);
|
|
147
|
+
if (!input.providerOrderId) this.app.throw(409, 'PayPal payment has no provider order');
|
|
148
|
+
const order = await this._gateway(clientOptions).captureOrder(this._gatewayOptions(clientOptions), {
|
|
149
|
+
id: input.providerOrderId,
|
|
150
|
+
paypalRequestId: input.idempotencyKey
|
|
151
|
+
});
|
|
152
|
+
return this._mapOrder(order, input, clientOptions);
|
|
153
|
+
}
|
|
154
|
+
async queryPayment(input, clientOptions) {
|
|
155
|
+
this._assertReady(clientOptions);
|
|
156
|
+
if (!input.providerOrderId) this.app.throw(409, 'PayPal payment has no provider order');
|
|
157
|
+
const order = await this._gateway(clientOptions).getOrder(this._gatewayOptions(clientOptions), {
|
|
158
|
+
id: input.providerOrderId
|
|
159
|
+
});
|
|
160
|
+
return this._mapOrder(order, input, clientOptions);
|
|
161
|
+
}
|
|
162
|
+
async createRefund(input, clientOptions) {
|
|
163
|
+
this._assertReady(clientOptions);
|
|
164
|
+
const refund = await this._gateway(clientOptions).refundCapturedPayment(this._gatewayOptions(clientOptions), {
|
|
165
|
+
captureId: input.providerCaptureId,
|
|
166
|
+
paypalRequestId: input.idempotencyKey,
|
|
167
|
+
body: {
|
|
168
|
+
amount: {
|
|
169
|
+
currencyCode: input.currency,
|
|
170
|
+
value: formatMinorAmount(input.amountMinor, input.currency)
|
|
171
|
+
},
|
|
172
|
+
customId: String(input.refundOperationId),
|
|
173
|
+
invoiceId: input.businessReference
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
return this._mapRefund(refund, input, clientOptions);
|
|
177
|
+
}
|
|
178
|
+
async queryRefund(input, clientOptions) {
|
|
179
|
+
this._assertReady(clientOptions);
|
|
180
|
+
if (!input.providerRefundId) this.app.throw(409, 'PayPal refund has no provider refund identifier');
|
|
181
|
+
const refund = await this._gateway(clientOptions).getRefund(this._gatewayOptions(clientOptions), {
|
|
182
|
+
refundId: input.providerRefundId
|
|
183
|
+
});
|
|
184
|
+
return this._mapRefund(refund, input, clientOptions);
|
|
185
|
+
}
|
|
186
|
+
async verifyWebhook(input, clientOptions) {
|
|
187
|
+
this._assertReady(clientOptions);
|
|
188
|
+
const webhook = PaypalWebhookSchema.safeParse(input.body);
|
|
189
|
+
if (!webhook.success) this.app.throw(400, 'PayPal webhook is invalid');
|
|
190
|
+
try {
|
|
191
|
+
await this._gateway(clientOptions).verifyWebhookSignature(this._gatewayOptions(clientOptions), input);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
this._throwGatewayError(error);
|
|
194
|
+
}
|
|
195
|
+
return await this._mapWebhook(webhook.data, clientOptions);
|
|
196
|
+
}
|
|
197
|
+
async _mapWebhook(event, clientOptions) {
|
|
198
|
+
const resource = event.resource;
|
|
199
|
+
const amount = parsePaypalAmount(resource.amount);
|
|
200
|
+
const currency = parsePaypalCurrency(resource.amount);
|
|
201
|
+
const resourceId = readString(resource.id);
|
|
202
|
+
if (!resourceId || !amount || !currency) this.app.throw(400, 'PayPal webhook has invalid resource');
|
|
203
|
+
if (isRefundEvent(event.event_type)) {
|
|
204
|
+
const refundOperationId = readString(readField(resource, 'customId', 'custom_id'));
|
|
205
|
+
const captureId = readNestedString(resource, ['supplementary_data', 'related_ids', 'capture_id']);
|
|
206
|
+
if (!refundOperationId || !captureId) this.app.throw(400, 'PayPal refund webhook is not correlated');
|
|
207
|
+
const refund = await this.$scope.pay.model.refundOperation.getById(refundOperationId);
|
|
208
|
+
if (!refund) this.app.throw(404, 'PayPal refund operation not found');
|
|
209
|
+
const session = await this.$scope.pay.model.paymentSession.getById(refund.paymentSessionId);
|
|
210
|
+
if (!session || session.providerCaptureId !== captureId) {
|
|
211
|
+
this.app.throw(409, 'PayPal refund webhook capture conflicts');
|
|
212
|
+
}
|
|
213
|
+
const snapshot = this._mapRefund(resource, {
|
|
214
|
+
paymentSessionId: session.id,
|
|
215
|
+
refundOperationId: refund.id,
|
|
216
|
+
businessReference: refund.businessReference,
|
|
217
|
+
idempotencyKey: '',
|
|
218
|
+
amountMinor: refund.amountMinor,
|
|
219
|
+
currency: refund.currency,
|
|
220
|
+
providerCaptureId: captureId,
|
|
221
|
+
providerRefundId: resourceId
|
|
222
|
+
}, clientOptions);
|
|
223
|
+
return {
|
|
224
|
+
eventId: event.id,
|
|
225
|
+
eventType: event.event_type,
|
|
226
|
+
refundOperationId: refund.id,
|
|
227
|
+
refund: snapshot,
|
|
228
|
+
summary: {
|
|
229
|
+
amountMinor: amount,
|
|
230
|
+
currency
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (event.event_type.startsWith('PAYMENT.CAPTURE.')) {
|
|
235
|
+
const orderId = readNestedString(resource, ['supplementaryData', 'relatedIds', 'orderId']);
|
|
236
|
+
if (!orderId) this.app.throw(400, 'PayPal capture webhook has no order identifier');
|
|
237
|
+
const order = await this._gateway(clientOptions).getOrder(this._gatewayOptions(clientOptions), {
|
|
238
|
+
id: orderId
|
|
239
|
+
});
|
|
240
|
+
const input = this._inputFromOrder(order, orderId);
|
|
241
|
+
const payment = this._mapOrder(order, input, clientOptions);
|
|
242
|
+
if (!['succeeded', 'failed', 'cancelled'].includes(payment.state)) {
|
|
243
|
+
this.app.throw(400, 'PayPal capture webhook is not terminal');
|
|
244
|
+
}
|
|
245
|
+
if (payment.providerCaptureId && payment.providerCaptureId !== resourceId) {
|
|
246
|
+
this.app.throw(409, 'PayPal capture webhook conflicts with order capture');
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
eventId: event.id,
|
|
250
|
+
eventType: event.event_type,
|
|
251
|
+
paymentSessionId: input.paymentSessionId,
|
|
252
|
+
payment,
|
|
253
|
+
summary: {
|
|
254
|
+
amountMinor: amount,
|
|
255
|
+
currency
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
this.app.throw(400, 'PayPal webhook event type is unsupported');
|
|
260
|
+
}
|
|
261
|
+
_inputFromOrder(order, providerOrderId) {
|
|
262
|
+
const orderRecord = asRecord(order);
|
|
263
|
+
const unit = asArray(readField(orderRecord, 'purchaseUnits', 'purchase_units'))[0];
|
|
264
|
+
const unitRecord = asRecord(unit);
|
|
265
|
+
const customId = readString(readField(unitRecord, 'customId', 'custom_id'));
|
|
266
|
+
const businessReference = readString(readField(unitRecord, 'invoiceId', 'invoice_id'));
|
|
267
|
+
const amountMinor = parsePaypalAmount(readField(unitRecord, 'amount'));
|
|
268
|
+
const currency = parsePaypalCurrency(readField(unitRecord, 'amount'));
|
|
269
|
+
if (!customId || !businessReference || amountMinor === undefined || !currency) {
|
|
270
|
+
this.app.throw(400, 'PayPal order is not correlated');
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
paymentSessionId: customId,
|
|
274
|
+
businessReference,
|
|
275
|
+
idempotencyKey: '',
|
|
276
|
+
amountMinor,
|
|
277
|
+
currency,
|
|
278
|
+
providerOrderId
|
|
279
|
+
};
|
|
10
280
|
}
|
|
11
|
-
|
|
12
|
-
|
|
281
|
+
_mapOrder(order, input, clientOptions) {
|
|
282
|
+
const orderRecord = asRecord(order);
|
|
283
|
+
const orderId = readString(orderRecord.id);
|
|
284
|
+
if (!orderId || orderId !== input.providerOrderId) {
|
|
285
|
+
this.app.throw(409, 'PayPal order identifier conflicts');
|
|
286
|
+
}
|
|
287
|
+
const units = asArray(readField(orderRecord, 'purchaseUnits', 'purchase_units'));
|
|
288
|
+
if (units.length !== 1) this.app.throw(409, 'PayPal order has an invalid purchase-unit count');
|
|
289
|
+
const unit = asRecord(units[0]);
|
|
290
|
+
if (readString(readField(unit, 'customId', 'custom_id')) !== String(input.paymentSessionId) || readString(readField(unit, 'invoiceId', 'invoice_id')) !== input.businessReference || parsePaypalAmount(readField(unit, 'amount')) !== input.amountMinor || parsePaypalCurrency(readField(unit, 'amount')) !== input.currency) {
|
|
291
|
+
this.app.throw(409, 'PayPal order facts conflict with the payment session');
|
|
292
|
+
}
|
|
293
|
+
this._assertMerchant(unit, clientOptions);
|
|
294
|
+
const captures = asArray(readField(asRecord(readField(unit, 'payments')), 'captures'));
|
|
295
|
+
if (captures.length > 1) this.app.throw(409, 'PayPal order has multiple captures');
|
|
296
|
+
const capture = asRecord(captures[0]);
|
|
297
|
+
const captureId = readString(capture.id);
|
|
298
|
+
const captureStatus = readString(capture.status);
|
|
299
|
+
if (captureId) {
|
|
300
|
+
if (parsePaypalAmount(capture.amount) !== input.amountMinor || parsePaypalCurrency(capture.amount) !== input.currency) {
|
|
301
|
+
this.app.throw(409, 'PayPal capture facts conflict with the payment session');
|
|
302
|
+
}
|
|
303
|
+
if (captureStatus === 'COMPLETED') {
|
|
304
|
+
return {
|
|
305
|
+
state: 'succeeded',
|
|
306
|
+
providerPaymentId: orderId,
|
|
307
|
+
providerOrderId: orderId,
|
|
308
|
+
providerCaptureId: captureId,
|
|
309
|
+
nextAction: {
|
|
310
|
+
kind: 'completed'
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
if (captureStatus === 'FAILED' || captureStatus === 'DECLINED') {
|
|
315
|
+
return {
|
|
316
|
+
state: 'failed',
|
|
317
|
+
providerPaymentId: orderId,
|
|
318
|
+
providerOrderId: orderId,
|
|
319
|
+
providerCaptureId: captureId
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
if (captureStatus === 'PENDING') {
|
|
323
|
+
return {
|
|
324
|
+
state: 'processing',
|
|
325
|
+
providerPaymentId: orderId,
|
|
326
|
+
providerOrderId: orderId,
|
|
327
|
+
providerCaptureId: captureId
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (orderRecord.status === OrderStatus.Voided) {
|
|
332
|
+
return {
|
|
333
|
+
state: 'cancelled',
|
|
334
|
+
providerPaymentId: orderId,
|
|
335
|
+
providerOrderId: orderId
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
state: 'requires_action',
|
|
340
|
+
providerPaymentId: orderId,
|
|
341
|
+
providerOrderId: orderId
|
|
342
|
+
};
|
|
13
343
|
}
|
|
14
|
-
|
|
15
|
-
|
|
344
|
+
_mapRefund(refund, input, clientOptions) {
|
|
345
|
+
const record = asRecord(refund);
|
|
346
|
+
const providerRefundId = readString(record.id);
|
|
347
|
+
if (!providerRefundId) this.app.throw(502, 'PayPal refund returned no identifier');
|
|
348
|
+
if (input.providerRefundId && input.providerRefundId !== providerRefundId || readString(readField(record, 'customId', 'custom_id')) !== String(input.refundOperationId) || readString(readField(record, 'invoiceId', 'invoice_id')) !== input.businessReference || parsePaypalAmount(record.amount) !== input.amountMinor || parsePaypalCurrency(record.amount) !== input.currency) {
|
|
349
|
+
this.app.throw(409, 'PayPal refund facts conflict with the refund operation');
|
|
350
|
+
}
|
|
351
|
+
this._assertMerchant(record, clientOptions);
|
|
352
|
+
return mapRefund(readString(record.status), providerRefundId);
|
|
353
|
+
}
|
|
354
|
+
_gateway(clientOptions) {
|
|
355
|
+
return clientOptions.gateway ?? createPaypalGateway(this.bean.core.fetch);
|
|
356
|
+
}
|
|
357
|
+
_gatewayOptions(clientOptions) {
|
|
358
|
+
const {
|
|
359
|
+
clientId,
|
|
360
|
+
clientSecret
|
|
361
|
+
} = clientOptions.secretCredential;
|
|
362
|
+
const webhookId = clientOptions.webhookId;
|
|
363
|
+
if (!clientId || !clientSecret || !webhookId) {
|
|
364
|
+
this.app.throw(503, 'PayPal is not fully configured');
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
environment: clientOptions.environment,
|
|
368
|
+
clientId,
|
|
369
|
+
clientSecret,
|
|
370
|
+
webhookId
|
|
371
|
+
};
|
|
16
372
|
}
|
|
17
|
-
|
|
18
|
-
this.app.throw(
|
|
373
|
+
_throwGatewayError(error) {
|
|
374
|
+
if (error instanceof PaypalGatewayError) this.app.throw(error.status, error.message);
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
_assertReady(clientOptions) {
|
|
378
|
+
const {
|
|
379
|
+
clientId,
|
|
380
|
+
clientSecret
|
|
381
|
+
} = clientOptions.secretCredential;
|
|
382
|
+
if (!clientId || !clientSecret || !clientOptions.webhookId || !clientOptions.merchantReference) {
|
|
383
|
+
this.app.throw(503, 'PayPal is not fully configured');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
_assertMerchant(value, clientOptions) {
|
|
387
|
+
const merchantId = readNestedString(value, ['payee', 'merchantId']) ?? readNestedString(value, ['payee', 'merchant_id']) ?? readNestedString(value, ['sellerReceivableBreakdown', 'payee', 'merchantId']) ?? readNestedString(value, ['seller_receivable_breakdown', 'payee', 'merchant_id']);
|
|
388
|
+
if (!merchantId || merchantId !== clientOptions.merchantReference) {
|
|
389
|
+
this.app.throw(409, 'PayPal merchant reference conflicts');
|
|
390
|
+
}
|
|
19
391
|
}
|
|
20
392
|
};
|
|
21
393
|
PayProviderPaypal = __decorate([PayProvider({
|
|
@@ -32,17 +404,85 @@ PayProviderPaypal = __decorate([PayProvider({
|
|
|
32
404
|
},
|
|
33
405
|
clients: {
|
|
34
406
|
default: {
|
|
35
|
-
environment:
|
|
407
|
+
environment: app.meta.env.PAYPAL_ENVIRONMENT,
|
|
36
408
|
secretCredential: {
|
|
37
409
|
clientId: app.meta.env.PAYPAL_CLIENT_ID,
|
|
38
410
|
clientSecret: app.meta.env.PAYPAL_CLIENT_SECRET
|
|
39
411
|
},
|
|
40
|
-
webhookId: app.meta.env.PAYPAL_WEBHOOK_ID
|
|
412
|
+
webhookId: app.meta.env.PAYPAL_WEBHOOK_ID,
|
|
413
|
+
merchantReference: app.meta.env.PAYPAL_MERCHANT_REFERENCE
|
|
41
414
|
}
|
|
42
415
|
}
|
|
43
416
|
}), BeanInfo({
|
|
44
417
|
module: "pay-paypal"
|
|
45
418
|
})], PayProviderPaypal);
|
|
419
|
+
function formatMinorAmount(amountMinor, currency) {
|
|
420
|
+
if (!Number.isSafeInteger(amountMinor) || amountMinor < 0 || currency !== 'USD') {
|
|
421
|
+
throw new Error('PayPal supports only nonnegative USD minor-unit amounts');
|
|
422
|
+
}
|
|
423
|
+
return `${Math.floor(amountMinor / 100)}.${String(amountMinor % 100).padStart(2, '0')}`;
|
|
424
|
+
}
|
|
425
|
+
function mapRefund(status, providerRefundId) {
|
|
426
|
+
if (status === 'COMPLETED') return {
|
|
427
|
+
state: 'succeeded',
|
|
428
|
+
providerRefundId
|
|
429
|
+
};
|
|
430
|
+
if (status === 'FAILED') return {
|
|
431
|
+
state: 'failed',
|
|
432
|
+
providerRefundId
|
|
433
|
+
};
|
|
434
|
+
if (status === 'CANCELLED') return {
|
|
435
|
+
state: 'cancelled',
|
|
436
|
+
providerRefundId
|
|
437
|
+
};
|
|
438
|
+
return {
|
|
439
|
+
state: 'pending',
|
|
440
|
+
providerRefundId
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
function asRecord(value) {
|
|
444
|
+
return value && typeof value === 'object' ? value : {};
|
|
445
|
+
}
|
|
446
|
+
function asArray(value) {
|
|
447
|
+
return Array.isArray(value) ? value : [];
|
|
448
|
+
}
|
|
449
|
+
function readString(value) {
|
|
450
|
+
return typeof value === 'string' && value ? value : undefined;
|
|
451
|
+
}
|
|
452
|
+
function readField(record, ...keys) {
|
|
453
|
+
for (const key of keys) {
|
|
454
|
+
if (record[key] !== undefined) return record[key];
|
|
455
|
+
}
|
|
456
|
+
return undefined;
|
|
457
|
+
}
|
|
458
|
+
function readNestedString(value, path) {
|
|
459
|
+
let current = value;
|
|
460
|
+
for (const key of path) {
|
|
461
|
+
const record = asRecord(current);
|
|
462
|
+
current = record[key] ?? record[camelToSnake(key)];
|
|
463
|
+
}
|
|
464
|
+
return readString(current);
|
|
465
|
+
}
|
|
466
|
+
function camelToSnake(value) {
|
|
467
|
+
return value.replace(/[A-Z]/g, character => `_${character.toLowerCase()}`);
|
|
468
|
+
}
|
|
469
|
+
function parsePaypalAmount(value) {
|
|
470
|
+
const amount = asRecord(value);
|
|
471
|
+
if (readString(readField(amount, 'currencyCode', 'currency_code')) !== 'USD') return undefined;
|
|
472
|
+
const decimal = readString(amount.value);
|
|
473
|
+
if (!decimal || !/^\d+\.\d{2}$/.test(decimal)) return undefined;
|
|
474
|
+
const [whole, fractional] = decimal.split('.');
|
|
475
|
+
const result = Number(whole) * 100 + Number(fractional);
|
|
476
|
+
return Number.isSafeInteger(result) ? result : undefined;
|
|
477
|
+
}
|
|
478
|
+
function parsePaypalCurrency(value) {
|
|
479
|
+
const amount = asRecord(value);
|
|
480
|
+
const currency = readString(readField(amount, 'currencyCode', 'currency_code'));
|
|
481
|
+
return currency === 'USD' ? currency : undefined;
|
|
482
|
+
}
|
|
483
|
+
function isRefundEvent(eventType) {
|
|
484
|
+
return eventType === 'PAYMENT.CAPTURE.REFUNDED' || eventType.startsWith('PAYMENT.REFUND.');
|
|
485
|
+
}
|
|
46
486
|
|
|
47
487
|
let ScopeModulePayPaypal = class ScopeModulePayPaypal extends BeanScopeBase {};
|
|
48
488
|
ScopeModulePayPaypal = __decorate([Scope(), BeanInfo({
|
|
@@ -50,5 +490,5 @@ ScopeModulePayPaypal = __decorate([Scope(), BeanInfo({
|
|
|
50
490
|
})], ScopeModulePayPaypal);
|
|
51
491
|
/** scope: end */
|
|
52
492
|
|
|
53
|
-
export { PayProviderPaypal, ScopeModulePayPaypal };
|
|
493
|
+
export { PayProviderPaypal, PaypalGatewayError, ScopeModulePayPaypal, createPaypalGateway };
|
|
54
494
|
//# sourceMappingURL=index.js.map
|
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","_input","_clientOptions","throw","queryPayment","createRefund","verifyWebhook","__decorate","PayProvider","base","capabilities","redirectCheckout","embeddedCheckout","automaticCapture","manualCapture","refunds","partialRefunds","webhooks","clients","default","environment","secretCredential","clientId","meta","env","PAYPAL_CLIENT_ID","clientSecret","PAYPAL_CLIENT_SECRET","webhookId","PAYPAL_WEBHOOK_ID","__z_BeanInfo","module","ScopeModulePayPaypal","BeanScopeBase","Scope"],"mappings":";;;;;AAuBA,MAAMA,GAAG,GAAGC,MAAM,EAAE;AA8Bb,IAAMC,iBAAiB,GAAvB,MAAMA,iBACX,SAAQC,QAAQ,CAAA;AAGhB,EAAA,MAAMC,YAAYA,CAChBC,MAAgC,EAChCC,cAA+C,EAAA;IAE/C,IAAI,CAACN,GAAG,CAACO,KAAK,CAAC,GAAG,EAAE,mCAAmC,CAAC;AAC1D,EAAA;AAEA,EAAA,MAAMC,YAAYA,CAChBH,MAAgC,EAChCC,cAA+C,EAAA;IAE/C,IAAI,CAACN,GAAG,CAACO,KAAK,CAAC,GAAG,EAAE,mCAAmC,CAAC;AAC1D,EAAA;AAEA,EAAA,MAAME,YAAYA,CAChBJ,MAA+B,EAC/BC,cAA+C,EAAA;IAE/C,IAAI,CAACN,GAAG,CAACO,KAAK,CAAC,GAAG,EAAE,mCAAmC,CAAC;AAC1D,EAAA;AAEA,EAAA,MAAMG,aAAaA,CACjBL,MAAgC,EAChCC,cAA+C,EAAA;IAE/C,IAAI,CAACN,GAAG,CAACO,KAAK,CAAC,GAAG,EAAE,+CAA+C,CAAC;AACtE,EAAA;;AA9BWL,iBAAiB,GAAAS,UAAA,CAAA,CAvB7BC,WAAW,CAA4B;AACtCC,EAAAA,IAAI,EAAE;AACJC,IAAAA,YAAY,EAAE;AACZC,MAAAA,gBAAgB,EAAE,IAAI;AACtBC,MAAAA,gBAAgB,EAAE,KAAK;AACvBC,MAAAA,gBAAgB,EAAE,IAAI;AACtBC,MAAAA,aAAa,EAAE,KAAK;AACpBC,MAAAA,OAAO,EAAE,IAAI;AACbC,MAAAA,cAAc,EAAE,IAAI;AACpBC,MAAAA,QAAQ,EAAE;AACX;GACF;AACDC,EAAAA,OAAO,EAAE;AACPC,IAAAA,OAAO,EAAE;AACPC,MAAAA,WAAW,EAAE,SAAS;AACtBC,MAAAA,gBAAgB,EAAE;AAChBC,QAAAA,QAAQ,EAAE1B,GAAG,CAAC2B,IAAI,CAACC,GAAG,CAACC,gBAAgB;AACvCC,QAAAA,YAAY,EAAE9B,GAAG,CAAC2B,IAAI,CAACC,GAAG,CAACG;OAC5B;AACDC,MAAAA,SAAS,EAAEhC,GAAG,CAAC2B,IAAI,CAACC,GAAG,CAACK;AACzB;AACF;CACF,CAAC,EAAAC,QAAA,CAAA;EAAAC,MAAA,EAAA;AAAA,CAAA,CAAA,CACW,EAAAjC,iBAAiB,CA+B7B;;AC9CM,IAAMkC,oBAAoB,GAA1B,MAAMA,oBAAqB,SAAQC,aAAa,CAAA;AAA1CD,oBAAoB,GAAAzB,UAAA,CAAA,CADhC2B,KAAK,EAAE,EAAAJ,QAAA,CAAA;EAAAC,MAAA,EAAA;AAAA,CAAA,CAAA,CACK,EAAAC,oBAAoB,CAAyB;AAuB1D;;;;"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/lib/paypalGateway.ts","../src/bean/payProvider.paypal.ts","../src/.metadata/index.ts"],"sourcesContent":[null,null,null],"names":["PaypalGatewayError","Error","constructor","status","message","createPaypalGateway","fetcher","createOrder","options","input","OrdersController","createClient","result","captureOrder","getOrder","refundCapturedPayment","PaymentsController","getRefund","verifyWebhookSignature","transmissionId","getHeader","headers","transmissionTime","certUrl","authAlgo","transmissionSig","apiBaseUrl","environment","tokenResponse","method","Buffer","from","clientId","clientSecret","toString","body","token","asRecord","ok","json","undefined","accessToken","readString","access_token","response","JSON","stringify","auth_algo","cert_url","transmission_id","transmission_sig","transmission_time","webhook_id","webhookId","webhook_event","verification_status","Client","clientCredentialsAuthCredentials","oAuthClientId","oAuthClientSecret","timeout","Environment","Production","Sandbox","value","key","Array","isArray","app","useApp","PaypalWebhookSchema","z","object","id","string","min","max","event_type","resource","record","unknown","PayProviderPaypal","BeanBase","startPayment","clientOptions","providerOrderId","queryPayment","returnUrl","cancelUrl","throw","_assertReady","order","_gateway","_gatewayOptions","paypalRequestId","idempotencyKey","intent","CheckoutPaymentIntent","Capture","applicationContext","purchaseUnits","customId","String","paymentSessionId","invoiceId","businessReference","amount","currencyCode","currency","formatMinorAmount","amountMinor","orderRecord","approvalUrl","asArray","links","map","find","item","rel","href","orderId","state","providerPaymentId","nextAction","kind","url","confirmPayment","_mapOrder","createRefund","refund","captureId","providerCaptureId","refundOperationId","_mapRefund","queryRefund","providerRefundId","refundId","verifyWebhook","webhook","safeParse","success","error","_throwGatewayError","_mapWebhook","data","event","parsePaypalAmount","parsePaypalCurrency","resourceId","isRefundEvent","readField","readNestedString","$scope","pay","model","refundOperation","getById","session","paymentSession","snapshot","eventId","eventType","summary","startsWith","_inputFromOrder","payment","includes","unit","unitRecord","units","length","_assertMerchant","captures","capture","captureStatus","OrderStatus","Voided","mapRefund","gateway","bean","core","fetch","secretCredential","merchantReference","merchantId","__decorate","PayProvider","base","capabilities","redirectCheckout","embeddedCheckout","automaticCapture","manualCapture","refunds","partialRefunds","webhooks","clients","default","meta","env","PAYPAL_ENVIRONMENT","PAYPAL_CLIENT_ID","PAYPAL_CLIENT_SECRET","PAYPAL_WEBHOOK_ID","PAYPAL_MERCHANT_REFERENCE","__z_BeanInfo","module","Number","isSafeInteger","Math","floor","padStart","keys","path","current","camelToSnake","replace","character","toLowerCase","decimal","test","whole","fractional","split","ScopeModulePayPaypal","BeanScopeBase","Scope"],"mappings":"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"}
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export * from './paypalGateway.ts';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface IPaypalGatewayOptions {
|
|
2
|
+
environment: 'sandbox' | 'live';
|
|
3
|
+
clientId: string;
|
|
4
|
+
clientSecret: string;
|
|
5
|
+
webhookId: string;
|
|
6
|
+
}
|
|
7
|
+
export interface IPaypalGateway {
|
|
8
|
+
createOrder(options: IPaypalGatewayOptions, input: unknown): Promise<unknown>;
|
|
9
|
+
captureOrder(options: IPaypalGatewayOptions, input: unknown): Promise<unknown>;
|
|
10
|
+
getOrder(options: IPaypalGatewayOptions, input: unknown): Promise<unknown>;
|
|
11
|
+
refundCapturedPayment(options: IPaypalGatewayOptions, input: unknown): Promise<unknown>;
|
|
12
|
+
getRefund(options: IPaypalGatewayOptions, input: unknown): Promise<unknown>;
|
|
13
|
+
verifyWebhookSignature(options: IPaypalGatewayOptions, input: {
|
|
14
|
+
headers: Record<string, string | string[] | undefined>;
|
|
15
|
+
body: unknown;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export declare class PaypalGatewayError extends Error {
|
|
19
|
+
readonly status: number;
|
|
20
|
+
constructor(status: number, message: string);
|
|
21
|
+
}
|
|
22
|
+
export declare function createPaypalGateway(fetcher: typeof fetch): IPaypalGateway;
|
|
23
|
+
//# sourceMappingURL=paypalGateway.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paypalGateway.d.ts","sourceRoot":"","sources":["../../src/lib/paypalGateway.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,SAAS,GAAG,MAAM,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,CAAC,OAAO,EAAE,qBAAqB,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E,YAAY,CAAC,OAAO,EAAE,qBAAqB,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/E,QAAQ,CAAC,OAAO,EAAE,qBAAqB,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3E,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxF,SAAS,CAAC,OAAO,EAAE,qBAAqB,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5E,sBAAsB,CACpB,OAAO,EAAE,qBAAqB,EAC9B,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,GAC/E,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAEzC,QAAQ,CAAC,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM;CAIlB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,KAAK,GAAG,cAAc,CAqEzE"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,CAAC;IACpB,UAAiB,aAAa;QAC5B,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;QACrC,oBAAoB,EAAE,MAAM,GAAG,SAAS,CAAC;QACzC,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,CAAC;IACpB,UAAiB,aAAa;QAC5B,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;QACrC,oBAAoB,EAAE,MAAM,GAAG,SAAS,CAAC;QACzC,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;QACtC,yBAAyB,EAAE,MAAM,GAAG,SAAS,CAAC;QAC9C,kBAAkB,EAAE,SAAS,GAAG,MAAM,CAAC;KACxC;CACF;AAED,OAAO,EAAE,CAAC"}
|
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.4",
|
|
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.4"
|
|
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": "c408492ce774644ac8a2530702db916986f52151"
|
|
43
44
|
}
|
|
@@ -11,17 +11,29 @@ import type {
|
|
|
11
11
|
IPayProviderWebhookInput,
|
|
12
12
|
} from 'vona-module-a-pay';
|
|
13
13
|
|
|
14
|
+
import { CheckoutPaymentIntent, OrderStatus } from '@cabloy/paypal-server-sdk';
|
|
14
15
|
import { BeanBase, useApp } from 'vona';
|
|
15
16
|
import { PayProvider } from 'vona-module-a-pay';
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
|
|
19
|
+
import type { IPaypalGateway, IPaypalGatewayOptions } from '../lib/paypalGateway.ts';
|
|
20
|
+
|
|
21
|
+
import { createPaypalGateway, PaypalGatewayError } from '../lib/paypalGateway.ts';
|
|
16
22
|
|
|
17
23
|
export interface IPayProviderPaypalClientRecord extends IPayProviderClientRecord {}
|
|
18
24
|
|
|
19
25
|
export interface IPayProviderPaypalClientOptions extends IPayProviderClientOptions {
|
|
20
26
|
secretCredential: { clientId: string | undefined; clientSecret: string | undefined };
|
|
21
27
|
webhookId: string | undefined;
|
|
28
|
+
gateway?: IPaypalGateway;
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
const app = useApp();
|
|
32
|
+
const PaypalWebhookSchema = z.object({
|
|
33
|
+
id: z.string().min(1).max(255),
|
|
34
|
+
event_type: z.string().min(1).max(255),
|
|
35
|
+
resource: z.record(z.string(), z.unknown()),
|
|
36
|
+
});
|
|
25
37
|
|
|
26
38
|
export interface IPayProviderOptionsPaypal extends IDecoratorPayProviderOptions<
|
|
27
39
|
IPayProviderPaypalClientRecord,
|
|
@@ -42,12 +54,13 @@ export interface IPayProviderOptionsPaypal extends IDecoratorPayProviderOptions<
|
|
|
42
54
|
},
|
|
43
55
|
clients: {
|
|
44
56
|
default: {
|
|
45
|
-
environment: 'sandbox',
|
|
57
|
+
environment: app.meta.env.PAYPAL_ENVIRONMENT as 'sandbox' | 'live',
|
|
46
58
|
secretCredential: {
|
|
47
59
|
clientId: app.meta.env.PAYPAL_CLIENT_ID,
|
|
48
60
|
clientSecret: app.meta.env.PAYPAL_CLIENT_SECRET,
|
|
49
61
|
},
|
|
50
62
|
webhookId: app.meta.env.PAYPAL_WEBHOOK_ID,
|
|
63
|
+
merchantReference: app.meta.env.PAYPAL_MERCHANT_REFERENCE,
|
|
51
64
|
},
|
|
52
65
|
},
|
|
53
66
|
})
|
|
@@ -56,32 +69,439 @@ export class PayProviderPaypal
|
|
|
56
69
|
implements IPayProviderExecute<IPayProviderPaypalClientOptions>
|
|
57
70
|
{
|
|
58
71
|
async startPayment(
|
|
59
|
-
|
|
60
|
-
|
|
72
|
+
input: IPayProviderPaymentInput,
|
|
73
|
+
clientOptions: IPayProviderPaypalClientOptions,
|
|
74
|
+
): Promise<IPayProviderPaymentSnapshot> {
|
|
75
|
+
if (input.providerOrderId) return await this.queryPayment(input, clientOptions);
|
|
76
|
+
if (!input.returnUrl || !input.cancelUrl) {
|
|
77
|
+
this.app.throw(422, 'PayPal payment requires return and cancel URLs');
|
|
78
|
+
}
|
|
79
|
+
this._assertReady(clientOptions);
|
|
80
|
+
const order = await this._gateway(clientOptions).createOrder(
|
|
81
|
+
this._gatewayOptions(clientOptions),
|
|
82
|
+
{
|
|
83
|
+
paypalRequestId: input.idempotencyKey,
|
|
84
|
+
body: {
|
|
85
|
+
intent: CheckoutPaymentIntent.Capture,
|
|
86
|
+
applicationContext: { returnUrl: input.returnUrl, cancelUrl: input.cancelUrl },
|
|
87
|
+
purchaseUnits: [
|
|
88
|
+
{
|
|
89
|
+
customId: String(input.paymentSessionId),
|
|
90
|
+
invoiceId: input.businessReference,
|
|
91
|
+
amount: {
|
|
92
|
+
currencyCode: input.currency,
|
|
93
|
+
value: formatMinorAmount(input.amountMinor, input.currency),
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
const orderRecord = asRecord(order);
|
|
101
|
+
const approvalUrl = readString(
|
|
102
|
+
asArray(orderRecord.links)
|
|
103
|
+
.map(asRecord)
|
|
104
|
+
.find(item => item.rel === 'approve')?.href,
|
|
105
|
+
);
|
|
106
|
+
const orderId = readString(orderRecord.id);
|
|
107
|
+
if (!orderId || !approvalUrl) this.app.throw(502, 'PayPal did not return an approval order');
|
|
108
|
+
return {
|
|
109
|
+
state: 'requires_action',
|
|
110
|
+
providerPaymentId: orderId,
|
|
111
|
+
providerOrderId: orderId,
|
|
112
|
+
nextAction: { kind: 'redirect', url: approvalUrl },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async confirmPayment(
|
|
117
|
+
input: IPayProviderPaymentInput,
|
|
118
|
+
clientOptions: IPayProviderPaypalClientOptions,
|
|
61
119
|
): Promise<IPayProviderPaymentSnapshot> {
|
|
62
|
-
this.
|
|
120
|
+
this._assertReady(clientOptions);
|
|
121
|
+
if (!input.providerOrderId) this.app.throw(409, 'PayPal payment has no provider order');
|
|
122
|
+
const order = await this._gateway(clientOptions).captureOrder(
|
|
123
|
+
this._gatewayOptions(clientOptions),
|
|
124
|
+
{
|
|
125
|
+
id: input.providerOrderId,
|
|
126
|
+
paypalRequestId: input.idempotencyKey,
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
return this._mapOrder(order, input, clientOptions);
|
|
63
130
|
}
|
|
64
131
|
|
|
65
132
|
async queryPayment(
|
|
66
|
-
|
|
67
|
-
|
|
133
|
+
input: IPayProviderPaymentInput,
|
|
134
|
+
clientOptions: IPayProviderPaypalClientOptions,
|
|
68
135
|
): Promise<IPayProviderPaymentSnapshot> {
|
|
69
|
-
this.
|
|
136
|
+
this._assertReady(clientOptions);
|
|
137
|
+
if (!input.providerOrderId) this.app.throw(409, 'PayPal payment has no provider order');
|
|
138
|
+
const order = await this._gateway(clientOptions).getOrder(this._gatewayOptions(clientOptions), {
|
|
139
|
+
id: input.providerOrderId,
|
|
140
|
+
});
|
|
141
|
+
return this._mapOrder(order, input, clientOptions);
|
|
70
142
|
}
|
|
71
143
|
|
|
72
144
|
async createRefund(
|
|
73
|
-
|
|
74
|
-
|
|
145
|
+
input: IPayProviderRefundInput,
|
|
146
|
+
clientOptions: IPayProviderPaypalClientOptions,
|
|
75
147
|
): Promise<IPayProviderRefundSnapshot> {
|
|
76
|
-
this.
|
|
148
|
+
this._assertReady(clientOptions);
|
|
149
|
+
const refund = await this._gateway(clientOptions).refundCapturedPayment(
|
|
150
|
+
this._gatewayOptions(clientOptions),
|
|
151
|
+
{
|
|
152
|
+
captureId: input.providerCaptureId,
|
|
153
|
+
paypalRequestId: input.idempotencyKey,
|
|
154
|
+
body: {
|
|
155
|
+
amount: {
|
|
156
|
+
currencyCode: input.currency,
|
|
157
|
+
value: formatMinorAmount(input.amountMinor, input.currency),
|
|
158
|
+
},
|
|
159
|
+
customId: String(input.refundOperationId),
|
|
160
|
+
invoiceId: input.businessReference,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
);
|
|
164
|
+
return this._mapRefund(refund, input, clientOptions);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async queryRefund(
|
|
168
|
+
input: IPayProviderRefundInput,
|
|
169
|
+
clientOptions: IPayProviderPaypalClientOptions,
|
|
170
|
+
): Promise<IPayProviderRefundSnapshot> {
|
|
171
|
+
this._assertReady(clientOptions);
|
|
172
|
+
if (!input.providerRefundId)
|
|
173
|
+
this.app.throw(409, 'PayPal refund has no provider refund identifier');
|
|
174
|
+
const refund = await this._gateway(clientOptions).getRefund(
|
|
175
|
+
this._gatewayOptions(clientOptions),
|
|
176
|
+
{
|
|
177
|
+
refundId: input.providerRefundId,
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
return this._mapRefund(refund, input, clientOptions);
|
|
77
181
|
}
|
|
78
182
|
|
|
79
183
|
async verifyWebhook(
|
|
80
|
-
|
|
81
|
-
|
|
184
|
+
input: IPayProviderWebhookInput,
|
|
185
|
+
clientOptions: IPayProviderPaypalClientOptions,
|
|
186
|
+
): Promise<IPayProviderVerifiedWebhook> {
|
|
187
|
+
this._assertReady(clientOptions);
|
|
188
|
+
const webhook = PaypalWebhookSchema.safeParse(input.body);
|
|
189
|
+
if (!webhook.success) this.app.throw(400, 'PayPal webhook is invalid');
|
|
190
|
+
try {
|
|
191
|
+
await this._gateway(clientOptions).verifyWebhookSignature(
|
|
192
|
+
this._gatewayOptions(clientOptions),
|
|
193
|
+
input,
|
|
194
|
+
);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
this._throwGatewayError(error);
|
|
197
|
+
}
|
|
198
|
+
return await this._mapWebhook(webhook.data, clientOptions);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private async _mapWebhook(
|
|
202
|
+
event: z.infer<typeof PaypalWebhookSchema>,
|
|
203
|
+
clientOptions: IPayProviderPaypalClientOptions,
|
|
82
204
|
): Promise<IPayProviderVerifiedWebhook> {
|
|
83
|
-
|
|
205
|
+
const resource = event.resource;
|
|
206
|
+
const amount = parsePaypalAmount(resource.amount);
|
|
207
|
+
const currency = parsePaypalCurrency(resource.amount);
|
|
208
|
+
const resourceId = readString(resource.id);
|
|
209
|
+
if (!resourceId || !amount || !currency)
|
|
210
|
+
this.app.throw(400, 'PayPal webhook has invalid resource');
|
|
211
|
+
|
|
212
|
+
if (isRefundEvent(event.event_type)) {
|
|
213
|
+
const refundOperationId = readString(readField(resource, 'customId', 'custom_id'));
|
|
214
|
+
const captureId = readNestedString(resource, [
|
|
215
|
+
'supplementary_data',
|
|
216
|
+
'related_ids',
|
|
217
|
+
'capture_id',
|
|
218
|
+
]);
|
|
219
|
+
if (!refundOperationId || !captureId)
|
|
220
|
+
this.app.throw(400, 'PayPal refund webhook is not correlated');
|
|
221
|
+
const refund = await this.$scope.pay.model.refundOperation.getById(
|
|
222
|
+
refundOperationId as never,
|
|
223
|
+
);
|
|
224
|
+
if (!refund) this.app.throw(404, 'PayPal refund operation not found');
|
|
225
|
+
const session = await this.$scope.pay.model.paymentSession.getById(refund.paymentSessionId);
|
|
226
|
+
if (!session || session.providerCaptureId !== captureId) {
|
|
227
|
+
this.app.throw(409, 'PayPal refund webhook capture conflicts');
|
|
228
|
+
}
|
|
229
|
+
const snapshot = this._mapRefund(
|
|
230
|
+
resource,
|
|
231
|
+
{
|
|
232
|
+
paymentSessionId: session.id,
|
|
233
|
+
refundOperationId: refund.id,
|
|
234
|
+
businessReference: refund.businessReference,
|
|
235
|
+
idempotencyKey: '',
|
|
236
|
+
amountMinor: refund.amountMinor,
|
|
237
|
+
currency: refund.currency,
|
|
238
|
+
providerCaptureId: captureId,
|
|
239
|
+
providerRefundId: resourceId,
|
|
240
|
+
},
|
|
241
|
+
clientOptions,
|
|
242
|
+
);
|
|
243
|
+
return {
|
|
244
|
+
eventId: event.id,
|
|
245
|
+
eventType: event.event_type,
|
|
246
|
+
refundOperationId: refund.id,
|
|
247
|
+
refund: snapshot,
|
|
248
|
+
summary: { amountMinor: amount, currency },
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (event.event_type.startsWith('PAYMENT.CAPTURE.')) {
|
|
253
|
+
const orderId = readNestedString(resource, ['supplementaryData', 'relatedIds', 'orderId']);
|
|
254
|
+
if (!orderId) this.app.throw(400, 'PayPal capture webhook has no order identifier');
|
|
255
|
+
const order = await this._gateway(clientOptions).getOrder(
|
|
256
|
+
this._gatewayOptions(clientOptions),
|
|
257
|
+
{
|
|
258
|
+
id: orderId,
|
|
259
|
+
},
|
|
260
|
+
);
|
|
261
|
+
const input = this._inputFromOrder(order, orderId);
|
|
262
|
+
const payment = this._mapOrder(order, input, clientOptions);
|
|
263
|
+
if (!['succeeded', 'failed', 'cancelled'].includes(payment.state)) {
|
|
264
|
+
this.app.throw(400, 'PayPal capture webhook is not terminal');
|
|
265
|
+
}
|
|
266
|
+
if (payment.providerCaptureId && payment.providerCaptureId !== resourceId) {
|
|
267
|
+
this.app.throw(409, 'PayPal capture webhook conflicts with order capture');
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
eventId: event.id,
|
|
271
|
+
eventType: event.event_type,
|
|
272
|
+
paymentSessionId: input.paymentSessionId,
|
|
273
|
+
payment,
|
|
274
|
+
summary: { amountMinor: amount, currency },
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
this.app.throw(400, 'PayPal webhook event type is unsupported');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private _inputFromOrder(order: unknown, providerOrderId: string): IPayProviderPaymentInput {
|
|
281
|
+
const orderRecord = asRecord(order);
|
|
282
|
+
const unit = asArray(readField(orderRecord, 'purchaseUnits', 'purchase_units'))[0];
|
|
283
|
+
const unitRecord = asRecord(unit);
|
|
284
|
+
const customId = readString(readField(unitRecord, 'customId', 'custom_id'));
|
|
285
|
+
const businessReference = readString(readField(unitRecord, 'invoiceId', 'invoice_id'));
|
|
286
|
+
const amountMinor = parsePaypalAmount(readField(unitRecord, 'amount'));
|
|
287
|
+
const currency = parsePaypalCurrency(readField(unitRecord, 'amount'));
|
|
288
|
+
if (!customId || !businessReference || amountMinor === undefined || !currency) {
|
|
289
|
+
this.app.throw(400, 'PayPal order is not correlated');
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
paymentSessionId: customId as never,
|
|
293
|
+
businessReference,
|
|
294
|
+
idempotencyKey: '',
|
|
295
|
+
amountMinor,
|
|
296
|
+
currency,
|
|
297
|
+
providerOrderId,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private _mapOrder(
|
|
302
|
+
order: unknown,
|
|
303
|
+
input: IPayProviderPaymentInput,
|
|
304
|
+
clientOptions: IPayProviderPaypalClientOptions,
|
|
305
|
+
): IPayProviderPaymentSnapshot {
|
|
306
|
+
const orderRecord = asRecord(order);
|
|
307
|
+
const orderId = readString(orderRecord.id);
|
|
308
|
+
if (!orderId || orderId !== input.providerOrderId) {
|
|
309
|
+
this.app.throw(409, 'PayPal order identifier conflicts');
|
|
310
|
+
}
|
|
311
|
+
const units = asArray(readField(orderRecord, 'purchaseUnits', 'purchase_units'));
|
|
312
|
+
if (units.length !== 1) this.app.throw(409, 'PayPal order has an invalid purchase-unit count');
|
|
313
|
+
const unit = asRecord(units[0]);
|
|
314
|
+
if (
|
|
315
|
+
readString(readField(unit, 'customId', 'custom_id')) !== String(input.paymentSessionId) ||
|
|
316
|
+
readString(readField(unit, 'invoiceId', 'invoice_id')) !== input.businessReference ||
|
|
317
|
+
parsePaypalAmount(readField(unit, 'amount')) !== input.amountMinor ||
|
|
318
|
+
parsePaypalCurrency(readField(unit, 'amount')) !== input.currency
|
|
319
|
+
) {
|
|
320
|
+
this.app.throw(409, 'PayPal order facts conflict with the payment session');
|
|
321
|
+
}
|
|
322
|
+
this._assertMerchant(unit, clientOptions);
|
|
323
|
+
const captures = asArray(readField(asRecord(readField(unit, 'payments')), 'captures'));
|
|
324
|
+
if (captures.length > 1) this.app.throw(409, 'PayPal order has multiple captures');
|
|
325
|
+
const capture = asRecord(captures[0]);
|
|
326
|
+
const captureId = readString(capture.id);
|
|
327
|
+
const captureStatus = readString(capture.status);
|
|
328
|
+
if (captureId) {
|
|
329
|
+
if (
|
|
330
|
+
parsePaypalAmount(capture.amount) !== input.amountMinor ||
|
|
331
|
+
parsePaypalCurrency(capture.amount) !== input.currency
|
|
332
|
+
) {
|
|
333
|
+
this.app.throw(409, 'PayPal capture facts conflict with the payment session');
|
|
334
|
+
}
|
|
335
|
+
if (captureStatus === 'COMPLETED') {
|
|
336
|
+
return {
|
|
337
|
+
state: 'succeeded',
|
|
338
|
+
providerPaymentId: orderId,
|
|
339
|
+
providerOrderId: orderId,
|
|
340
|
+
providerCaptureId: captureId,
|
|
341
|
+
nextAction: { kind: 'completed' },
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
if (captureStatus === 'FAILED' || captureStatus === 'DECLINED') {
|
|
345
|
+
return {
|
|
346
|
+
state: 'failed',
|
|
347
|
+
providerPaymentId: orderId,
|
|
348
|
+
providerOrderId: orderId,
|
|
349
|
+
providerCaptureId: captureId,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
if (captureStatus === 'PENDING') {
|
|
353
|
+
return {
|
|
354
|
+
state: 'processing',
|
|
355
|
+
providerPaymentId: orderId,
|
|
356
|
+
providerOrderId: orderId,
|
|
357
|
+
providerCaptureId: captureId,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (orderRecord.status === OrderStatus.Voided) {
|
|
362
|
+
return { state: 'cancelled', providerPaymentId: orderId, providerOrderId: orderId };
|
|
363
|
+
}
|
|
364
|
+
return { state: 'requires_action', providerPaymentId: orderId, providerOrderId: orderId };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private _mapRefund(
|
|
368
|
+
refund: unknown,
|
|
369
|
+
input: IPayProviderRefundInput,
|
|
370
|
+
clientOptions: IPayProviderPaypalClientOptions,
|
|
371
|
+
): IPayProviderRefundSnapshot {
|
|
372
|
+
const record = asRecord(refund);
|
|
373
|
+
const providerRefundId = readString(record.id);
|
|
374
|
+
if (!providerRefundId) this.app.throw(502, 'PayPal refund returned no identifier');
|
|
375
|
+
if (
|
|
376
|
+
(input.providerRefundId && input.providerRefundId !== providerRefundId) ||
|
|
377
|
+
readString(readField(record, 'customId', 'custom_id')) !== String(input.refundOperationId) ||
|
|
378
|
+
readString(readField(record, 'invoiceId', 'invoice_id')) !== input.businessReference ||
|
|
379
|
+
parsePaypalAmount(record.amount) !== input.amountMinor ||
|
|
380
|
+
parsePaypalCurrency(record.amount) !== input.currency
|
|
381
|
+
) {
|
|
382
|
+
this.app.throw(409, 'PayPal refund facts conflict with the refund operation');
|
|
383
|
+
}
|
|
384
|
+
this._assertMerchant(record, clientOptions);
|
|
385
|
+
return mapRefund(readString(record.status), providerRefundId);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private _gateway(clientOptions: IPayProviderPaypalClientOptions) {
|
|
389
|
+
return clientOptions.gateway ?? createPaypalGateway(this.bean.core.fetch);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private _gatewayOptions(clientOptions: IPayProviderPaypalClientOptions): IPaypalGatewayOptions {
|
|
393
|
+
const { clientId, clientSecret } = clientOptions.secretCredential;
|
|
394
|
+
const webhookId = clientOptions.webhookId;
|
|
395
|
+
if (!clientId || !clientSecret || !webhookId) {
|
|
396
|
+
this.app.throw(503, 'PayPal is not fully configured');
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
environment: clientOptions.environment,
|
|
400
|
+
clientId,
|
|
401
|
+
clientSecret,
|
|
402
|
+
webhookId,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private _throwGatewayError(error: unknown): never {
|
|
407
|
+
if (error instanceof PaypalGatewayError) this.app.throw(error.status, error.message);
|
|
408
|
+
throw error;
|
|
84
409
|
}
|
|
410
|
+
|
|
411
|
+
private _assertReady(clientOptions: IPayProviderPaypalClientOptions) {
|
|
412
|
+
const { clientId, clientSecret } = clientOptions.secretCredential;
|
|
413
|
+
if (
|
|
414
|
+
!clientId ||
|
|
415
|
+
!clientSecret ||
|
|
416
|
+
!clientOptions.webhookId ||
|
|
417
|
+
!clientOptions.merchantReference
|
|
418
|
+
) {
|
|
419
|
+
this.app.throw(503, 'PayPal is not fully configured');
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private _assertMerchant(
|
|
424
|
+
value: Record<string, unknown>,
|
|
425
|
+
clientOptions: IPayProviderPaypalClientOptions,
|
|
426
|
+
) {
|
|
427
|
+
const merchantId =
|
|
428
|
+
readNestedString(value, ['payee', 'merchantId']) ??
|
|
429
|
+
readNestedString(value, ['payee', 'merchant_id']) ??
|
|
430
|
+
readNestedString(value, ['sellerReceivableBreakdown', 'payee', 'merchantId']) ??
|
|
431
|
+
readNestedString(value, ['seller_receivable_breakdown', 'payee', 'merchant_id']);
|
|
432
|
+
if (!merchantId || merchantId !== clientOptions.merchantReference) {
|
|
433
|
+
this.app.throw(409, 'PayPal merchant reference conflicts');
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function formatMinorAmount(amountMinor: number, currency: string) {
|
|
439
|
+
if (!Number.isSafeInteger(amountMinor) || amountMinor < 0 || currency !== 'USD') {
|
|
440
|
+
throw new Error('PayPal supports only nonnegative USD minor-unit amounts');
|
|
441
|
+
}
|
|
442
|
+
return `${Math.floor(amountMinor / 100)}.${String(amountMinor % 100).padStart(2, '0')}`;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function mapRefund(
|
|
446
|
+
status: string | undefined,
|
|
447
|
+
providerRefundId: string,
|
|
448
|
+
): IPayProviderRefundSnapshot {
|
|
449
|
+
if (status === 'COMPLETED') return { state: 'succeeded', providerRefundId };
|
|
450
|
+
if (status === 'FAILED') return { state: 'failed', providerRefundId };
|
|
451
|
+
if (status === 'CANCELLED') return { state: 'cancelled', providerRefundId };
|
|
452
|
+
return { state: 'pending', providerRefundId };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function asRecord(value: unknown): Record<string, unknown> {
|
|
456
|
+
return value && typeof value === 'object' ? (value as Record<string, unknown>) : {};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function asArray(value: unknown): unknown[] {
|
|
460
|
+
return Array.isArray(value) ? value : [];
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function readString(value: unknown): string | undefined {
|
|
464
|
+
return typeof value === 'string' && value ? value : undefined;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function readField(record: Record<string, unknown>, ...keys: string[]) {
|
|
468
|
+
for (const key of keys) {
|
|
469
|
+
if (record[key] !== undefined) return record[key];
|
|
470
|
+
}
|
|
471
|
+
return undefined;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function readNestedString(value: unknown, path: string[]) {
|
|
475
|
+
let current: unknown = value;
|
|
476
|
+
for (const key of path) {
|
|
477
|
+
const record = asRecord(current);
|
|
478
|
+
current = record[key] ?? record[camelToSnake(key)];
|
|
479
|
+
}
|
|
480
|
+
return readString(current);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function camelToSnake(value: string) {
|
|
484
|
+
return value.replace(/[A-Z]/g, character => `_${character.toLowerCase()}`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function parsePaypalAmount(value: unknown): number | undefined {
|
|
488
|
+
const amount = asRecord(value);
|
|
489
|
+
if (readString(readField(amount, 'currencyCode', 'currency_code')) !== 'USD') return undefined;
|
|
490
|
+
const decimal = readString(amount.value);
|
|
491
|
+
if (!decimal || !/^\d+\.\d{2}$/.test(decimal)) return undefined;
|
|
492
|
+
const [whole, fractional] = decimal.split('.');
|
|
493
|
+
const result = Number(whole) * 100 + Number(fractional);
|
|
494
|
+
return Number.isSafeInteger(result) ? result : undefined;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function parsePaypalCurrency(value: unknown) {
|
|
498
|
+
const amount = asRecord(value);
|
|
499
|
+
const currency = readString(readField(amount, 'currencyCode', 'currency_code'));
|
|
500
|
+
return currency === 'USD' ? currency : undefined;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function isRefundEvent(eventType: string) {
|
|
504
|
+
return eventType === 'PAYMENT.CAPTURE.REFUNDED' || eventType.startsWith('PAYMENT.REFUND.');
|
|
85
505
|
}
|
|
86
506
|
|
|
87
507
|
declare module 'vona-module-a-pay' {
|
package/src/lib/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export * from './paypalGateway.ts';
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Client,
|
|
3
|
+
Environment,
|
|
4
|
+
OrdersController,
|
|
5
|
+
PaymentsController,
|
|
6
|
+
} from '@cabloy/paypal-server-sdk';
|
|
7
|
+
|
|
8
|
+
export interface IPaypalGatewayOptions {
|
|
9
|
+
environment: 'sandbox' | 'live';
|
|
10
|
+
clientId: string;
|
|
11
|
+
clientSecret: string;
|
|
12
|
+
webhookId: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface IPaypalGateway {
|
|
16
|
+
createOrder(options: IPaypalGatewayOptions, input: unknown): Promise<unknown>;
|
|
17
|
+
captureOrder(options: IPaypalGatewayOptions, input: unknown): Promise<unknown>;
|
|
18
|
+
getOrder(options: IPaypalGatewayOptions, input: unknown): Promise<unknown>;
|
|
19
|
+
refundCapturedPayment(options: IPaypalGatewayOptions, input: unknown): Promise<unknown>;
|
|
20
|
+
getRefund(options: IPaypalGatewayOptions, input: unknown): Promise<unknown>;
|
|
21
|
+
verifyWebhookSignature(
|
|
22
|
+
options: IPaypalGatewayOptions,
|
|
23
|
+
input: { headers: Record<string, string | string[] | undefined>; body: unknown },
|
|
24
|
+
): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class PaypalGatewayError extends Error {
|
|
28
|
+
constructor(
|
|
29
|
+
readonly status: number,
|
|
30
|
+
message: string,
|
|
31
|
+
) {
|
|
32
|
+
super(message);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createPaypalGateway(fetcher: typeof fetch): IPaypalGateway {
|
|
37
|
+
return {
|
|
38
|
+
async createOrder(options, input) {
|
|
39
|
+
return (await new OrdersController(createClient(options)).createOrder(input as never)).result;
|
|
40
|
+
},
|
|
41
|
+
async captureOrder(options, input) {
|
|
42
|
+
return (await new OrdersController(createClient(options)).captureOrder(input as never))
|
|
43
|
+
.result;
|
|
44
|
+
},
|
|
45
|
+
async getOrder(options, input) {
|
|
46
|
+
return (await new OrdersController(createClient(options)).getOrder(input as never)).result;
|
|
47
|
+
},
|
|
48
|
+
async refundCapturedPayment(options, input) {
|
|
49
|
+
return (
|
|
50
|
+
await new PaymentsController(createClient(options)).refundCapturedPayment(input as never)
|
|
51
|
+
).result;
|
|
52
|
+
},
|
|
53
|
+
async getRefund(options, input) {
|
|
54
|
+
return (await new PaymentsController(createClient(options)).getRefund(input as never)).result;
|
|
55
|
+
},
|
|
56
|
+
async verifyWebhookSignature(options, input) {
|
|
57
|
+
const transmissionId = getHeader(input.headers, 'paypal-transmission-id');
|
|
58
|
+
const transmissionTime = getHeader(input.headers, 'paypal-transmission-time');
|
|
59
|
+
const certUrl = getHeader(input.headers, 'paypal-cert-url');
|
|
60
|
+
const authAlgo = getHeader(input.headers, 'paypal-auth-algo');
|
|
61
|
+
const transmissionSig = getHeader(input.headers, 'paypal-transmission-sig');
|
|
62
|
+
if (!transmissionId || !transmissionTime || !certUrl || !authAlgo || !transmissionSig) {
|
|
63
|
+
throw new PaypalGatewayError(401, 'PayPal webhook transmission headers are invalid');
|
|
64
|
+
}
|
|
65
|
+
const apiBaseUrl =
|
|
66
|
+
options.environment === 'sandbox'
|
|
67
|
+
? 'https://api-m.sandbox.paypal.com'
|
|
68
|
+
: 'https://api-m.paypal.com';
|
|
69
|
+
const tokenResponse = await fetcher(`${apiBaseUrl}/v1/oauth2/token`, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: {
|
|
72
|
+
'authorization': `Basic ${Buffer.from(`${options.clientId}:${options.clientSecret}`).toString('base64')}`,
|
|
73
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
74
|
+
},
|
|
75
|
+
body: 'grant_type=client_credentials',
|
|
76
|
+
});
|
|
77
|
+
const token = asRecord(tokenResponse.ok ? await tokenResponse.json() : undefined);
|
|
78
|
+
const accessToken = readString(token.access_token);
|
|
79
|
+
if (!accessToken) {
|
|
80
|
+
throw new PaypalGatewayError(401, 'PayPal webhook verification authentication failed');
|
|
81
|
+
}
|
|
82
|
+
const response = await fetcher(`${apiBaseUrl}/v1/notifications/verify-webhook-signature`, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: {
|
|
85
|
+
'authorization': `Bearer ${accessToken}`,
|
|
86
|
+
'content-type': 'application/json',
|
|
87
|
+
},
|
|
88
|
+
body: JSON.stringify({
|
|
89
|
+
auth_algo: authAlgo,
|
|
90
|
+
cert_url: certUrl,
|
|
91
|
+
transmission_id: transmissionId,
|
|
92
|
+
transmission_sig: transmissionSig,
|
|
93
|
+
transmission_time: transmissionTime,
|
|
94
|
+
webhook_id: options.webhookId,
|
|
95
|
+
webhook_event: input.body,
|
|
96
|
+
}),
|
|
97
|
+
});
|
|
98
|
+
if (!response.ok) throw new PaypalGatewayError(401, 'PayPal webhook verification failed');
|
|
99
|
+
const result = asRecord(await response.json());
|
|
100
|
+
if (readString(result.verification_status) !== 'SUCCESS') {
|
|
101
|
+
throw new PaypalGatewayError(401, 'PayPal webhook signature is invalid');
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function createClient(options: IPaypalGatewayOptions) {
|
|
108
|
+
return new Client({
|
|
109
|
+
clientCredentialsAuthCredentials: {
|
|
110
|
+
oAuthClientId: options.clientId,
|
|
111
|
+
oAuthClientSecret: options.clientSecret,
|
|
112
|
+
},
|
|
113
|
+
timeout: 0,
|
|
114
|
+
environment: options.environment === 'live' ? Environment.Production : Environment.Sandbox,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function asRecord(value: unknown): Record<string, unknown> {
|
|
119
|
+
return value && typeof value === 'object' ? (value as Record<string, unknown>) : {};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function readString(value: unknown): string | undefined {
|
|
123
|
+
return typeof value === 'string' && value ? value : undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getHeader(headers: Record<string, string | string[] | undefined>, key: string) {
|
|
127
|
+
const value = headers[key];
|
|
128
|
+
return Array.isArray(value) ? value[0] : value;
|
|
129
|
+
}
|
package/src/types/index.ts
CHANGED