@@ -2,10 +2,12 @@ import {
2
2
AbstractPaymentProvider ,
3
3
ContainerRegistrationKeys ,
4
4
MedusaError ,
5
+ Modules ,
5
6
PaymentActions ,
6
7
PaymentSessionStatus ,
7
8
isDefined ,
8
9
} from '@medusajs/framework/utils' ;
10
+ import jsonwebtoken from 'jsonwebtoken' ;
9
11
import type {
10
12
AuthorizePaymentInput ,
11
13
AuthorizePaymentOutput ,
@@ -21,6 +23,7 @@ import type {
21
23
DeletePaymentOutput ,
22
24
GetPaymentStatusInput ,
23
25
GetPaymentStatusOutput ,
26
+ ICacheService ,
24
27
InitiatePaymentInput ,
25
28
InitiatePaymentOutput ,
26
29
Logger ,
@@ -42,7 +45,7 @@ import type {
42
45
} from '@medusajs/types' ;
43
46
import type { Transaction , TransactionNotification , TransactionStatus } from 'braintree' ;
44
47
import Braintree from 'braintree' ;
45
- import type { BraintreeOptions , CustomFields } from '../types' ;
48
+ import type { BraintreeOptions , CustomFields , DecodedClientToken , DecodedClientTokenAuthorization } from '../types' ;
46
49
import { getSmallestUnit } from '../utils/get-smallest-unit' ;
47
50
48
51
export interface BraintreePaymentSessionData {
@@ -53,23 +56,56 @@ export interface BraintreePaymentSessionData {
53
56
braintreeTransaction ?: Transaction ;
54
57
}
55
58
59
+ const buildTokenCacheKey = ( customerId : string ) => `braintree:clientToken:${ customerId } ` ;
60
+
56
61
class BraintreeBase extends AbstractPaymentProvider < BraintreeOptions > {
57
62
identifier = 'braintree' ;
58
63
protected readonly options_ : BraintreeOptions ;
59
64
protected gateway : Braintree . BraintreeGateway ;
60
65
logger : Logger ;
61
66
container_ : MedusaContainer ;
67
+ cache : ICacheService ;
62
68
protected constructor ( container : MedusaContainer , options : BraintreeOptions ) {
63
69
super ( container , options ) ;
64
70
65
71
this . options_ = options ;
66
72
this . logger = container [ ContainerRegistrationKeys . LOGGER ] ;
67
73
this . container_ = container ;
68
-
74
+ this . cache = container [ Modules . CACHE ] as unknown as ICacheService ;
69
75
this . init ( ) ;
70
76
}
71
77
72
- private parsePaymentSessionData ( data : Record < string , unknown > ) : BraintreePaymentSessionData {
78
+ async saveClientTokenToCache ( clientToken : string , customerId : string , expiresOn : number ) : Promise < void > {
79
+ const expiryTime = expiresOn * 1000 - Math . floor ( Date . now ( ) ) - 1000 ;
80
+
81
+ if ( ! customerId || ! clientToken || expiryTime < 0 ) return ;
82
+
83
+ await this . cache . set ( buildTokenCacheKey ( customerId ) , clientToken , Math . floor ( expiryTime / 1000 ) ) ;
84
+ }
85
+
86
+ async getClientTokenFromCache ( customerId : string ) : Promise < string | null > {
87
+ const token = ( await this . cache . get ( buildTokenCacheKey ( customerId ) ) ) as string | null ;
88
+ return token ;
89
+ }
90
+
91
+ async getValidClientToken ( customerId : string ) : Promise < string | null > {
92
+ if ( ! customerId ) {
93
+ const generatedToken = await this . gateway . clientToken . generate ( { } ) ;
94
+ return generatedToken . clientToken ;
95
+ }
96
+
97
+ const token = await this . getClientTokenFromCache ( customerId ) ;
98
+
99
+ if ( token ) return token ;
100
+
101
+ const generatedToken = await this . gateway . clientToken . generate ( { } ) ;
102
+ const defaultExpiryTime = Math . floor ( Date . now ( ) / 1000 ) + 24 * 3600 * 1000 ; // 24 hours default
103
+
104
+ await this . saveClientTokenToCache ( generatedToken . clientToken , customerId , defaultExpiryTime ) ;
105
+ return generatedToken . clientToken ;
106
+ }
107
+
108
+ private async parsePaymentSessionData ( data : Record < string , unknown > ) : Promise < BraintreePaymentSessionData > {
73
109
return {
74
110
clientToken : data . clientToken as string ,
75
111
amount : data . amount as number ,
@@ -79,7 +115,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
79
115
} ;
80
116
}
81
117
82
- protected init ( ) : void {
118
+ init ( ) : void {
83
119
let environment : Braintree . Environment ;
84
120
switch ( this . options_ . environment ) {
85
121
case 'qa' :
@@ -148,7 +184,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
148
184
}
149
185
150
186
async capturePayment ( input : CapturePaymentInput ) : Promise < CapturePaymentOutput > {
151
- const sessionData = this . parsePaymentSessionData ( input . data ?? { } ) ;
187
+ const sessionData = await this . parsePaymentSessionData ( input . data ?? { } ) ;
152
188
const braintreeTransaction = sessionData . braintreeTransaction ;
153
189
154
190
if ( ! braintreeTransaction ) {
@@ -179,12 +215,11 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
179
215
} ,
180
216
} ;
181
217
return capturePaymentResult ;
182
- } else {
183
- throw new MedusaError (
184
- MedusaError . Types . NOT_FOUND ,
185
- `No payments found for transaction ${ braintreeTransaction . id } ` ,
186
- ) ;
187
218
}
219
+ throw new MedusaError (
220
+ MedusaError . Types . NOT_FOUND ,
221
+ `No payments found for transaction ${ braintreeTransaction . id } ` ,
222
+ ) ;
188
223
}
189
224
case settled :
190
225
case settling :
@@ -209,7 +244,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
209
244
210
245
async authorizePayment ( input : AuthorizePaymentInput ) : Promise < AuthorizePaymentOutput > {
211
246
try {
212
- const sessionData = this . parsePaymentSessionData ( input . data ?? { } ) ;
247
+ const sessionData = await this . parsePaymentSessionData ( input . data ?? { } ) ;
213
248
214
249
this . logger . warn ( `authorizePayment: ${ JSON . stringify ( sessionData ) } ` ) ;
215
250
@@ -258,7 +293,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
258
293
}
259
294
260
295
async cancelPayment ( input : CancelPaymentInput ) : Promise < CancelPaymentOutput > {
261
- const sessionData = this . parsePaymentSessionData ( input . data ?? { } ) ;
296
+ const sessionData = await this . parsePaymentSessionData ( input . data ?? { } ) ;
262
297
const { braintreeTransaction } = await this . retrieveTransaction ( sessionData . braintreeTransaction ?. id as string ) ;
263
298
264
299
if ( ! braintreeTransaction ) {
@@ -343,13 +378,15 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
343
378
}
344
379
345
380
async initiatePayment ( input : InitiatePaymentInput ) : Promise < InitiatePaymentOutput > {
381
+ const token = await this . getValidClientToken ( input . context ?. customer ?. id as string ) ;
346
382
const paymentSessionId = input . context ?. idempotency_key ;
347
-
383
+ if ( ! token ) {
384
+ throw new MedusaError ( MedusaError . Types . INVALID_ARGUMENT , 'Failed to generate client token' ) ;
385
+ }
348
386
const { data } = input ;
349
- const tokenData = await this . gateway . clientToken . generate ( { } ) ;
350
387
351
388
const dataToSave : BraintreePaymentSessionData & { medusaPaymentSessionId : string } = {
352
- clientToken : tokenData . clientToken ,
389
+ clientToken : token ,
353
390
medusaPaymentSessionId : paymentSessionId as string ,
354
391
paymentMethodNonce : data ?. paymentMethodNonce as string ,
355
392
amount : getSmallestUnit ( input . amount , input . currency_code ) ,
@@ -367,7 +404,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
367
404
} : {
368
405
input : AuthorizePaymentInput ;
369
406
} ) : Promise < Transaction > {
370
- const sessionData = this . parsePaymentSessionData ( input . data ?? { } ) ;
407
+ const sessionData = await this . parsePaymentSessionData ( input . data ?? { } ) ;
371
408
372
409
const toPay = sessionData . amount . toString ( ) ;
373
410
@@ -399,7 +436,6 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
399
436
if ( braintreeTransaction . transaction ?. id ) {
400
437
await this . gateway . transaction . void ( braintreeTransaction . transaction . id ) ;
401
438
}
402
-
403
439
throw new MedusaError ( MedusaError . Types . INVALID_DATA , `Failed to sync payment session: ${ error . message } ` ) ;
404
440
}
405
441
} catch ( error ) {
@@ -497,7 +533,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
497
533
}
498
534
499
535
async deletePayment ( input : DeletePaymentInput ) : Promise < DeletePaymentOutput > {
500
- const sessionData = this . parsePaymentSessionData ( input . data ?? { } ) ;
536
+ const sessionData = await this . parsePaymentSessionData ( input . data ?? { } ) ;
501
537
const braintreeTransaction = sessionData . braintreeTransaction ;
502
538
503
539
if ( braintreeTransaction ) {
@@ -564,20 +600,24 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
564
600
case 'submitted_for_settlement' :
565
601
status = PaymentSessionStatus . AUTHORIZED ;
566
602
break ;
567
- case 'voided' :
603
+ case 'voided' : {
568
604
status = PaymentSessionStatus . CANCELED ;
605
+
569
606
break ;
570
- case 'failed' :
607
+ }
608
+ case 'failed' : {
571
609
status = PaymentSessionStatus . ERROR ;
610
+
572
611
break ;
612
+ }
573
613
default :
574
614
status = PaymentSessionStatus . PENDING ;
575
615
}
576
616
return { status } ;
577
617
}
578
618
579
619
async savePaymentMethod ( input : SavePaymentMethodInput ) : Promise < SavePaymentMethodOutput > {
580
- const sessionData = this . parsePaymentSessionData ( input . data ?? { } ) ;
620
+ const sessionData = await this . parsePaymentSessionData ( input . data ?? { } ) ;
581
621
582
622
const braintreeCustomerId = input . context ?. account_holder ?. data ?. id as string ;
583
623
if ( ! braintreeCustomerId ) {
@@ -611,7 +651,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
611
651
}
612
652
613
653
async refundPayment ( input : RefundPaymentInput ) : Promise < RefundPaymentOutput > {
614
- const sessionData = this . parsePaymentSessionData ( input . data ?? { } ) ;
654
+ const sessionData = await this . parsePaymentSessionData ( input . data ?? { } ) ;
615
655
const { braintreeTransaction } = await this . retrieveTransaction ( sessionData . braintreeTransaction ?. id as string ) ;
616
656
if ( ! braintreeTransaction ) {
617
657
throw new MedusaError ( MedusaError . Types . NOT_FOUND , 'Braintree transaction not found' ) ;
@@ -662,7 +702,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
662
702
} ;
663
703
return refundResult ;
664
704
} catch ( e ) {
665
- this . logger . error ( `Error creating Braintree refund: ${ e . message } ` , e ) ;
705
+ this . logger . error ( `Error creating Braintree refund: ${ e . message } ${ JSON . stringify ( e ) } ` , e ) ;
666
706
throw new MedusaError ( MedusaError . Types . INVALID_DATA , `Failed to create Braintree refund: ${ e . message } ` ) ;
667
707
}
668
708
} else {
@@ -676,7 +716,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
676
716
}
677
717
678
718
async retrievePayment ( input : RetrievePaymentInput ) : Promise < RetrievePaymentOutput > {
679
- const paymentSessionData = this . parsePaymentSessionData ( input . data ?? { } ) ;
719
+ const paymentSessionData = await this . parsePaymentSessionData ( input . data ?? { } ) ;
680
720
681
721
if ( ! paymentSessionData . braintreeTransaction ?. id ) {
682
722
throw new MedusaError ( MedusaError . Types . NOT_FOUND , 'Braintree transaction not found' ) ;
0 commit comments