Skip to content

Commit e1a5fe5

Browse files
authored
Merge pull request #21 from lambda-curry/fix/cached-token-2
2 parents d651144 + 597fb7a commit e1a5fe5

File tree

12 files changed

+3329
-3130
lines changed

12 files changed

+3329
-3130
lines changed

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
yarn-error.log
77

88
.idea
9-
9+
*.tgz
1010
coverage
11-
11+
launch.json
1212
!src/**
13+
.medusa/
1314

1415
./tsconfig.tsbuildinfo
1516
medusa-db.sql

plugins/braintree-payment/.medusa/server/src/index.d.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

plugins/braintree-payment/.medusa/server/src/providers/payment-braintree/src/core/braintree-base.d.ts

Lines changed: 0 additions & 44 deletions
This file was deleted.

plugins/braintree-payment/.medusa/server/src/providers/payment-braintree/src/types/index.d.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.

plugins/braintree-payment/.medusa/server/src/types/index.d.ts

Lines changed: 0 additions & 2 deletions
This file was deleted.

plugins/braintree-payment/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ BRAINTREE_ENABLE_3D_SECURE=true|false
5555
Add the following configuration to the `payment` section of your `medusa-config.js` or `config.ts` file:
5656

5757
```javascript
58+
dependencies:[Modules.CACHE]
5859
{
5960
resolve: '@lambdacurry/medusa-payment-braintree/providers/payment-braintree',
6061
id: 'braintree',

plugins/braintree-payment/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@lambdacurry/medusa-payment-braintree",
3-
"version": "0.0.7",
3+
"version": "0.0.8",
44
"description": "Braintree plugin for Medusa",
55
"author": "Lambda Curry (https://lambdacurry.dev)",
66
"license": "MIT",
@@ -46,7 +46,9 @@
4646
"@medusajs/test-utils": "^2.8.2",
4747
"@medusajs/ui": "^4.0.3",
4848
"@swc/core": "1.5.7",
49-
"@types/braintree": "^3.3.14"
49+
"@types/braintree": "^3.3.14",
50+
"@types/jsonwebtoken": "^9.0.10",
51+
"jsonwebtoken": "^9.0.2"
5052
},
5153
"scripts": {
5254
"build": "npx medusa plugin:build",
@@ -60,7 +62,8 @@
6062
"@medusajs/icons": "^2.8.2",
6163
"@medusajs/medusa": "2.8.2",
6264
"@medusajs/test-utils": "^2.8.2",
63-
"@medusajs/ui": "^4.0.3"
65+
"@medusajs/ui": "^4.0.3",
66+
"jsonwebtoken": "^9.0.2"
6467
},
6568
"engines": {
6669
"node": ">=20"
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Export types
2-
export * from './providers/payment-braintree/src/types'
3-
export * from './providers/payment-braintree/src/core/braintree-base'
2+
export * from './providers/payment-braintree/src/types';
3+
export * from './providers/payment-braintree/src/core/braintree-base';
44

55
// Export provider
6-
export { default as BraintreePaymentProvider } from './providers/payment-braintree/src'
6+
export { default as BraintreePaymentProvider } from './providers/payment-braintree/src';

plugins/braintree-payment/src/providers/payment-braintree/src/core/braintree-base.ts

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import {
22
AbstractPaymentProvider,
33
ContainerRegistrationKeys,
44
MedusaError,
5+
Modules,
56
PaymentActions,
67
PaymentSessionStatus,
78
isDefined,
89
} from '@medusajs/framework/utils';
10+
import jsonwebtoken from 'jsonwebtoken';
911
import type {
1012
AuthorizePaymentInput,
1113
AuthorizePaymentOutput,
@@ -21,6 +23,7 @@ import type {
2123
DeletePaymentOutput,
2224
GetPaymentStatusInput,
2325
GetPaymentStatusOutput,
26+
ICacheService,
2427
InitiatePaymentInput,
2528
InitiatePaymentOutput,
2629
Logger,
@@ -42,7 +45,7 @@ import type {
4245
} from '@medusajs/types';
4346
import type { Transaction, TransactionNotification, TransactionStatus } from 'braintree';
4447
import Braintree from 'braintree';
45-
import type { BraintreeOptions, CustomFields } from '../types';
48+
import type { BraintreeOptions, CustomFields, DecodedClientToken, DecodedClientTokenAuthorization } from '../types';
4649
import { getSmallestUnit } from '../utils/get-smallest-unit';
4750

4851
export interface BraintreePaymentSessionData {
@@ -53,23 +56,56 @@ export interface BraintreePaymentSessionData {
5356
braintreeTransaction?: Transaction;
5457
}
5558

59+
const buildTokenCacheKey = (customerId: string) => `braintree:clientToken:${customerId}`;
60+
5661
class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
5762
identifier = 'braintree';
5863
protected readonly options_: BraintreeOptions;
5964
protected gateway: Braintree.BraintreeGateway;
6065
logger: Logger;
6166
container_: MedusaContainer;
67+
cache: ICacheService;
6268
protected constructor(container: MedusaContainer, options: BraintreeOptions) {
6369
super(container, options);
6470

6571
this.options_ = options;
6672
this.logger = container[ContainerRegistrationKeys.LOGGER];
6773
this.container_ = container;
68-
74+
this.cache = container[Modules.CACHE] as unknown as ICacheService;
6975
this.init();
7076
}
7177

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> {
73109
return {
74110
clientToken: data.clientToken as string,
75111
amount: data.amount as number,
@@ -79,7 +115,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
79115
};
80116
}
81117

82-
protected init(): void {
118+
init(): void {
83119
let environment: Braintree.Environment;
84120
switch (this.options_.environment) {
85121
case 'qa':
@@ -148,7 +184,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
148184
}
149185

150186
async capturePayment(input: CapturePaymentInput): Promise<CapturePaymentOutput> {
151-
const sessionData = this.parsePaymentSessionData(input.data ?? {});
187+
const sessionData = await this.parsePaymentSessionData(input.data ?? {});
152188
const braintreeTransaction = sessionData.braintreeTransaction;
153189

154190
if (!braintreeTransaction) {
@@ -179,12 +215,11 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
179215
},
180216
};
181217
return capturePaymentResult;
182-
} else {
183-
throw new MedusaError(
184-
MedusaError.Types.NOT_FOUND,
185-
`No payments found for transaction ${braintreeTransaction.id}`,
186-
);
187218
}
219+
throw new MedusaError(
220+
MedusaError.Types.NOT_FOUND,
221+
`No payments found for transaction ${braintreeTransaction.id}`,
222+
);
188223
}
189224
case settled:
190225
case settling:
@@ -209,7 +244,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
209244

210245
async authorizePayment(input: AuthorizePaymentInput): Promise<AuthorizePaymentOutput> {
211246
try {
212-
const sessionData = this.parsePaymentSessionData(input.data ?? {});
247+
const sessionData = await this.parsePaymentSessionData(input.data ?? {});
213248

214249
this.logger.warn(`authorizePayment: ${JSON.stringify(sessionData)}`);
215250

@@ -258,7 +293,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
258293
}
259294

260295
async cancelPayment(input: CancelPaymentInput): Promise<CancelPaymentOutput> {
261-
const sessionData = this.parsePaymentSessionData(input.data ?? {});
296+
const sessionData = await this.parsePaymentSessionData(input.data ?? {});
262297
const { braintreeTransaction } = await this.retrieveTransaction(sessionData.braintreeTransaction?.id as string);
263298

264299
if (!braintreeTransaction) {
@@ -343,13 +378,15 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
343378
}
344379

345380
async initiatePayment(input: InitiatePaymentInput): Promise<InitiatePaymentOutput> {
381+
const token = await this.getValidClientToken(input.context?.customer?.id as string);
346382
const paymentSessionId = input.context?.idempotency_key;
347-
383+
if (!token) {
384+
throw new MedusaError(MedusaError.Types.INVALID_ARGUMENT, 'Failed to generate client token');
385+
}
348386
const { data } = input;
349-
const tokenData = await this.gateway.clientToken.generate({});
350387

351388
const dataToSave: BraintreePaymentSessionData & { medusaPaymentSessionId: string } = {
352-
clientToken: tokenData.clientToken,
389+
clientToken: token,
353390
medusaPaymentSessionId: paymentSessionId as string,
354391
paymentMethodNonce: data?.paymentMethodNonce as string,
355392
amount: getSmallestUnit(input.amount, input.currency_code),
@@ -367,7 +404,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
367404
}: {
368405
input: AuthorizePaymentInput;
369406
}): Promise<Transaction> {
370-
const sessionData = this.parsePaymentSessionData(input.data ?? {});
407+
const sessionData = await this.parsePaymentSessionData(input.data ?? {});
371408

372409
const toPay = sessionData.amount.toString();
373410

@@ -399,7 +436,6 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
399436
if (braintreeTransaction.transaction?.id) {
400437
await this.gateway.transaction.void(braintreeTransaction.transaction.id);
401438
}
402-
403439
throw new MedusaError(MedusaError.Types.INVALID_DATA, `Failed to sync payment session: ${error.message}`);
404440
}
405441
} catch (error) {
@@ -497,7 +533,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
497533
}
498534

499535
async deletePayment(input: DeletePaymentInput): Promise<DeletePaymentOutput> {
500-
const sessionData = this.parsePaymentSessionData(input.data ?? {});
536+
const sessionData = await this.parsePaymentSessionData(input.data ?? {});
501537
const braintreeTransaction = sessionData.braintreeTransaction;
502538

503539
if (braintreeTransaction) {
@@ -564,20 +600,24 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
564600
case 'submitted_for_settlement':
565601
status = PaymentSessionStatus.AUTHORIZED;
566602
break;
567-
case 'voided':
603+
case 'voided': {
568604
status = PaymentSessionStatus.CANCELED;
605+
569606
break;
570-
case 'failed':
607+
}
608+
case 'failed': {
571609
status = PaymentSessionStatus.ERROR;
610+
572611
break;
612+
}
573613
default:
574614
status = PaymentSessionStatus.PENDING;
575615
}
576616
return { status };
577617
}
578618

579619
async savePaymentMethod(input: SavePaymentMethodInput): Promise<SavePaymentMethodOutput> {
580-
const sessionData = this.parsePaymentSessionData(input.data ?? {});
620+
const sessionData = await this.parsePaymentSessionData(input.data ?? {});
581621

582622
const braintreeCustomerId = input.context?.account_holder?.data?.id as string;
583623
if (!braintreeCustomerId) {
@@ -611,7 +651,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
611651
}
612652

613653
async refundPayment(input: RefundPaymentInput): Promise<RefundPaymentOutput> {
614-
const sessionData = this.parsePaymentSessionData(input.data ?? {});
654+
const sessionData = await this.parsePaymentSessionData(input.data ?? {});
615655
const { braintreeTransaction } = await this.retrieveTransaction(sessionData.braintreeTransaction?.id as string);
616656
if (!braintreeTransaction) {
617657
throw new MedusaError(MedusaError.Types.NOT_FOUND, 'Braintree transaction not found');
@@ -662,7 +702,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
662702
};
663703
return refundResult;
664704
} 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);
666706
throw new MedusaError(MedusaError.Types.INVALID_DATA, `Failed to create Braintree refund: ${e.message}`);
667707
}
668708
} else {
@@ -676,7 +716,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
676716
}
677717

678718
async retrievePayment(input: RetrievePaymentInput): Promise<RetrievePaymentOutput> {
679-
const paymentSessionData = this.parsePaymentSessionData(input.data ?? {});
719+
const paymentSessionData = await this.parsePaymentSessionData(input.data ?? {});
680720

681721
if (!paymentSessionData.braintreeTransaction?.id) {
682722
throw new MedusaError(MedusaError.Types.NOT_FOUND, 'Braintree transaction not found');

0 commit comments

Comments
 (0)