feat: Improvements to payment module and Stripe provider (#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 <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
1 parent
44cd0224af
commit
f5235862c0
4 files changed
+51
-85
No files matched your search
@@ -697,16 +697,3 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
|||||||
data: ProviderWebhookPayload["payload"]
|
data: ProviderWebhookPayload["payload"]
|
||||||
): Promise<WebhookActionResult>
|
): Promise<WebhookActionResult>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @ignore
|
|
||||||
*/
|
|
||||||
export function isPaymentProviderError(obj: any): obj is PaymentProviderError {
|
|
||||||
return (
|
|
||||||
obj &&
|
|
||||||
typeof obj === "object" &&
|
|
||||||
"error" in obj &&
|
|
||||||
"code" in obj &&
|
|
||||||
"detail" in obj
|
|
||||||
)
|
|
||||||
}
|
|
||||||
-1
@@ -556,7 +556,6 @@ moduleIntegrationTestRunner<IPaymentModuleService>({
|
|||||||
data: {},
|
data: {},
|
||||||
context: {
|
context: {
|
||||||
extra: {},
|
extra: {},
|
||||||
resource_id: "test",
|
|
||||||
email: "test@test.com",
|
email: "test@test.com",
|
||||||
billing_address: {},
|
billing_address: {},
|
||||||
customer: {},
|
customer: {},
|
||||||
|
|||||||
@@ -13,11 +13,7 @@ import {
|
|||||||
UpdatePaymentProviderSession,
|
UpdatePaymentProviderSession,
|
||||||
WebhookActionResult,
|
WebhookActionResult,
|
||||||
} from "@medusajs/framework/types"
|
} from "@medusajs/framework/types"
|
||||||
import {
|
import { MedusaError, ModulesSdkUtils } from "@medusajs/framework/utils"
|
||||||
isPaymentProviderError,
|
|
||||||
MedusaError,
|
|
||||||
ModulesSdkUtils,
|
|
||||||
} from "@medusajs/framework/utils"
|
|
||||||
import { PaymentProvider } from "@models"
|
import { PaymentProvider } from "@models"
|
||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
|
|
||||||
@@ -171,3 +167,13 @@ Please make sure that the provider is registered in the container and it is conf
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPaymentProviderError(obj: any): obj is PaymentProviderError {
|
||||||
|
return (
|
||||||
|
obj &&
|
||||||
|
typeof obj === "object" &&
|
||||||
|
"error" in obj &&
|
||||||
|
"code" in obj &&
|
||||||
|
"detail" in obj
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
import { EOL } from "os"
|
|
||||||
|
|
||||||
import Stripe from "stripe"
|
import Stripe from "stripe"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -13,9 +11,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
AbstractPaymentProvider,
|
AbstractPaymentProvider,
|
||||||
isDefined,
|
isDefined,
|
||||||
isPaymentProviderError,
|
|
||||||
isPresent,
|
isPresent,
|
||||||
MedusaError,
|
|
||||||
PaymentActions,
|
PaymentActions,
|
||||||
PaymentSessionStatus,
|
PaymentSessionStatus,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
@@ -60,23 +56,37 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
|||||||
return this.options_
|
return this.options_
|
||||||
}
|
}
|
||||||
|
|
||||||
getPaymentIntentOptions(): PaymentIntentOptions {
|
normalizePaymentIntentParameters(
|
||||||
const options: PaymentIntentOptions = {}
|
extra?: Record<string, unknown>
|
||||||
|
): Partial<Stripe.PaymentIntentCreateParams> {
|
||||||
|
const res = {} as Partial<Stripe.PaymentIntentCreateParams>
|
||||||
|
|
||||||
if (this?.paymentIntentOptions?.capture_method) {
|
res.description = (extra?.payment_description ??
|
||||||
options.capture_method = this.paymentIntentOptions.capture_method
|
this.options_?.paymentDescription) as string
|
||||||
}
|
|
||||||
|
|
||||||
if (this?.paymentIntentOptions?.setup_future_usage) {
|
res.capture_method =
|
||||||
options.setup_future_usage = this.paymentIntentOptions.setup_future_usage
|
(extra?.capture_method as "automatic" | "manual") ??
|
||||||
}
|
this.paymentIntentOptions.capture_method ??
|
||||||
|
(this.options_.capture ? "automatic" : "manual")
|
||||||
|
|
||||||
if (this?.paymentIntentOptions?.payment_method_types) {
|
res.setup_future_usage =
|
||||||
options.payment_method_types =
|
(extra?.setup_future_usage as "off_session" | "on_session" | undefined) ??
|
||||||
this.paymentIntentOptions.payment_method_types
|
this.paymentIntentOptions.setup_future_usage
|
||||||
}
|
|
||||||
|
|
||||||
return options
|
res.payment_method_types = this.paymentIntentOptions
|
||||||
|
.payment_method_types as string[]
|
||||||
|
|
||||||
|
res.automatic_payment_methods =
|
||||||
|
(extra?.automatic_payment_methods as { enabled: true } | undefined) ??
|
||||||
|
(this.options_?.automaticPaymentMethods ? { enabled: true } : undefined)
|
||||||
|
|
||||||
|
res.off_session = extra?.off_session as boolean | undefined
|
||||||
|
|
||||||
|
res.confirm = extra?.confirm as boolean | undefined
|
||||||
|
|
||||||
|
res.payment_method = extra?.payment_method as string | undefined
|
||||||
|
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPaymentStatus(
|
async getPaymentStatus(
|
||||||
@@ -106,24 +116,16 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
|||||||
async initiatePayment(
|
async initiatePayment(
|
||||||
input: CreatePaymentProviderSession
|
input: CreatePaymentProviderSession
|
||||||
): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
|
): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
|
||||||
const intentRequestData = this.getPaymentIntentOptions()
|
|
||||||
const { email, extra, session_id, customer } = input.context
|
const { email, extra, session_id, customer } = input.context
|
||||||
const { currency_code, amount } = input
|
const { currency_code, amount } = input
|
||||||
|
|
||||||
const description = (extra?.payment_description ??
|
const additionalParameters = this.normalizePaymentIntentParameters(extra)
|
||||||
this.options_?.paymentDescription) as string
|
|
||||||
|
|
||||||
const intentRequest: Stripe.PaymentIntentCreateParams = {
|
const intentRequest: Stripe.PaymentIntentCreateParams = {
|
||||||
description,
|
|
||||||
amount: getSmallestUnit(amount, currency_code),
|
amount: getSmallestUnit(amount, currency_code),
|
||||||
currency: currency_code,
|
currency: currency_code,
|
||||||
metadata: { session_id: session_id! },
|
metadata: { session_id: session_id! },
|
||||||
capture_method: this.options_.capture ? "automatic" : "manual",
|
...additionalParameters,
|
||||||
...intentRequestData,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options_?.automaticPaymentMethods) {
|
|
||||||
intentRequest.automatic_payment_methods = { enabled: true }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customer?.metadata?.stripe_id) {
|
if (customer?.metadata?.stripe_id) {
|
||||||
@@ -273,15 +275,7 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
|||||||
const stripeId = context.customer?.metadata?.stripe_id
|
const stripeId = context.customer?.metadata?.stripe_id
|
||||||
|
|
||||||
if (stripeId !== data.customer) {
|
if (stripeId !== data.customer) {
|
||||||
const result = await this.initiatePayment(input)
|
return await this.initiatePayment(input)
|
||||||
if (isPaymentProviderError(result)) {
|
|
||||||
return this.buildError(
|
|
||||||
"An error occurred in updatePayment during the initiate of the new payment for the new customer",
|
|
||||||
result
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
} else {
|
} else {
|
||||||
if (isPresent(amount) && data.amount === amountNumeric) {
|
if (isPresent(amount) && data.amount === amountNumeric) {
|
||||||
return { data }
|
return { data }
|
||||||
@@ -300,25 +294,6 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updatePaymentData(sessionId: string, data: Record<string, unknown>) {
|
|
||||||
try {
|
|
||||||
// Prevent from updating the amount from here as it should go through
|
|
||||||
// the updatePayment method to perform the correct logic
|
|
||||||
if (isPresent(data.amount)) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.INVALID_DATA,
|
|
||||||
"Cannot update amount, use updatePayment instead"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (await this.stripe_.paymentIntents.update(sessionId, {
|
|
||||||
...data,
|
|
||||||
})) as unknown as PaymentProviderSessionResponse["data"]
|
|
||||||
} catch (e) {
|
|
||||||
return this.buildError("An error occurred in updatePaymentData", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getWebhookActionAndData(
|
async getWebhookActionAndData(
|
||||||
webhookData: ProviderWebhookPayload["payload"]
|
webhookData: ProviderWebhookPayload["payload"]
|
||||||
): Promise<WebhookActionResult> {
|
): Promise<WebhookActionResult> {
|
||||||
@@ -374,18 +349,17 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
|||||||
this.options_.webhookSecret
|
this.options_.webhookSecret
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
protected buildError(
|
protected buildError(message: string, error: Error): PaymentProviderError {
|
||||||
message: string,
|
const errorDetails =
|
||||||
error: Stripe.StripeRawError | PaymentProviderError | Error
|
"raw" in error ? (error.raw as Stripe.StripeRawError) : error
|
||||||
): PaymentProviderError {
|
|
||||||
return {
|
return {
|
||||||
error: message,
|
error: `${message}: ${error.message}`,
|
||||||
code: "code" in error ? error.code : "unknown",
|
code: "code" in errorDetails ? errorDetails.code : "unknown",
|
||||||
detail: isPaymentProviderError(error)
|
detail:
|
||||||
? `${error.error}${EOL}${error.detail ?? ""}`
|
"detail" in errorDetails
|
||||||
: "detail" in error
|
? `${error.message}: ${errorDetails.detail}`
|
||||||
? error.detail
|
: error.message,
|
||||||
: error.message ?? "",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user