Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for creating payment methods to payment module #11063

Merged
merged 2 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2543,7 +2543,6 @@ medusaIntegrationTestRunner({
await paymentModule.createPaymentCollections({
amount: 5001,
currency_code: "dkk",
region_id: defaultRegion.id,
})

const paymentSession = await paymentModule.createPaymentSession(
Expand Down Expand Up @@ -2615,7 +2614,6 @@ medusaIntegrationTestRunner({
await paymentModule.createPaymentCollections({
amount: 5000,
currency_code: "dkk",
region_id: defaultRegion.id,
})

const paymentSession = await paymentModule.createPaymentSession(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1330,7 +1330,6 @@ medusaIntegrationTestRunner({
).data.shipping_option

paymentCollection = await paymentService.createPaymentCollections({
region_id: region.id,
amount: 1000,
currency_code: "usd",
})
Expand Down
29 changes: 17 additions & 12 deletions packages/core/types/src/payment/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,18 +585,6 @@ export interface PaymentProviderDTO {
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.
*/
Expand Down Expand Up @@ -657,3 +645,20 @@ export interface RefundReasonDTO {
*/
updated_at: Date | string
}

export interface PaymentMethodDTO {
/**
* The ID of the payment method.
*/
id: string

/**
* The data of the payment method, as returned by the payment provider.
*/
data: Record<string, unknown>

/**
* The ID of the associated payment provider.
*/
provider_id: string
}
20 changes: 20 additions & 0 deletions packages/core/types/src/payment/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,23 @@ export interface UpdateRefundReasonDTO {
*/
metadata?: Record<string, unknown> | null
}

/**
* The payment method to be created.
*/
export interface CreatePaymentMethodDTO {
/**
* The provider's ID.
*/
provider_id: string

/**
* Necessary data for the associated payment provider to process the payment.
*/
data: Record<string, unknown>

/**
* Necessary context data for the associated payment provider.
*/
context: PaymentProviderContext
}
30 changes: 29 additions & 1 deletion packages/core/types/src/payment/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ export type CreatePaymentProviderSession = {
currency_code: string
}

export type SavePaymentMethod = {
/**
* Any data that should be used by the provider for saving the payment method.
*/
data: Record<string, unknown>

/**
* The context of the payment provider, such as the customer ID.
*/
context: PaymentProviderContext
}

/**
* @interface
*
Expand Down Expand Up @@ -118,6 +130,18 @@ export type PaymentProviderSessionResponse = {
data: Record<string, unknown>
}

export type SavePaymentMethodResponse = {
/**
* The ID of the payment method in the payment provider.
*/
id: string

/**
* The data returned from the payment provider after saving the payment method.
*/
data: Record<string, unknown>
}

/**
* @interface
*
Expand Down Expand Up @@ -254,10 +278,14 @@ export interface IPaymentProvider {
paymentSessionData: Record<string, unknown>
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>

listPaymentMethods(
listPaymentMethods?(
context: PaymentProviderContext
): Promise<PaymentMethodResponse[]>

savePaymentMethod?(
input: SavePaymentMethod
): Promise<PaymentProviderError | SavePaymentMethodResponse>

getPaymentStatus(
paymentSessionData: Record<string, unknown>
): Promise<PaymentSessionStatus>
Expand Down
70 changes: 70 additions & 0 deletions packages/core/types/src/payment/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import {
CaptureDTO,
FilterableCaptureProps,
FilterablePaymentCollectionProps,
FilterablePaymentMethodProps,
FilterablePaymentProps,
FilterablePaymentProviderProps,
FilterablePaymentSessionProps,
FilterableRefundProps,
FilterableRefundReasonProps,
PaymentCollectionDTO,
PaymentDTO,
PaymentMethodDTO,
PaymentProviderDTO,
PaymentSessionDTO,
RefundDTO,
Expand Down Expand Up @@ -749,6 +751,74 @@ export interface IPaymentModuleService extends IModuleService {
sharedContext?: Context
): Promise<[PaymentProviderDTO[], number]>

/**
* This method retrieves all payment methods based on the context and configuration.
*
* @param {FilterablePaymentMethodProps} filters - The filters to apply on the retrieved payment methods.
* @param {FindConfig<PaymentMethodDTO>} config - The configurations determining how the payment method is retrieved. Its properties, such as `select` or `relations`, accept the
* attributes or relations associated with a payment method.
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<PaymentMethodDTO[]>} The list of payment methods.
*
* @example
* To retrieve a list of payment methods for a customer:
*
* ```ts
* const paymentMethods =
* await paymentModuleService.listPaymentMethods({
* provider_id: "pp_stripe_stripe",
* context: {
* customer: {
* id: "cus_123",
* metadata: {
* pp_stripe_stripe_customer_id: "str_1234"
* }
* },
* },
* })
* ```
*
*/
listPaymentMethods(
filters: FilterablePaymentMethodProps,
config: FindConfig<PaymentMethodDTO>,
sharedContext?: Context
): Promise<PaymentMethodDTO[]>

/**
* This method retrieves all payment methods along with the total count of available payment methods, based on the context and configuration.
*
* @param {FilterablePaymentMethodProps} filters - The filters to apply on the retrieved payment methods.
* @param {FindConfig<PaymentMethodDTO>} config - The configurations determining how the payment method is retrieved. Its properties, such as `select` or `relations`, accept the
* attributes or relations associated with a payment method.
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<[PaymentMethodDTO[], number]>} The list of payment methods along with their total count.
*
* @example
* To retrieve a list of payment methods for a customer:
*
* ```ts
* const [paymentMethods, count] =
* await paymentModuleService.listAndCountPaymentMethods({
* provider_id: "pp_stripe_stripe",
* context: {
* customer: {
* id: "cus_123",
* metadata: {
* pp_stripe_stripe_customer_id: "str_1234"
* }
* },
* },
* })
* ```
*
*/
listAndCountPaymentMethods(
filters: FilterablePaymentMethodProps,
config: FindConfig<PaymentMethodDTO>,
sharedContext?: Context
): Promise<[PaymentMethodDTO[], number]>

/**
* This method retrieves a paginated list of captures based on optional filters and configuration.
*
Expand Down
55 changes: 0 additions & 55 deletions packages/core/utils/src/payment/abstract-payment-provider.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import {
CreatePaymentProviderSession,
IPaymentProvider,
PaymentMethodResponse,
PaymentProviderContext,
PaymentProviderError,
PaymentProviderSessionResponse,
PaymentSessionStatus,
Expand Down Expand Up @@ -625,59 +623,6 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
context: UpdatePaymentProviderSession
): 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
* to process the action of the payment provider.
Expand Down
5 changes: 0 additions & 5 deletions packages/modules/payment/src/providers/system.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
CreatePaymentProviderSession,
PaymentMethodResponse,
PaymentProviderError,
PaymentProviderSessionResponse,
ProviderWebhookPayload,
Expand Down Expand Up @@ -73,10 +72,6 @@ export class SystemProviderService extends AbstractPaymentProvider {
return {}
}

async listPaymentMethods(_): Promise<PaymentMethodResponse[]> {
return []
}

async getWebhookActionAndData(
data: ProviderWebhookPayload["payload"]
): Promise<WebhookActionResult> {
Expand Down
52 changes: 50 additions & 2 deletions packages/modules/payment/src/services/payment-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Context,
CreateCaptureDTO,
CreatePaymentCollectionDTO,
CreatePaymentMethodDTO,
CreatePaymentSessionDTO,
CreateRefundDTO,
DAL,
Expand Down Expand Up @@ -914,10 +915,16 @@ export default class PaymentModuleService
config: FindConfig<PaymentMethodDTO> = {},
@MedusaContext() sharedContext?: Context
): Promise<PaymentMethodDTO[]> {
return await this.paymentProviderService_.listPaymentMethods(
const res = await this.paymentProviderService_.listPaymentMethods(
filters.provider_id,
filters.context
)

return res.map((item) => ({
id: item.id,
data: item.data,
provider_id: filters.provider_id,
}))
}

@InjectManager()
Expand All @@ -932,7 +939,48 @@ export default class PaymentModuleService
filters.context
)

return [paymentMethods, paymentMethods.length]
const normalizedResponse = paymentMethods.map((item) => ({
id: item.id,
data: item.data,
provider_id: filters.provider_id,
}))

return [normalizedResponse, paymentMethods.length]
}

// @ts-ignore
createPaymentMethods(
data: CreatePaymentCollectionDTO,
sharedContext?: Context
): Promise<PaymentCollectionDTO>

createPaymentMethods(
data: CreatePaymentMethodDTO[],
sharedContext?: Context
): Promise<PaymentMethodDTO[]>
@InjectManager()
async createPaymentMethods(
data: CreatePaymentMethodDTO | CreatePaymentMethodDTO[],
@MedusaContext() sharedContext?: Context
): Promise<PaymentMethodDTO | PaymentMethodDTO[]> {
const input = Array.isArray(data) ? data : [data]

const result = await promiseAll(
input.map((item) =>
this.paymentProviderService_.savePaymentMethod(item.provider_id, item)
),
{ aggregateErrors: true }
)

const normalizedResponse = result.map((item, i) => {
return {
id: item.id,
data: item.data,
provider_id: input[i].provider_id,
}
})

return Array.isArray(data) ? normalizedResponse : normalizedResponse[0]
}

@InjectManager()
Expand Down
Loading
Loading