@@ -148,9 +148,7 @@ const getBraintreeErrorMessage = (response: BraintreeErrorResponseLike): string
148148 const settlementResponseText = response . transaction ?. processorSettlementResponseText ?. trim ( ) ;
149149 if ( settlementResponseText ) {
150150 const settlementResponseCode = response . transaction ?. processorSettlementResponseCode ?. trim ( ) ;
151- return settlementResponseCode
152- ? `${ settlementResponseText } (${ settlementResponseCode } )`
153- : settlementResponseText ;
151+ return settlementResponseCode ? `${ settlementResponseText } (${ settlementResponseCode } )` : settlementResponseText ;
154152 }
155153
156154 const validationErrors = getBraintreeValidationErrors ( response . errors ) . map ( formatBraintreeValidationError ) ;
@@ -178,9 +176,7 @@ export function throwOnBraintreeFailure(
178176 response . transaction ?. gatewayRejectionReason ||
179177 response . transaction ?. processorResponseText ||
180178 response . transaction ?. processorSettlementResponseText ;
181- const type = hasProcessorSignal
182- ? MedusaError . Types . PAYMENT_AUTHORIZATION_ERROR
183- : MedusaError . Types . INVALID_DATA ;
179+ const type = hasProcessorSignal ? MedusaError . Types . PAYMENT_AUTHORIZATION_ERROR : MedusaError . Types . INVALID_DATA ;
184180
185181 log ( `${ operation } failed` , new Error ( message ) , {
186182 ...context ,
@@ -228,7 +224,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
228224 this . options_ = options ;
229225 this . logger = container [ ContainerRegistrationKeys . LOGGER ] ;
230226 this . cache = container [ Modules . CACHE ] ;
231- this . init ( ) ;
227+ this . gateway = this . init ( ) ;
232228 }
233229
234230 async saveClientTokenToCache ( clientToken : string , customerId : string , expiresOnEpochSeconds : number ) : Promise < void > {
@@ -263,9 +259,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
263259 }
264260
265261 private isTestForceSettledEnabled ( ) : boolean {
266- return (
267- process . env . TEST_FORCE_SETTLED === 'true' && this . options_ . environment . toLowerCase ( ) === 'sandbox'
268- ) ;
262+ return process . env . TEST_FORCE_SETTLED === 'true' && this . options_ . environment . toLowerCase ( ) === 'sandbox' ;
269263 }
270264
271265 async getValidClientToken (
@@ -314,7 +308,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
314308 return result . data as BraintreePaymentSessionData ;
315309 }
316310
317- init ( ) : void {
311+ init ( ) : Braintree . BraintreeGateway {
318312 const envKey = ( this . options_ . environment || 'sandbox' ) . toLowerCase ( ) ;
319313 const envMap : Record < string , Braintree . Environment > = {
320314 qa : Braintree . Environment . Qa ,
@@ -334,13 +328,17 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
334328 } ) ;
335329
336330 this . logDebug ( `Gateway initialized (environment: ${ envKey } )` ) ;
331+ return this . gateway ;
337332 }
338333
339334 static validateOptions ( options : BraintreeOptions ) : void {
340335 const requiredFields = [ 'merchantId' , 'publicKey' , 'privateKey' , 'webhookSecret' , 'environment' ] ;
341336
342337 for ( const field of requiredFields ) {
343- if ( ! isDefined ( options [ field ] ) || typeof options [ field ] !== 'string' ) {
338+ if (
339+ ! isDefined ( options [ field as keyof BraintreeOptions ] ) ||
340+ typeof options [ field as keyof BraintreeOptions ] !== 'string'
341+ ) {
344342 throw new MedusaError (
345343 MedusaError . Types . INVALID_ARGUMENT ,
346344 `Required option "${ field } " is missing or invalid in Braintree plugin` ,
@@ -364,7 +362,10 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
364362
365363 const booleanFields = [ 'enable3DSecure' , 'savePaymentMethod' , 'autoCapture' , 'allowRefundOnRefunded' , 'logging' ] ;
366364 for ( const field of booleanFields ) {
367- if ( isDefined ( options [ field ] ) && typeof options [ field ] !== 'boolean' ) {
365+ if (
366+ isDefined ( options [ field as keyof BraintreeOptions ] ) &&
367+ typeof options [ field as keyof BraintreeOptions ] !== 'boolean'
368+ ) {
368369 throw new MedusaError (
369370 MedusaError . Types . INVALID_ARGUMENT ,
370371 `Option "${ field } " must be a boolean in Braintree plugin` ,
@@ -815,9 +816,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
815816
816817 if ( process . env . TEST_FORCE_SETTLED === 'true' ) {
817818 if ( ! this . isTestForceSettledEnabled ( ) ) {
818- this . logger . warn (
819- '[Braintree refund] TEST_FORCE_SETTLED ignored — only supported when environment is sandbox' ,
820- ) ;
819+ this . logger . warn ( '[Braintree refund] TEST_FORCE_SETTLED ignored — only supported when environment is sandbox' ) ;
821820 } else {
822821 shouldVoid = false ;
823822 await this . gateway . testing . settle ( transaction . id ) ;
@@ -839,16 +838,18 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
839838 }
840839
841840 const voidedTransaction = voidResponse ?. transaction ?? ( await this . retrieveTransaction ( transaction . id ) ) ;
842-
841+ const braintreeRefund = {
842+ success : true ,
843+ transactionId : voidedTransaction ?. id ,
844+ type : 'void' ,
845+ } ;
846+ const priorRefunds = Array . isArray ( input . data ?. braintreeRefunds ) ? input . data . braintreeRefunds : [ ] ;
847+ // we need to preserve the original transaction data and store the refund history separately
843848 const refundResult : RefundPaymentOutput = {
844849 data : {
845850 ...input . data ,
846- transaction : voidedTransaction ,
847- braintreeRefund : {
848- success : true ,
849- transactionId : voidedTransaction ?. id ,
850- type : 'void' ,
851- } ,
851+ transaction : transaction ,
852+ braintreeRefunds : [ ...priorRefunds , braintreeRefund ] ,
852853 } ,
853854 } ;
854855
@@ -861,7 +862,7 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
861862 ) ;
862863 throw new MedusaError (
863864 MedusaError . Types . NOT_FOUND ,
864- `Braintree transaction with ID ${ transaction . id } cannot be refunded` ,
865+ `Braintree transaction with ID ${ transaction . id } cannot be refunded because it's in status ${ transaction . status } ` ,
865866 ) ;
866867 }
867868
@@ -885,11 +886,19 @@ class BraintreeBase extends AbstractPaymentProvider<BraintreeOptions> {
885886
886887 const refundTransaction = refundResponse . transaction ?? ( await this . retrieveTransaction ( transaction . id ) ) ;
887888
889+ const braintreeRefund = {
890+ success : true ,
891+ transactionId : refundTransaction ?. id ,
892+ type : 'refund' ,
893+ } ;
894+ const priorRefunds = Array . isArray ( input . data ?. braintreeRefunds ) ? input . data . braintreeRefunds : [ ] ;
895+ // we need to preserve the original transaction data and store the refund history separately. This is to support multiple partial refunds
896+
888897 const refundResult : RefundPaymentOutput = {
889898 data : {
890899 ...input . data ,
891- transaction : refundTransaction ,
892- braintreeRefund : refundTransaction ,
900+ transaction : transaction ,
901+ braintreeRefunds : [ ... priorRefunds , braintreeRefund ] ,
893902 } ,
894903 } ;
895904
0 commit comments