Skip to content

Commit c9b4948

Browse files
sradevskiolivermrbl
authored andcommitted
feat: Improvements to payment module and Stripe provider (medusajs#10980)
* fix: Correctly parse Stripe error, remove unused method * fix: Isolate the payment provider error check function * fix: Allow passing few extra parameters to Stripe --------- Co-authored-by: Oli Juhl <[email protected]>
1 parent 459c2c5 commit c9b4948

File tree

4 files changed

+62
-96
lines changed

4 files changed

+62
-96
lines changed

packages/core/utils/src/payment/abstract-payment-provider.ts

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
3939
static validateOptions(options: Record<any, any>): void | never {}
4040

4141
/**
42-
* The constructor allows you to access resources from the [module's container](https://docs.medusajs.com/learn/fundamentals/modules/container)
42+
* The constructor allows you to access resources from the [module's container](https://docs.medusajs.com/learn/fundamentals/modules/container)
4343
* using the first parameter, and the module's options using the second parameter.
44-
*
44+
*
4545
* :::note
46-
*
46+
*
4747
* A module's options are passed when you register it in the Medusa application.
48-
*
48+
*
4949
* :::
5050
*
5151
* @param {Record<string, unknown>} cradle - The module's container cradle used to resolve resources.
@@ -55,35 +55,35 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
5555
* @example
5656
* import { AbstractPaymentProvider } from "@medusajs/framework/utils"
5757
* import { Logger } from "@medusajs/framework/types"
58-
*
58+
*
5959
* type Options = {
6060
* apiKey: string
6161
* }
62-
*
62+
*
6363
* type InjectedDependencies = {
6464
* logger: Logger
6565
* }
66-
*
66+
*
6767
* class MyPaymentProviderService extends AbstractPaymentProvider<Options> {
6868
* protected logger_: Logger
6969
* protected options_: Options
7070
* // assuming you're initializing a client
7171
* protected client
72-
*
72+
*
7373
* constructor(
7474
* container: InjectedDependencies,
7575
* options: Options
7676
* ) {
7777
* super(container, options)
78-
*
78+
*
7979
* this.logger_ = container.logger
8080
* this.options_ = options
81-
*
81+
*
8282
* // TODO initialize your client
8383
* }
8484
* // ...
8585
* }
86-
*
86+
*
8787
* export default MyPaymentProviderService
8888
*/
8989
protected constructor(
@@ -697,16 +697,3 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
697697
data: ProviderWebhookPayload["payload"]
698698
): Promise<WebhookActionResult>
699699
}
700-
701-
/**
702-
* @ignore
703-
*/
704-
export function isPaymentProviderError(obj: any): obj is PaymentProviderError {
705-
return (
706-
obj &&
707-
typeof obj === "object" &&
708-
"error" in obj &&
709-
"code" in obj &&
710-
"detail" in obj
711-
)
712-
}

packages/modules/payment/integration-tests/__tests__/services/payment-module/index.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,6 @@ moduleIntegrationTestRunner<IPaymentModuleService>({
556556
data: {},
557557
context: {
558558
extra: {},
559-
resource_id: "test",
560559
561560
billing_address: {},
562561
customer: {},

packages/modules/payment/src/services/payment-provider.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@ import {
1313
UpdatePaymentProviderSession,
1414
WebhookActionResult,
1515
} from "@medusajs/framework/types"
16-
import {
17-
isPaymentProviderError,
18-
MedusaError,
19-
ModulesSdkUtils,
20-
} from "@medusajs/framework/utils"
16+
import { MedusaError, ModulesSdkUtils } from "@medusajs/framework/utils"
2117
import { PaymentProvider } from "@models"
2218
import { EOL } from "os"
2319

@@ -171,3 +167,13 @@ Please make sure that the provider is registered in the container and it is conf
171167
)
172168
}
173169
}
170+
171+
function isPaymentProviderError(obj: any): obj is PaymentProviderError {
172+
return (
173+
obj &&
174+
typeof obj === "object" &&
175+
"error" in obj &&
176+
"code" in obj &&
177+
"detail" in obj
178+
)
179+
}

packages/modules/providers/payment-stripe/src/core/stripe-base.ts

Lines changed: 40 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { EOL } from "os"
2-
31
import Stripe from "stripe"
42

