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.
@@ -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(_input: IPayProviderPaymentInput, _clientOptions: IPayProviderPaypalClientOptions): Promise<IPayProviderPaymentSnapshot>;
16
- queryPayment(_input: IPayProviderPaymentInput, _clientOptions: IPayProviderPaypalClientOptions): Promise<IPayProviderPaymentSnapshot>;
17
- createRefund(_input: IPayProviderRefundInput, _clientOptions: IPayProviderPaypalClientOptions): Promise<IPayProviderRefundSnapshot>;
18
- verifyWebhook(_input: IPayProviderWebhookInput, _clientOptions: IPayProviderPaypalClientOptions): Promise<IPayProviderVerifiedWebhook>;
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;AAE3B,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,MAAM,EAAE,wBAAwB,EAChC,cAAc,EAAE,+BAA+B,GAC9C,OAAO,CAAC,2BAA2B,CAAC;IAIjC,YAAY,CAChB,MAAM,EAAE,wBAAwB,EAChC,cAAc,EAAE,+BAA+B,GAC9C,OAAO,CAAC,2BAA2B,CAAC;IAIjC,YAAY,CAChB,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;CAGxC;AAED,OAAO,QAAQ,mBAAmB,CAAC;IACjC,UAAiB,kBAAkB;QACjC,mBAAmB,EAAE,yBAAyB,CAAC;KAChD;CACF"}
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(_input, _clientOptions) {
9
- this.app.throw(501, 'PayPal provider is not configured');
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
- async queryPayment(_input, _clientOptions) {
12
- this.app.throw(501, 'PayPal provider is not configured');
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
- async createRefund(_input, _clientOptions) {
15
- this.app.throw(501, 'PayPal provider is not configured');
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
- async verifyWebhook(_input, _clientOptions) {
18
- this.app.throw(501, 'PayPal webhook verification is not configured');
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: 'sandbox',
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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"}
@@ -1,2 +1,2 @@
1
- export {};
1
+ export * from './paypalGateway.ts';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC"}
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"}
@@ -3,6 +3,8 @@ declare module 'vona' {
3
3
  PAYPAL_CLIENT_ID: string | undefined;
4
4
  PAYPAL_CLIENT_SECRET: string | undefined;
5
5
  PAYPAL_WEBHOOK_ID: string | undefined;
6
+ PAYPAL_MERCHANT_REFERENCE: string | undefined;
7
+ PAYPAL_ENVIRONMENT: 'sandbox' | 'live';
6
8
  }
7
9
  }
8
10
  export {};
@@ -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;KACvC;CACF;AAED,OAAO,EAAE,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.2",
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
- "vona-module-a-pay": "5.0.2"
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": "95488ea0a7779f34f6ebddeca7719d8545bc55b3"
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
- _input: IPayProviderPaymentInput,
60
- _clientOptions: IPayProviderPaypalClientOptions,
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.app.throw(501, 'PayPal provider is not configured');
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
- _input: IPayProviderPaymentInput,
67
- _clientOptions: IPayProviderPaypalClientOptions,
133
+ input: IPayProviderPaymentInput,
134
+ clientOptions: IPayProviderPaypalClientOptions,
68
135
  ): Promise<IPayProviderPaymentSnapshot> {
69
- this.app.throw(501, 'PayPal provider is not configured');
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
- _input: IPayProviderRefundInput,
74
- _clientOptions: IPayProviderPaypalClientOptions,
145
+ input: IPayProviderRefundInput,
146
+ clientOptions: IPayProviderPaypalClientOptions,
75
147
  ): Promise<IPayProviderRefundSnapshot> {
76
- this.app.throw(501, 'PayPal provider is not configured');
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
- _input: IPayProviderWebhookInput,
81
- _clientOptions: IPayProviderPaypalClientOptions,
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
- this.app.throw(501, 'PayPal webhook verification is not configured');
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
+ }
@@ -3,6 +3,8 @@ declare module 'vona' {
3
3
  PAYPAL_CLIENT_ID: string | undefined;
4
4
  PAYPAL_CLIENT_SECRET: string | undefined;
5
5
  PAYPAL_WEBHOOK_ID: string | undefined;
6
+ PAYPAL_MERCHANT_REFERENCE: string | undefined;
7
+ PAYPAL_ENVIRONMENT: 'sandbox' | 'live';
6
8
  }
7
9
  }
8
10