feat: Add support for listing saved payment methods in module and Stripe (#10994)
This commit is contained in:
1 parent
114b2133aa
commit
f99f720dd4
8 files changed
+171
No files matched your search
@@ -1,6 +1,7 @@
|
|||||||
import { BaseFilterable } from "../dal"
|
import { BaseFilterable } from "../dal"
|
||||||
import { OperatorMap } from "../dal/utils"
|
import { OperatorMap } from "../dal/utils"
|
||||||
import { BigNumberValue } from "../totals"
|
import { BigNumberValue } from "../totals"
|
||||||
|
import { PaymentProviderContext } from "./provider"
|
||||||
/* ********** PAYMENT COLLECTION ********** */
|
/* ********** PAYMENT COLLECTION ********** */
|
||||||
|
|
||||||
export type PaymentCollectionStatus =
|
export type PaymentCollectionStatus =
|
||||||
@@ -245,6 +246,22 @@ export interface FilterableRefundProps extends BaseFilterable<RefundDTO> {
|
|||||||
*/
|
*/
|
||||||
deleted_at?: OperatorMap<string>
|
deleted_at?: OperatorMap<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The filters to apply on the retrieved payment sessions.
|
||||||
|
*/
|
||||||
|
export interface FilterablePaymentMethodProps {
|
||||||
|
/**
|
||||||
|
* Filter the payment methods by provider.
|
||||||
|
*/
|
||||||
|
provider_id: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter the payment methods by the context of their associated payment provider.
|
||||||
|
*/
|
||||||
|
context: PaymentProviderContext
|
||||||
|
}
|
||||||
|
|
||||||
/* ********** PAYMENT ********** */
|
/* ********** PAYMENT ********** */
|
||||||
export interface PaymentDTO {
|
export interface PaymentDTO {
|
||||||
/**
|
/**
|
||||||
@@ -568,6 +585,18 @@ export interface PaymentProviderDTO {
|
|||||||
is_enabled: boolean
|
is_enabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PaymentMethodDTO {
|
||||||
|
/**
|
||||||
|
* The ID of the payment method in the payment provider's system.
|
||||||
|
*/
|
||||||
|
id: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data of the payment method, as returned by the payment provider.
|
||||||
|
*/
|
||||||
|
data: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The filters to apply on the retrieved payment providers.
|
* The filters to apply on the retrieved payment providers.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -135,6 +135,11 @@ export type PaymentProviderAuthorizeResponse = {
|
|||||||
data: PaymentProviderSessionResponse["data"]
|
data: PaymentProviderSessionResponse["data"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PaymentMethodResponse = {
|
||||||
|
id: string
|
||||||
|
data: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @interface
|
* @interface
|
||||||
*
|
*
|
||||||
@@ -249,6 +254,10 @@ export interface IPaymentProvider {
|
|||||||
paymentSessionData: Record<string, unknown>
|
paymentSessionData: Record<string, unknown>
|
||||||
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
|
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
|
||||||
|
|
||||||
|
listPaymentMethods(
|
||||||
|
context: PaymentProviderContext
|
||||||
|
): Promise<PaymentMethodResponse[]>
|
||||||
|
|
||||||
getPaymentStatus(
|
getPaymentStatus(
|
||||||
paymentSessionData: Record<string, unknown>
|
paymentSessionData: Record<string, unknown>
|
||||||
): Promise<PaymentSessionStatus>
|
): Promise<PaymentSessionStatus>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
CreatePaymentProviderSession,
|
CreatePaymentProviderSession,
|
||||||
IPaymentProvider,
|
IPaymentProvider,
|
||||||
|
PaymentMethodResponse,
|
||||||
|
PaymentProviderContext,
|
||||||
PaymentProviderError,
|
PaymentProviderError,
|
||||||
PaymentProviderSessionResponse,
|
PaymentProviderSessionResponse,
|
||||||
PaymentSessionStatus,
|
PaymentSessionStatus,
|
||||||
@@ -623,6 +625,59 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
|||||||
context: UpdatePaymentProviderSession
|
context: UpdatePaymentProviderSession
|
||||||
): Promise<PaymentProviderError | PaymentProviderSessionResponse>
|
): Promise<PaymentProviderError | PaymentProviderSessionResponse>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List the payment methods associated with the context (eg. customer) of the payment provider, if any.
|
||||||
|
*
|
||||||
|
* @param context - The context for which the payment methods are listed. Usually the customer should be provided.
|
||||||
|
* @returns An object whose `payment_methods` property is set to the data returned by the payment provider.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // other imports...
|
||||||
|
* import {
|
||||||
|
* PaymentProviderContext,
|
||||||
|
* PaymentProviderError,
|
||||||
|
* PaymentMethodResponse
|
||||||
|
* PaymentProviderSessionResponse,
|
||||||
|
* } from "@medusajs/framework/types"
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* class MyPaymentProviderService extends AbstractPaymentProvider<
|
||||||
|
* Options
|
||||||
|
* > {
|
||||||
|
* async listPaymentMethods(
|
||||||
|
* context: PaymentProviderContext
|
||||||
|
* ): Promise<PaymentMethodResponse> {
|
||||||
|
* const {
|
||||||
|
* customer,
|
||||||
|
* } = context
|
||||||
|
* const externalCustomerId = customer.metadata.stripe_id
|
||||||
|
*
|
||||||
|
* try {
|
||||||
|
* // assuming you have a client that updates the payment
|
||||||
|
* const response = await this.client.listPaymentMethods(
|
||||||
|
* {customer: externalCustomerId}
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* return response.map((method) => ({
|
||||||
|
* id: method.id,
|
||||||
|
* data: method
|
||||||
|
* }))
|
||||||
|
* } catch (e) {
|
||||||
|
* return {
|
||||||
|
* error: e,
|
||||||
|
* code: "unknown",
|
||||||
|
* detail: e
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
abstract listPaymentMethods(
|
||||||
|
context: PaymentProviderContext
|
||||||
|
): Promise<PaymentMethodResponse[]>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is executed when a webhook event is received from the third-party payment provider. Use it
|
* This method is executed when a webhook event is received from the third-party payment provider. Use it
|
||||||
* to process the action of the payment provider.
|
* to process the action of the payment provider.
|
||||||
|
|||||||
@@ -23,4 +23,13 @@ export const joinerConfig = defineJoinerConfig(Modules.PAYMENT, {
|
|||||||
payment_provider_id: PaymentProvider.name,
|
payment_provider_id: PaymentProvider.name,
|
||||||
refund_reason_id: RefundReason.name,
|
refund_reason_id: RefundReason.name,
|
||||||
},
|
},
|
||||||
|
alias: [
|
||||||
|
{
|
||||||
|
name: ["payment_method", "payment_methods"],
|
||||||
|
entity: "PaymentMethod",
|
||||||
|
args: {
|
||||||
|
methodSuffix: "PaymentMethods",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
CreatePaymentProviderSession,
|
CreatePaymentProviderSession,
|
||||||
|
PaymentMethodResponse,
|
||||||
PaymentProviderError,
|
PaymentProviderError,
|
||||||
PaymentProviderSessionResponse,
|
PaymentProviderSessionResponse,
|
||||||
ProviderWebhookPayload,
|
ProviderWebhookPayload,
|
||||||
@@ -72,6 +73,10 @@ export class SystemProviderService extends AbstractPaymentProvider {
|
|||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async listPaymentMethods(_): Promise<PaymentMethodResponse[]> {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
async getWebhookActionAndData(
|
async getWebhookActionAndData(
|
||||||
data: ProviderWebhookPayload["payload"]
|
data: ProviderWebhookPayload["payload"]
|
||||||
): Promise<WebhookActionResult> {
|
): Promise<WebhookActionResult> {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
CreateRefundDTO,
|
CreateRefundDTO,
|
||||||
DAL,
|
DAL,
|
||||||
FilterablePaymentCollectionProps,
|
FilterablePaymentCollectionProps,
|
||||||
|
FilterablePaymentMethodProps,
|
||||||
FilterablePaymentProviderProps,
|
FilterablePaymentProviderProps,
|
||||||
FilterablePaymentSessionProps,
|
FilterablePaymentSessionProps,
|
||||||
FindConfig,
|
FindConfig,
|
||||||
@@ -20,6 +21,7 @@ import {
|
|||||||
PaymentCollectionDTO,
|
PaymentCollectionDTO,
|
||||||
PaymentCollectionUpdatableFields,
|
PaymentCollectionUpdatableFields,
|
||||||
PaymentDTO,
|
PaymentDTO,
|
||||||
|
PaymentMethodDTO,
|
||||||
PaymentProviderDTO,
|
PaymentProviderDTO,
|
||||||
PaymentSessionDTO,
|
PaymentSessionDTO,
|
||||||
ProviderWebhookPayload,
|
ProviderWebhookPayload,
|
||||||
@@ -906,6 +908,33 @@ export default class PaymentModuleService
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@InjectManager()
|
||||||
|
async listPaymentMethods(
|
||||||
|
filters: FilterablePaymentMethodProps,
|
||||||
|
config: FindConfig<PaymentMethodDTO> = {},
|
||||||
|
@MedusaContext() sharedContext?: Context
|
||||||
|
): Promise<PaymentMethodDTO[]> {
|
||||||
|
return await this.paymentProviderService_.listPaymentMethods(
|
||||||
|
filters.provider_id,
|
||||||
|
filters.context
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectManager()
|
||||||
|
async listAndCountPaymentMethods(
|
||||||
|
filters: FilterablePaymentMethodProps,
|
||||||
|
config: FindConfig<PaymentMethodDTO> = {},
|
||||||
|
@MedusaContext() sharedContext?: Context
|
||||||
|
): Promise<[PaymentMethodDTO[], number]> {
|
||||||
|
const paymentMethods =
|
||||||
|
await this.paymentProviderService_.listPaymentMethods(
|
||||||
|
filters.provider_id,
|
||||||
|
filters.context
|
||||||
|
)
|
||||||
|
|
||||||
|
return [paymentMethods, paymentMethods.length]
|
||||||
|
}
|
||||||
|
|
||||||
@InjectManager()
|
@InjectManager()
|
||||||
private async maybeUpdatePaymentCollection_(
|
private async maybeUpdatePaymentCollection_(
|
||||||
paymentCollectionId: string,
|
paymentCollectionId: string,
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import {
|
|||||||
DAL,
|
DAL,
|
||||||
IPaymentProvider,
|
IPaymentProvider,
|
||||||
Logger,
|
Logger,
|
||||||
|
PaymentMethodResponse,
|
||||||
PaymentProviderAuthorizeResponse,
|
PaymentProviderAuthorizeResponse,
|
||||||
|
PaymentProviderContext,
|
||||||
PaymentProviderDataInput,
|
PaymentProviderDataInput,
|
||||||
PaymentProviderError,
|
PaymentProviderError,
|
||||||
PaymentProviderSessionResponse,
|
PaymentProviderSessionResponse,
|
||||||
@@ -150,6 +152,14 @@ Please make sure that the provider is registered in the container and it is conf
|
|||||||
return res as Record<string, unknown>
|
return res as Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async listPaymentMethods(
|
||||||
|
providerId: string,
|
||||||
|
context: PaymentProviderContext
|
||||||
|
): Promise<PaymentMethodResponse[]> {
|
||||||
|
const provider = this.retrieveProvider(providerId)
|
||||||
|
return await provider.listPaymentMethods(context)
|
||||||
|
}
|
||||||
|
|
||||||
async getWebhookActionAndData(
|
async getWebhookActionAndData(
|
||||||
providerId: string,
|
providerId: string,
|
||||||
data: ProviderWebhookPayload["payload"]
|
data: ProviderWebhookPayload["payload"]
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import Stripe from "stripe"
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
CreatePaymentProviderSession,
|
CreatePaymentProviderSession,
|
||||||
|
PaymentMethodResponse,
|
||||||
|
PaymentProviderContext,
|
||||||
PaymentProviderError,
|
PaymentProviderError,
|
||||||
PaymentProviderSessionResponse,
|
PaymentProviderSessionResponse,
|
||||||
ProviderWebhookPayload,
|
ProviderWebhookPayload,
|
||||||
@@ -86,6 +88,8 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
|||||||
|
|
||||||
res.payment_method = extra?.payment_method as string | undefined
|
res.payment_method = extra?.payment_method as string | undefined
|
||||||
|
|
||||||
|
res.return_url = extra?.return_url as string | undefined
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,6 +298,27 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async listPaymentMethods(
|
||||||
|
context: PaymentProviderContext
|
||||||
|
): Promise<PaymentMethodResponse[]> {
|
||||||
|
const customerId = context.customer?.metadata?.stripe_id
|
||||||
|
if (!customerId) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentMethods = await this.stripe_.customers.listPaymentMethods(
|
||||||
|
customerId as string,
|
||||||
|
// In order to keep the interface simple, we just list the maximum payment methods, which should be enough in almost all cases.
|
||||||
|
// We can always extend the interface to allow additional filtering, if necessary.
|
||||||
|
{ limit: 100 }
|
||||||
|
)
|
||||||
|
|
||||||
|
return paymentMethods.data.map((method) => ({
|
||||||
|
id: method.id,
|
||||||
|
data: method as unknown as Record<string, unknown>,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
async getWebhookActionAndData(
|
async getWebhookActionAndData(
|
||||||
webhookData: ProviderWebhookPayload["payload"]
|
webhookData: ProviderWebhookPayload["payload"]
|
||||||
): Promise<WebhookActionResult> {
|
): Promise<WebhookActionResult> {
|
||||||
|
|||||||
Reference in new issue
Block a user