vona-module-pay-paypal 5.0.2 → 5.0.3

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