53
import {
@@ -13,9 +11,7 @@ import {
1311
import {
1412
AbstractPaymentProvider,
1513
isDefined,
16-
isPaymentProviderError,
1714
isPresent,
18-
MedusaError,
1915
PaymentActions,
2016
PaymentSessionStatus,
2117
} from "@medusajs/framework/utils"
@@ -60,23 +56,37 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
6056
return this.options_
6157
}
6258

63-
getPaymentIntentOptions(): PaymentIntentOptions {
64-
const options: PaymentIntentOptions = {}
59+
normalizePaymentIntentParameters(
60+
extra?: Record<string, unknown>
61+
): Partial<Stripe.PaymentIntentCreateParams> {
62+
const res = {} as Partial<Stripe.PaymentIntentCreateParams>
6563

66-
if (this?.paymentIntentOptions?.capture_method) {
67-
options.capture_method = this.paymentIntentOptions.capture_method
68-
}
64+
res.description = (extra?.payment_description ??
65+
this.options_?.paymentDescription) as string
6966

70-
if (this?.paymentIntentOptions?.setup_future_usage) {
71-
options.setup_future_usage = this.paymentIntentOptions.setup_future_usage
72-
}
67+
res.capture_method =
68+
(extra?.capture_method as "automatic" | "manual") ??
69+
this.paymentIntentOptions.capture_method ??
70+
(this.options_.capture ? "automatic" : "manual")
7371

74-
if (this?.paymentIntentOptions?.payment_method_types) {
75-
options.payment_method_types =
76-
this.paymentIntentOptions.payment_method_types
77-
}
72+
res.setup_future_usage =
73+
(extra?.setup_future_usage as "off_session" | "on_session" | undefined) ??
74+
this.paymentIntentOptions.setup_future_usage
75+
76+
res.payment_method_types = this.paymentIntentOptions
77+
.payment_method_types as string[]
78+
79+
res.automatic_payment_methods =
80+
(extra?.automatic_payment_methods as { enabled: true } | undefined) ??
81+
(this.options_?.automaticPaymentMethods ? { enabled: true } : undefined)
7882

79-
return options
83+
res.off_session = extra?.off_session as boolean | undefined
84+
85+
res.confirm = extra?.confirm as boolean | undefined
86+
87+
res.payment_method = extra?.payment_method as string | undefined
88+
89+
return res
8090
}
8191

8292
async getPaymentStatus(
@@ -106,24 +116,16 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
106116
async initiatePayment(
107117
input: CreatePaymentProviderSession
108118
): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
109-
const intentRequestData = this.getPaymentIntentOptions()
110119
const { email, extra, session_id, customer } = input.context
111120
const { currency_code, amount } = input
112121

113-
const description = (extra?.payment_description ??
114-
this.options_?.paymentDescription) as string
122+
const additionalParameters = this.normalizePaymentIntentParameters(extra)
115123

116124
const intentRequest: Stripe.PaymentIntentCreateParams = {
117-
description,
118125
amount: getSmallestUnit(amount, currency_code),
119126
currency: currency_code,
120127
metadata: { session_id: session_id! },
121-
capture_method: this.options_.capture ? "automatic" : "manual",
122-
...intentRequestData,
123-
}
124-
125-
if (this.options_?.automaticPaymentMethods) {
126-
intentRequest.automatic_payment_methods = { enabled: true }
128+
...additionalParameters,
127129
}
128130

129131
if (customer?.metadata?.stripe_id) {
@@ -273,15 +275,7 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
273275
const stripeId = context.customer?.metadata?.stripe_id
274276

275277
if (stripeId !== data.customer) {
276-
const result = await this.initiatePayment(input)
277-
if (isPaymentProviderError(result)) {
278-
return this.buildError(
279-
"An error occurred in updatePayment during the initiate of the new payment for the new customer",
280-
result
281-
)
282-
}
283-
284-
return result
278+
return await this.initiatePayment(input)
285279
} else {
286280
if (isPresent(amount) && data.amount === amountNumeric) {
287281
return { data }
@@ -300,25 +294,6 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
300294
}
301295
}
302296

303-
async updatePaymentData(sessionId: string, data: Record<string, unknown>) {
304-
try {
305-
// Prevent from updating the amount from here as it should go through
306-
// the updatePayment method to perform the correct logic
307-
if (isPresent(data.amount)) {
308-
throw new MedusaError(
309-
MedusaError.Types.INVALID_DATA,
310-
"Cannot update amount, use updatePayment instead"
311-
)
312-
}
313-
314-
return (await this.stripe_.paymentIntents.update(sessionId, {
315-
...data,
316-
})) as unknown as PaymentProviderSessionResponse["data"]
317-
} catch (e) {
318-
return this.buildError("An error occurred in updatePaymentData", e)
319-
}
320-
}
321-
322297
async getWebhookActionAndData(
323298
webhookData: ProviderWebhookPayload["payload"]
324299
): Promise<WebhookActionResult> {
@@ -374,18 +349,17 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
374349
this.options_.webhookSecret
375350
)
376351
}
377-
protected buildError(
378-
message: string,
379-
error: Stripe.StripeRawError | PaymentProviderError | Error
380-
): PaymentProviderError {
352+
protected buildError(message: string, error: Error): PaymentProviderError {
353+
const errorDetails =
354+
"raw" in error ? (error.raw as Stripe.StripeRawError) : error
355+
381356
return {
382-
error: message,
383-
code: "code" in error ? error.code : "unknown",
384-
detail: isPaymentProviderError(error)
385-
? `${error.error}${EOL}${error.detail ?? ""}`
386-
: "detail" in error
387-
? error.detail
388-
: error.message ?? "",
357+
error: `${message}: ${error.message}`,
358+
code: "code" in errorDetails ? errorDetails.code : "unknown",
359+
detail:
360+
"detail" in errorDetails
361+
? `${error.message}: ${errorDetails.detail}`
362+
: error.message,
389363
}
390364
}
391365
}

0 commit comments

Comments
 (0)