diff --git a/packages/core/types/src/payment/common.ts b/packages/core/types/src/payment/common.ts index 2dbc248c36862..bbcf6332fc8f3 100644 --- a/packages/core/types/src/payment/common.ts +++ b/packages/core/types/src/payment/common.ts @@ -4,18 +4,22 @@ import { BigNumberValue } from "../totals" /* ********** PAYMENT COLLECTION ********** */ export type PaymentCollectionStatus = - | "not_paid" - | "awaiting" + | "pending" + | "paid" + | "partially_paid" | "authorized" | "partially_authorized" - | "canceled" + | "refunded" + | "partially_refunded" export type PaymentSessionStatus = | "authorized" | "captured" + | "partially_captured" + | "refunded" + | "partially_refunded" | "pending" | "requires_more" - | "error" | "canceled" /** diff --git a/packages/core/types/src/payment/mutations.ts b/packages/core/types/src/payment/mutations.ts index dc1a1ec750477..6c6132063f43a 100644 --- a/packages/core/types/src/payment/mutations.ts +++ b/packages/core/types/src/payment/mutations.ts @@ -186,11 +186,6 @@ export interface CreateCaptureDTO { */ amount?: BigNumberInput - /** - * The associated payment's ID. - */ - payment_id: string - /** * Who captured the payment. For example, * a user's ID. @@ -207,16 +202,11 @@ export interface CreateRefundDTO { */ amount?: BigNumberInput - /** - * The associated payment's ID. - */ - payment_id: string - /** * Who refunded the payment. For example, * a user's ID. */ - created_by?: string + refunded_by?: string } /** @@ -243,15 +233,10 @@ export interface CreatePaymentSessionDTO { */ amount: BigNumberInput - /** - * Necessary data for the associated payment provider to process the payment. - */ - data: Record - /** * Necessary context data for the associated payment provider. */ - context?: PaymentProviderContext + context: PaymentProviderContext } /** @@ -264,24 +249,24 @@ export interface UpdatePaymentSessionDTO { id: string /** - * Necessary data for the associated payment provider to process the payment. - */ - data: Record - - /** - * The ISO 3 character currency code. + * The provider's payment method token */ - currency_code: string + provider_token?: string /** - * The amount to be authorized. + * Necessary context data for the associated payment provider. */ - amount: BigNumberInput + context: PaymentProviderContext +} +/** + * The attributes to authorize in a payment session. + */ +export interface AuthorizePaymentSessionDTO { /** - * Necessary context data for the associated payment provider. + * The provider token to authorize payment session */ - context?: PaymentProviderContext + provider_token?: string } /** diff --git a/packages/core/types/src/payment/provider.ts b/packages/core/types/src/payment/provider.ts index 0f65e9f62a480..972582ff0361c 100644 --- a/packages/core/types/src/payment/provider.ts +++ b/packages/core/types/src/payment/provider.ts @@ -10,25 +10,16 @@ import { ProviderWebhookPayload } from "./mutations" export type PaymentAddressDTO = Partial /** - * The customer associated with the payment. + * The customer of the payment. */ export type PaymentCustomerDTO = Partial -/** - * Normalized events from payment provider to internal payment module events. - */ -export type PaymentActions = - | "authorized" - | "captured" - | "failed" - | "not_supported" - /** * @interface * - * Context data provided to the payment provider when authorizing a payment session. + * Context data provided to the payment provider. */ -export type PaymentProviderContext = { +export type PaymentProviderContext = Record & { /** * The payment's billing address. */ @@ -55,21 +46,15 @@ export type PaymentProviderContext = { order_id?: string /** - * The customer associated with this payment. + * The associated customer detail */ customer?: PaymentCustomerDTO - - /** - * The extra fields specific to the provider session. - */ - extra?: Record } /** * @interface * - * The data used initiate a payment in a provider when a payment - * session is created. + * The data used initiate a payment in a provider */ export type CreatePaymentProviderSession = { /** @@ -96,76 +81,110 @@ export type CreatePaymentProviderSession = { /** * @interface * - * The attributes to update a payment related to a payment session in a provider. + * The attributes to update a payment in a provider. */ export type UpdatePaymentProviderSession = { - /** - * A payment's context. - */ - context: PaymentProviderContext - /** * The `data` field of the payment session. */ data: Record /** - * The payment session's amount. + * A context necessary for the payment provider. */ - amount: BigNumberInput + context: PaymentProviderContext - /** - * The ISO 3 character code of the payment session. + /* + * The payment method token */ - currency_code: string + token?: string } /** * @interface * - * The response of operations on a payment. + * The attributes to authorize a payment in a provider. */ -export type PaymentProviderSessionResponse = { +export type AuthorizePaymentProviderSession = { /** - * The data to be stored in the `data` field of the Payment Session to be created. - * The `data` field is useful to hold any data required by the third-party provider to process the payment or retrieve its details at a later point. + * The `data` field of the payment session. */ data: Record + + /* + * The payment method token + */ + token?: string } /** * @interface * - * The successful result of authorizing a payment session using a payment provider. + * The response of operations on a payment. */ -export type PaymentProviderAuthorizeResponse = { +export type PaymentProviderSessionResponse = { /** * The status of the payment, which will be stored in the payment session's `status` field. */ status: PaymentSessionStatus /** - * The `data` to be stored in the payment session's `data` field. + * The captured amount of the payment */ - data: PaymentProviderSessionResponse["data"] -} + capturedAmount: BigNumberValue -/** - * @interface - * - * The details of which payment provider to use to perform an action, and what - * data to be passed to that provider. - */ -export type PaymentProviderDataInput = { /** - * The ID of the provider to be used to perform an action. + * The refunded amount of the payment */ - provider_id: string + refundedAmount: BigNumberValue /** - * The data to be passed to the provider. + * The data to be stored in the `data` field of the Payment Session to be created. + * The `data` field is useful to hold any data required by the third-party provider to process the payment or retrieve its details at a later point. */ data: Record + + /** + * A context necessary for the payment provider. + */ + context: PaymentProviderContext + + /** + * The related event or mutation occurred on a payment. + */ + event?: { + /** + * Event type that related to changes in provider payment + */ + type: "authorize" | "capture" | "refund" | "cancel" + + /** + * The event's details. + */ + detail?: { + /** + * The amount to be changed + */ + amount?: BigNumberValue + + /** + * Who captured the payment. For example, + * a user's ID. + */ + captured_by?: string + + /** + * Who refunded the payment. For example, + * a user's ID. + */ + refunded_by?: string + + /** + * The reason to be canceled + */ + reason?: string + } + } } /** @@ -188,57 +207,6 @@ export interface PaymentProviderError { detail?: any } -/** - * @interface - * - * The details of an action to be performed as a result of a received webhook event. - */ -export type WebhookActionData = { - /** - * The associated payment session's ID. - */ - session_id: string - - /** - * The associated cart's ID. - */ - cart_id: string - - /** - * The associated order's ID. - */ - order_id: string - - /** - * The amount to be captured or authorized (based on the action's type.) - */ - amount: BigNumberValue -} - -/** - * @interface - * - * The actions that the payment provider informs the Payment Module to perform. - */ -export type WebhookActionResult = - | { - /** - * Received an event that is not processable. - */ - action: "not_supported" - } - | { - /** - * Normalized events from payment provider to internal payment module events. - */ - action: PaymentActions - - /** - * The webhook action's details. - */ - data: WebhookActionData - } - export interface IPaymentProvider { /** * @ignore @@ -253,8 +221,7 @@ export interface IPaymentProvider { * For example, in the Stripe provider, this method is used to create a Payment Intent for the customer. * * @param {CreatePaymentProviderSession} data - The data necessary to initiate the payment. - * @returns {Promise} Either the payment's data, which is stored in the `data` field - * of the payment session, or an error object. + * @returns {Promise} Either the payment's status and data or an error object. */ initiatePayment( data: CreatePaymentProviderSession @@ -263,22 +230,22 @@ export interface IPaymentProvider { /** * This method is used to update a payment associated with a session in the third-party provider. * - * @param {UpdatePaymentProviderSession} context - The data related to the update. - * @returns {Promise} Either the payment's data or an error object. + * @param {UpdatePaymentProviderSession} data - The data related to the update. + * @returns {Promise} Either the payment's status and data or an error object. */ updatePayment( - context: UpdatePaymentProviderSession + data: UpdatePaymentProviderSession ): Promise /** * This method is called before a payment session is deleted. It's used to perform any actions necessary before the deletion. * * @param {Record} paymentSessionData - The `data` field of the Payment Session. - * @returns {Promise} Either an error object or an empty object. + * @returns {Promise} Either an error object or null if successful. */ deletePayment( paymentSessionData: Record - ): Promise + ): Promise /** * This method is called when a payment session should be authorized. @@ -287,15 +254,12 @@ export interface IPaymentProvider { * Refer to [this guide](https://docs.medusajs.com/experimental/payment/payment-flow/#3-authorize-payment-session) * to learn more about how this fits into the payment flow and how to handle required actions. * - * @param {Record} paymentSessionData - The `data` field of the payment session. - * @param {Record} context - The context of the authorization. - * @returns {Promise} The authorization details or an error object. If - * the authorization details are returned, the `data` and `status` field are set in the associated payment session. + * @param {AuthorizePaymentProviderSession} data - The data related to authorize. + * @returns {Promise} Either the payment's status and data or an error object. */ authorizePayment( - paymentSessionData: Record, - context: Record - ): Promise + data: AuthorizePaymentProviderSession + ): Promise /** * This method is called when a payment should be captured. The payment is captured in one of the following scenarios: @@ -306,64 +270,52 @@ export interface IPaymentProvider { * * In this method, you can interact with the third-party provider and perform any actions necessary to capture the payment. * - * @param {Record} paymentSessionData - The `data` field of the payment. + * @param {Record} paymentSessionData - The `data` field of the Payment Session. * @param {BigNumberInput} captureAmount - The amount to capture. - * @returns {Promise} Either an error object or a value that's stored in the `data` field of the payment capture. + * @returns {Promise} Either the payment's status and data or an error object. */ capturePayment( paymentSessionData: Record, captureAmount?: BigNumberInput - ): Promise + ): Promise /** * This method is called when a payment should be refunded. This is typically triggered manually by the merchant. * * In this method, you can interact with the third-party provider and perform any actions necessary to refund the payment. * - * @param {Record} paymentSessionData - The `data` field of a Payment. + * @param {Record} paymentSessionData - The `data` field of the Payment Session. * @param {BigNumberInput} refundAmount - The amount to refund. - * @returns {Promise} Either an error object or an object that's stored in the `data` field of the payment refund. + * @returns {Promise} Either the payment's status and data or an error object. */ refundPayment( paymentSessionData: Record, - refundAmount: BigNumberInput - ): Promise + refundAmount?: BigNumberInput + ): Promise /** * This method is used to provide a uniform way of retrieving the payment information from the third-party provider. * * For example, in Stripe’s payment provider this method is used to retrieve the payment intent details from Stripe. * - * @param {Record} paymentSessionData - - * The `data` field of a payment session. Make sure to store in the `data` field any necessary data that would allow you to retrieve the payment data from the third-party provider. - * @returns {Promise} Either an error object or the payment's data retrieved from a third-party provider. + * @param {Record} paymentSessionData - The `data` field of the Payment Session. + * @returns {Promise} Either the payment's status and data or an error object. */ retrievePayment( paymentSessionData: Record - ): Promise + ): Promise /** * This method is called when a payment is canceled. * * In this method, you can interact with the third-party provider and perform any actions necessary to cancel the payment. * - * @param {Record} paymentSessionData - The `data` field of the payment. - * @returns {Promise} Either an error object or a value that's stored in the `data` field of the payment. + * @param {Record} paymentSessionData - The `data` field of the Payment Session. + * @returns {Promise} Either the payment's status and data or an error object. */ cancelPayment( paymentSessionData: Record - ): Promise - - /** - * This method is used to get the status of a payment or a payment session. - * - * @param {Record} paymentSessionData - - * The `data` field of a payment as a parameter. You can use this data to interact with the third-party provider to check the status of the payment if necessary. - * @returns {Promise} The status of the payment or payment session. - */ - getPaymentStatus( - paymentSessionData: Record - ): Promise + ): Promise /** * The method is called when a webhook event is received for this provider. @@ -376,5 +328,5 @@ export interface IPaymentProvider { */ getWebhookActionAndData( data: ProviderWebhookPayload["payload"] - ): Promise + ): Promise } diff --git a/packages/core/types/src/payment/service.ts b/packages/core/types/src/payment/service.ts index a3413e7bab577..40e3f19f5ea2e 100644 --- a/packages/core/types/src/payment/service.ts +++ b/packages/core/types/src/payment/service.ts @@ -16,6 +16,7 @@ import { RefundDTO, } from "./common" import { + AuthorizePaymentSessionDTO, CreateCaptureDTO, CreatePaymentCollectionDTO, CreatePaymentSessionDTO, @@ -424,7 +425,7 @@ export interface IPaymentModuleService extends IModuleService { * provider_id: "stripe", * currency_code: "usd", * amount: 3000, - * data: {}, + * context: {}, * } * ) */ @@ -460,7 +461,7 @@ export interface IPaymentModuleService extends IModuleService { * * @param {string} id - The ID of the payment session. * @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module. - * @returns {Promise} Resolves whent the payment session is deleted successfully. + * @returns {Promise} Resolves when the payment session is deleted successfully. * * @example * await paymentModuleService.deletePaymentSession("payses_123") @@ -473,22 +474,18 @@ export interface IPaymentModuleService extends IModuleService { * Learn more about the payment flow in [this guide](https://docs.medusajs.com/experimental/payment/payment-flow/) * * @param {string} id - The payment session's ID. - * @param {Record} context - Context data to pass to the associated payment provider. + * @param {AuthorizePaymentSessionDTO} data - The attributes to authorize in a payment session. * @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module. - * @returns {Promise} The created payment. + * @returns {Promise} Resolves when the payment session is authorized successfully. * * @example - * const payment = - * await paymentModuleService.authorizePaymentSession( - * "payses_123", - * {} - * ) + * await paymentModuleService.authorizePaymentSession("payses_123", { provider_token: "" }) */ authorizePaymentSession( id: string, - context: Record, + data?: AuthorizePaymentSessionDTO, sharedContext?: Context - ): Promise + ): Promise /** * This method retrieves a paginated list of payment sessions based on optional filters and configuration. @@ -622,35 +619,34 @@ export interface IPaymentModuleService extends IModuleService { * * Learn more about the payment flow in [this guide](https://docs.medusajs.com/experimental/payment/payment-flow/) * + * @param {string} paymentId - The ID of the payment to create the capture for. * @param {CreateCaptureDTO} data - The payment capture to be created. * @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module. * @returns {Promise} The payment's details. * * @example - * const payment = await paymentModuleService.capturePayment({ - * payment_id: "pay_123", - * }) + * const payment = await paymentModuleService.capturePayment("pay_123") */ capturePayment( - data: CreateCaptureDTO, + paymentId: string, + data?: CreateCaptureDTO, sharedContext?: Context ): Promise /** * This method refunds a payment using its associated payment provider. An amount can only be refunded if it has been captured first. * + * @param {string} paymentId - The ID of the payment to create the refund for. * @param {CreateRefundDTO} data - The refund to be created. * @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module. * @returns {Promise} The payment's details. * * @example - * const payment = await paymentModuleService.refundPayment({ - * payment_id: "pay_123", - * amount: 300, - * }) + * const payment = await paymentModuleService.refundPayment("pay_123", { amount: 300 }) */ refundPayment( - data: CreateRefundDTO, + paymentId: string, + data?: CreateRefundDTO, sharedContext?: Context ): Promise diff --git a/packages/core/utils/src/payment/abstract-payment-provider.ts b/packages/core/utils/src/payment/abstract-payment-provider.ts index 4b22a0fbefd1e..dadd0a4a7e32c 100644 --- a/packages/core/utils/src/payment/abstract-payment-provider.ts +++ b/packages/core/utils/src/payment/abstract-payment-provider.ts @@ -1,14 +1,13 @@ import { + AuthorizePaymentProviderSession, BigNumberInput, CreatePaymentProviderSession, IPaymentProvider, MedusaContainer, PaymentProviderError, PaymentProviderSessionResponse, - PaymentSessionStatus, ProviderWebhookPayload, UpdatePaymentProviderSession, - WebhookActionResult, } from "@medusajs/types" /** @@ -123,7 +122,7 @@ import { * * // used in other methods * async retrievePayment( - * paymentSessionData: Record + * paymentSessionData: PaymentProviderSessionResponse["data"] * ): Promise< * PaymentProviderError | * PaymentProviderSessionResponse["session_data"] @@ -216,53 +215,42 @@ export abstract class AbstractPaymentProvider> } abstract capturePayment( - paymentSessionData: Record, + paymentSessionData: PaymentProviderSessionResponse["data"], captureAmount?: BigNumberInput - ): Promise + ): Promise abstract authorizePayment( - paymentSessionData: Record, - context: Record - ): Promise< - | PaymentProviderError - | { - status: PaymentSessionStatus - data: PaymentProviderSessionResponse["data"] - } - > + data: AuthorizePaymentProviderSession + ): Promise abstract cancelPayment( - paymentSessionData: Record - ): Promise + paymentSessionData: PaymentProviderSessionResponse["data"] + ): Promise abstract initiatePayment( - context: CreatePaymentProviderSession + data: CreatePaymentProviderSession ): Promise abstract deletePayment( - paymentSessionData: Record - ): Promise - - abstract getPaymentStatus( - paymentSessionData: Record - ): Promise + paymentSessionData: PaymentProviderSessionResponse["data"] + ): Promise abstract refundPayment( - paymentSessionData: Record, - refundAmount: BigNumberInput - ): Promise + paymentSessionData: PaymentProviderSessionResponse["data"], + refundAmount?: BigNumberInput + ): Promise abstract retrievePayment( - paymentSessionData: Record - ): Promise + paymentSessionData: PaymentProviderSessionResponse["data"] + ): Promise abstract updatePayment( - context: UpdatePaymentProviderSession + data: UpdatePaymentProviderSession ): Promise abstract getWebhookActionAndData( data: ProviderWebhookPayload["payload"] - ): Promise + ): Promise } /** diff --git a/packages/core/utils/src/payment/payment-collection.ts b/packages/core/utils/src/payment/payment-collection.ts index 600445705f50d..ff85d306ca82c 100644 --- a/packages/core/utils/src/payment/payment-collection.ts +++ b/packages/core/utils/src/payment/payment-collection.ts @@ -5,23 +5,31 @@ */ export enum PaymentCollectionStatus { /** - * The payment collection isn't paid. + * The payment collection is pending. */ - NOT_PAID = "not_paid", + PENDING = "pending", /** - * The payment collection is awaiting payment. + * The payment collection is paid. */ - AWAITING = "awaiting", + PAID = "paid", + /** + * The payment collection is partially paid. + */ + PARTIALLY_PAID = "partially_paid", /** * The payment collection is authorized. */ AUTHORIZED = "authorized", /** - * Some of the payments in the payment collection are authorized. + * The payment collection is partially authorized. */ PARTIALLY_AUTHORIZED = "partially_authorized", /** - * The payment collection is canceled. + * The payment collection is refunded. + */ + REFUNDED = "refunded", + /** + * The payment collection is refunded. */ - CANCELED = "canceled", + PARTIALLY_REFUNDED = "partially_refunded", } diff --git a/packages/core/utils/src/payment/payment-session.ts b/packages/core/utils/src/payment/payment-session.ts index 65e127daa0680..e4910d0b9808a 100644 --- a/packages/core/utils/src/payment/payment-session.ts +++ b/packages/core/utils/src/payment/payment-session.ts @@ -12,6 +12,18 @@ export enum PaymentSessionStatus { * The payment is captured. */ CAPTURED = "captured", + /** + * The payment is partially captured. + */ + PARTIALLY_CAPTURED = "partially_captured", + /** + * The payment is refunded. + */ + REFUNDED = "refunded", + /** + * The payment is refunded. + */ + PARTIALLY_REFUNDED = "partially_refunded", /** * The payment is pending. */ @@ -20,10 +32,6 @@ export enum PaymentSessionStatus { * The payment requires an action. */ REQUIRES_MORE = "requires_more", - /** - * An error occurred while processing the payment. - */ - ERROR = "error", /** * The payment is canceled. */ diff --git a/packages/modules/payment/src/models/payment-collection.ts b/packages/modules/payment/src/models/payment-collection.ts index fefa36eb74328..3a06b3f240d4b 100644 --- a/packages/modules/payment/src/models/payment-collection.ts +++ b/packages/modules/payment/src/models/payment-collection.ts @@ -95,9 +95,9 @@ export default class PaymentCollection { @Enum({ items: () => PaymentCollectionStatus, - default: PaymentCollectionStatus.NOT_PAID, + default: PaymentCollectionStatus.PENDING, }) - status: PaymentCollectionStatus = PaymentCollectionStatus.NOT_PAID + status: PaymentCollectionStatus = PaymentCollectionStatus.PENDING @ManyToMany(() => PaymentProvider) payment_providers = new Collection>(this) diff --git a/packages/modules/payment/src/providers/system.ts b/packages/modules/payment/src/providers/system.ts index 88c61e5675490..a4d9a0340ce05 100644 --- a/packages/modules/payment/src/providers/system.ts +++ b/packages/modules/payment/src/providers/system.ts @@ -3,10 +3,10 @@ import { PaymentProviderError, PaymentProviderSessionResponse, ProviderWebhookPayload, - WebhookActionResult, } from "@medusajs/types" import { AbstractPaymentProvider, + BigNumber, PaymentActions, PaymentSessionStatus, } from "@medusajs/utils" @@ -15,24 +15,16 @@ export class SystemProviderService extends AbstractPaymentProvider { static identifier = "system" static PROVIDER = "system" - async getStatus(_): Promise { - return "authorized" - } - - async getPaymentData(_): Promise> { - return {} - } - async initiatePayment( - context: CreatePaymentProviderSession + data: CreatePaymentProviderSession ): Promise { - return { data: {} } - } - - async getPaymentStatus( - paymentSessionData: Record - ): Promise { - throw new Error("Method not implemented.") + return { + status: "captured", + capturedAmount: new BigNumber(data.amount), + refundedAmount: 0, + data: {}, + session_id: data.context.session_id!, + } } async retrievePayment( diff --git a/packages/modules/payment/src/services/payment-module.ts b/packages/modules/payment/src/services/payment-module.ts index d8be042e41c25..c03cbf089b98a 100644 --- a/packages/modules/payment/src/services/payment-module.ts +++ b/packages/modules/payment/src/services/payment-module.ts @@ -1,5 +1,5 @@ import { - BigNumberInput, + AuthorizePaymentSessionDTO, CaptureDTO, Context, CreateCaptureDTO, @@ -19,6 +19,7 @@ import { PaymentCollectionUpdatableFields, PaymentDTO, PaymentProviderDTO, + PaymentProviderSessionResponse, PaymentSessionDTO, ProviderWebhookPayload, RefundDTO, @@ -36,7 +37,6 @@ import { MedusaContext, MedusaError, ModulesSdkUtils, - PaymentActions, PaymentCollectionStatus, PaymentSessionStatus, promiseAll, @@ -291,63 +291,57 @@ export default class PaymentModuleService @InjectManager("baseRepository_") async createPaymentSession( paymentCollectionId: string, - input: CreatePaymentSessionDTO, + data: CreatePaymentSessionDTO, @MedusaContext() sharedContext?: Context ): Promise { - let paymentSession: PaymentSession | undefined - let providerSessionData: Record | undefined + let paymentSession: PaymentSession + let res: PaymentProviderSessionResponse + + const retrievePaymentSession = async () => + this.paymentSessionService_.retrieve(paymentSession.id, {}, sharedContext) + const deletePaymentSession = async () => + this.paymentSessionService_.delete(paymentSession.id, sharedContext) + + paymentSession = await this.createPaymentSession_( + paymentCollectionId, + data, + sharedContext + ) try { - paymentSession = await this.createPaymentSession_( - paymentCollectionId, - input, - sharedContext - ) - providerSessionData = await this.paymentProviderService_.createSession( - input.provider_id, - { - context: { ...input.context, session_id: paymentSession.id }, - amount: input.amount, - currency_code: input.currency_code, - token: input.provider_token, - } - ) - paymentSession = ( - await this.paymentSessionService_.update( + res = await this.paymentProviderService_.createSession(data.provider_id, { + context: { ...data.context, session_id: paymentSession.id }, + amount: data.amount, + currency_code: data.currency_code, + token: data.provider_token, + }) + } catch (err) { + await deletePaymentSession() + throw err + } + + await this.handleProviderSessionResponse_(res, sharedContext) + paymentSession = await retrievePaymentSession() + + if ( + paymentSession.status === PaymentSessionStatus.PENDING && + data.provider_token + ) { + try { + res = await this.paymentProviderService_.authorizePayment( + paymentSession.provider_id, { - id: paymentSession.id, - data: { ...input.data, ...providerSessionData }, - }, - sharedContext - ) - )[0] - if (input.provider_token) { - await this.authorizePaymentSession( - paymentSession!.id, - {}, - sharedContext - ) - paymentSession = await this.paymentSessionService_.retrieve( - paymentSession!.id, - {}, - sharedContext - ) - } - } catch (error) { - if (providerSessionData) { - await this.paymentProviderService_.deleteSession({ - provider_id: input.provider_id, - data: providerSessionData, - }) - } - if (paymentSession) { - await this.paymentSessionService_.delete( - paymentSession.id, - sharedContext + data: paymentSession.data, + token: data.provider_token, + } ) + } catch (err) { + // TODO: cancel provider session + await deletePaymentSession() + throw err } - - throw error + await this.handleProviderSessionResponse_(res, sharedContext) + paymentSession = await retrievePaymentSession() } return await this.baseRepository_.serialize(paymentSession, { @@ -361,19 +355,16 @@ export default class PaymentModuleService data: CreatePaymentSessionDTO, @MedusaContext() sharedContext?: Context ): Promise { - const paymentSession = await this.paymentSessionService_.create( + return this.paymentSessionService_.create( { payment_collection_id: paymentCollectionId, provider_id: data.provider_id, amount: data.amount, currency_code: data.currency_code, context: data.context, - data: data.data, }, sharedContext ) - - return paymentSession } @InjectManager("baseRepository_") @@ -387,13 +378,17 @@ export default class PaymentModuleService sharedContext ) + await this.handleProviderSessionResponse_( + await this.paymentProviderService_.updateSession(session.provider_id, { + data: session.data, + context: { ...data.context, session_id: session.id }, + token: data.provider_token, + }), + sharedContext + ) + const updated = await this.paymentSessionService_.update( - { - id: session.id, - amount: data.amount, - currency_code: data.currency_code, - data: data.data, - }, + { id: session.id, context: data.context }, sharedContext ) @@ -411,10 +406,10 @@ export default class PaymentModuleService sharedContext ) - await this.paymentProviderService_.deleteSession({ - provider_id: session.provider_id, - data: session.data, - }) + await this.paymentProviderService_.deleteSession( + session.provider_id, + session.data + ) await this.paymentSessionService_.delete(id, sharedContext) } @@ -422,109 +417,76 @@ export default class PaymentModuleService @InjectManager("baseRepository_") async authorizePaymentSession( id: string, - context: Record, + data?: AuthorizePaymentSessionDTO, @MedusaContext() sharedContext?: Context - ): Promise { + ): Promise { + const paymentSession = await this.paymentSessionService_.retrieve( + id, + { select: ["data", "provider_id", "context"] }, + sharedContext + ) + + await this.handleProviderSessionResponse_( + await this.paymentProviderService_.authorizePayment( + paymentSession.provider_id, + { + data: paymentSession.data, + token: data?.provider_token, + } + ), + sharedContext + ) + } + + @InjectTransactionManager("baseRepository_") + private async authorizePaymentSession_( + id: string, + data?: PaymentProviderSessionResponse["data"], + @MedusaContext() sharedContext?: Context + ): Promise { const session = await this.paymentSessionService_.retrieve( id, { select: [ "id", - "data", - "provider_id", - "amount", "raw_amount", "currency_code", "payment_collection_id", + "provider_id", ], + relations: ["payment.id"], }, sharedContext ) - // this method needs to be idempotent - if (session.authorized_at) { - const payment = await this.paymentService_.retrieve( - { session_id: session.id }, - { relations: ["payment_collection"] }, - sharedContext - ) - return await this.baseRepository_.serialize(payment, { populate: true }) - } - - let { data, status } = await this.paymentProviderService_.authorizePayment( - { - provider_id: session.provider_id, - data: session.data, - }, - context - ) - - if ( - status !== PaymentSessionStatus.AUTHORIZED && - status !== PaymentSessionStatus.CAPTURED - ) { + if (session.status === PaymentSessionStatus.CANCELED) { throw new MedusaError( - MedusaError.Types.NOT_ALLOWED, - `Session: ${session.id} is not authorized with the provider.` + MedusaError.Types.INVALID_DATA, + `The payment session: ${session.id} has been canceled.` ) } - let payment - try { - payment = await this.authorizePaymentSession_( - session, - data, - status as PaymentSessionStatus, + if (session.payment) { + return this.paymentService_.retrieve( + session.payment.id, + {}, sharedContext ) - } catch (error) { - await this.paymentProviderService_.cancelPayment({ - provider_id: session.provider_id, - data, - }) - - throw error - } - - await this.maybeUpdatePaymentCollection_( - session.payment_collection_id, - sharedContext - ) - - return await this.retrievePayment( - payment.id, - { relations: ["payment_collection"] }, - sharedContext - ) - } - - @InjectTransactionManager("baseRepository_") - async authorizePaymentSession_( - session: PaymentSession, - data: Record, - status: PaymentSessionStatus, - @MedusaContext() sharedContext?: Context - ): Promise { - let autoCapture = false - if (status === PaymentSessionStatus.CAPTURED) { - status = PaymentSessionStatus.AUTHORIZED - autoCapture = true } await this.paymentSessionService_.update( { - id: session.id, + id, data, - status, - authorized_at: - status === PaymentSessionStatus.AUTHORIZED ? new Date() : null, + status: PaymentSessionStatus.AUTHORIZED, + authorized_at: new Date(), }, sharedContext ) - const payment = await this.paymentService_.create( + return this.paymentService_.create( { - amount: session.amount, + amount: session.raw_amount, currency_code: session.currency_code, payment_session: session.id, payment_collection_id: session.payment_collection_id, @@ -533,15 +495,6 @@ export default class PaymentModuleService }, sharedContext ) - - if (autoCapture) { - await this.capturePayment( - { payment_id: payment.id, amount: session.amount as BigNumberInput }, - sharedContext - ) - } - - return payment } @InjectManager("baseRepository_") @@ -591,35 +544,34 @@ export default class PaymentModuleService @InjectManager("baseRepository_") async capturePayment( - data: CreateCaptureDTO, - @MedusaContext() sharedContext: Context = {} + paymentId: string, + data?: CreateCaptureDTO, + @MedusaContext() sharedContext?: Context ): Promise { - const [capture, isFullyCaptured] = await this.capturePayment_( - data, + const payment = await this.paymentService_.retrieve( + paymentId, + { select: ["provider_id", "data"] }, sharedContext ) - try { - await this.capturePaymentFromProvider_(capture, sharedContext) - } catch (error) { - await super.deleteCaptures(capture.id, sharedContext) - throw error - } - - if (isFullyCaptured) { - await this.paymentService_.update( - { id: data.payment_id, captured_at: new Date() }, - sharedContext - ) + const res = await this.paymentProviderService_.capturePayment( + payment.provider_id, + payment.data!, + data?.amount + ) + res.event = { + ...res.event, + type: "capture", + detail: { + ...res.event?.detail, + captured_by: data?.captured_by, + }, } - await this.maybeUpdatePaymentCollection_( - capture.payment.payment_collection_id, - sharedContext - ) + await this.handleProviderSessionResponse_(res, sharedContext) return await this.retrievePayment( - data.payment_id, + paymentId, { relations: ["captures"] }, sharedContext ) @@ -627,21 +579,15 @@ export default class PaymentModuleService @InjectTransactionManager("baseRepository_") private async capturePayment_( - data: CreateCaptureDTO, - @MedusaContext() sharedContext: Context = {} - ): Promise<[Capture, boolean]> { + paymentId: string, + data?: PaymentProviderSessionResponse["data"], + { amount, captured_by }: CreateCaptureDTO = {}, + @MedusaContext() sharedContext?: Context + ): Promise { const payment = await this.paymentService_.retrieve( - data.payment_id, + paymentId, { - select: [ - "id", - "data", - "provider_id", - "payment_collection_id", - "amount", - "raw_amount", - "canceled_at", - ], + select: ["id", "raw_amount", "canceled_at", "captured_at"], relations: ["captures.raw_amount"], }, sharedContext @@ -661,14 +607,12 @@ export default class PaymentModuleService ) } - const capturedAmount = payment.captures.reduce((captureAmount, next) => { - return MathBN.add(captureAmount, next.raw_amount) - }, MathBN.convert(0)) - + const capturedAmount = payment.captures.reduce( + (amount, { raw_amount }) => MathBN.add(amount, raw_amount), + MathBN.convert(0) + ) const remainingToCapture = MathBN.sub(payment.raw_amount, capturedAmount) - const newCaptureAmount = data.amount - ? new BigNumber(data.amount) - : remainingToCapture + const newCaptureAmount = amount ? new BigNumber(amount) : remainingToCapture if ( MathBN.eq(remainingToCapture, 0) || @@ -676,7 +620,14 @@ export default class PaymentModuleService ) { throw new MedusaError( MedusaError.Types.INVALID_DATA, - `You cannot capture more than the authorized amount substracted by what is already captured.` + `You cannot capture more than the authorized amount subtracted by what is already captured.` + ) + } + + if (MathBN.lte(newCaptureAmount, 0)) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `You must capture amount more than 0.` ) } @@ -686,58 +637,54 @@ export default class PaymentModuleService ) const isFullyCaptured = MathBN.gte(totalCaptured, payment.raw_amount) - return [ - await this.captureService_.create( - { - payment: data.payment_id, - amount: newCaptureAmount, - captured_by: data.captured_by, - }, - sharedContext - ), - isFullyCaptured, - ] - } - @InjectManager("baseRepository_") - private async capturePaymentFromProvider_( - capture: Capture, - @MedusaContext() sharedContext: Context = {} - ) { - const captureData = await this.paymentProviderService_.capturePayment( + const capture = await this.captureService_.create( { - data: capture.payment.data!, - provider_id: capture.payment.provider_id, + payment: paymentId, + amount: newCaptureAmount, + created_by: captured_by, + data, }, - capture.raw_amount + sharedContext ) - return this.captureService_.update( - { id: capture.id, data: captureData }, + await this.paymentService_.update( + { id: paymentId, captured_at: isFullyCaptured ? new Date() : null, data }, sharedContext ) + + return capture } @InjectManager("baseRepository_") async refundPayment( - data: CreateRefundDTO, - @MedusaContext() sharedContext: Context = {} + paymentId: string, + data?: CreateRefundDTO, + @MedusaContext() sharedContext?: Context ): Promise { - const refund = await this.refundPayment_(data, sharedContext) + const payment = await this.paymentService_.retrieve( + paymentId, + { select: ["provider_id", "data"] }, + sharedContext + ) - try { - await this.refundPaymentFromProvider_(refund, sharedContext) - } catch (error) { - await super.deleteRefunds(refund.id, sharedContext) - throw error + const res = await this.paymentProviderService_.refundPayment( + payment.provider_id, + payment.data!, + data?.amount + ) + res.event = { + ...res.event, + type: "refund", + detail: { + ...res.event?.detail, + refunded_by: data?.refunded_by, + }, } - await this.maybeUpdatePaymentCollection_( - refund.payment.payment_collection_id, - sharedContext - ) + await this.handleProviderSessionResponse_(res, sharedContext) return await this.retrievePayment( - data.payment_id, + paymentId, { relations: ["refunds"] }, sharedContext ) @@ -745,36 +692,31 @@ export default class PaymentModuleService @InjectTransactionManager("baseRepository_") private async refundPayment_( - data: CreateRefundDTO, - @MedusaContext() sharedContext: Context = {} + paymentId: string, + data?: PaymentProviderSessionResponse["data"], + { amount, refunded_by }: CreateRefundDTO = {}, + @MedusaContext() sharedContext?: Context ): Promise { const payment = await this.paymentService_.retrieve( - data.payment_id, - { - select: [ - "id", - "data", - "provider_id", - "payment_collection_id", - "amount", - "raw_amount", - ], - relations: ["captures.raw_amount"], - }, + paymentId, + { relations: ["captures.raw_amount", "refunds.raw_amount"] }, sharedContext ) - const capturedAmount = payment.captures.reduce((captureAmount, next) => { - return MathBN.add(captureAmount, next.raw_amount) - }, MathBN.convert(0)) - - const refundAmount = data.amount - ? new BigNumber(data.amount) - : capturedAmount + const capturedAmount = payment.captures.reduce( + (amount, { raw_amount }) => MathBN.add(amount, raw_amount), + MathBN.convert(0) + ) + const refundedAmount = payment.refunds.reduce( + (amount, { raw_amount }) => MathBN.add(amount, raw_amount), + MathBN.convert(0) + ) + const refundableAmount = MathBN.sub(capturedAmount, refundedAmount) + const refundAmount = amount ? new BigNumber(amount) : refundableAmount if ( - MathBN.eq(capturedAmount, 0) || - MathBN.lt(capturedAmount, refundAmount) + MathBN.eq(refundableAmount, 0) || + MathBN.gt(refundAmount, refundableAmount) ) { throw new MedusaError( MedusaError.Types.INVALID_DATA, @@ -782,33 +724,26 @@ export default class PaymentModuleService ) } - return this.refundService_.create( + if (MathBN.lte(refundAmount, 0)) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `You must refund amount more than 0.` + ) + } + + const refund = await this.refundService_.create( { - payment: data.payment_id, + payment: paymentId, amount: refundAmount, - created_by: data.created_by, + data, + created_by: refunded_by, }, sharedContext ) - } - @InjectManager("baseRepository_") - private async refundPaymentFromProvider_( - refund: Refund, - @MedusaContext() sharedContext: Context = {} - ) { - const refundData = await this.paymentProviderService_.refundPayment( - { - data: refund.payment.data!, - provider_id: refund.payment.provider_id, - }, - refund.raw_amount - ) + await this.paymentService_.update({ id: paymentId, data }, sharedContext) - return this.refundService_.update( - { id: refund.id, data: refundData }, - sharedContext - ) + return refund } @InjectManager("baseRepository_") @@ -830,10 +765,10 @@ export default class PaymentModuleService // ) // } - await this.paymentProviderService_.cancelPayment({ - data: payment.data!, - provider_id: payment.provider_id, - }) + await this.paymentProviderService_.cancelPayment( + payment.provider_id, + payment.data! + ) await this.paymentService_.update( { id: paymentId, canceled_at: new Date() }, @@ -849,49 +784,12 @@ export default class PaymentModuleService @MedusaContext() sharedContext?: Context ): Promise { const providerId = `pp_${eventData.provider}` - - const event = await this.paymentProviderService_.getWebhookActionAndData( + const res = await this.paymentProviderService_.getWebhookActionAndData( providerId, eventData.payload ) - if (event.action === PaymentActions.NOT_SUPPORTED) { - return - } - - switch (event.action) { - case PaymentActions.SUCCESSFUL: { - const [payment] = await this.listPayments( - { - payment_session_id: event.data.session_id, - }, - {}, - sharedContext - ) - - if (event.data.order_id && !event.data.cart_id) { - await this.authorizePaymentSession( - event.data.session_id, - {}, - sharedContext - ) - } - - if (payment && !payment.captured_at) { - await this.capturePayment( - { payment_id: payment.id, amount: event.data.amount }, - sharedContext - ) - } - break - } - case PaymentActions.AUTHORIZED: - await this.authorizePaymentSession( - event.data.session_id, - {}, - sharedContext - ) - } + await this.handleProviderSessionResponse_(res, sharedContext) } @InjectManager("baseRepository_") @@ -934,7 +832,78 @@ export default class PaymentModuleService ] } - @InjectManager("baseRepository_") + @InjectTransactionManager("baseRepository_") + private async maybeUpdatePaymentSession_( + paymentSessionId: string, + data?: PaymentProviderSessionResponse["data"], + sharedContext?: Context + ) { + const paymentSession = await this.paymentSessionService_.retrieve( + paymentSessionId, + { + select: ["raw_amount", "status"], + relations: [ + "payment.captures.raw_amount", + "payment.refunds.raw_amount", + ], + }, + sharedContext + ) + + if (paymentSession.status === PaymentSessionStatus.CANCELED) return + + if ( + paymentSession.status === PaymentSessionStatus.PENDING || + paymentSession.status === PaymentSessionStatus.REQUIRES_MORE + ) { + if (data) { + await this.paymentSessionService_.update( + { id: paymentSession.id, data }, + sharedContext + ) + } + } else { + let status: PaymentSessionStatus = paymentSession.status + + if (paymentSession.payment!.canceled_at) { + status = PaymentSessionStatus.CANCELED + } else { + const capturedAmount = paymentSession.payment!.captures.reduce( + (amount, { raw_amount }) => MathBN.add(amount, raw_amount), + MathBN.convert(0) + ) + const refundedAmount = paymentSession.payment!.refunds.reduce( + (amount, { raw_amount }) => MathBN.add(amount, raw_amount), + MathBN.convert(0) + ) + + if (MathBN.gt(capturedAmount, 0)) { + if (MathBN.gt(refundedAmount, 0)) { + if (MathBN.eq(capturedAmount, refundedAmount)) { + status = PaymentSessionStatus.REFUNDED + } else { + status = PaymentSessionStatus.PARTIALLY_REFUNDED + } + } else if ( + MathBN.eq(capturedAmount, paymentSession.payment!.raw_amount) + ) { + status = PaymentSessionStatus.CAPTURED + } else { + status = PaymentSessionStatus.PARTIALLY_CAPTURED + } + } + } + + if (data || status !== paymentSession.status) { + await this.paymentSessionService_.update( + { id: paymentSession.id, data, status }, + sharedContext + ) + } + } + } + + @InjectTransactionManager("baseRepository_") private async maybeUpdatePaymentCollection_( paymentCollectionId: string, sharedContext?: Context @@ -942,65 +911,208 @@ export default class PaymentModuleService const paymentCollection = await this.paymentCollectionService_.retrieve( paymentCollectionId, { - select: ["amount", "raw_amount", "status"], + select: ["raw_amount", "status"], relations: [ - "payment_sessions.amount", - "payment_sessions.raw_amount", - "payments.captures.amount", "payments.captures.raw_amount", - "payments.refunds.amount", "payments.refunds.raw_amount", ], }, sharedContext ) + let status: PaymentCollectionStatus = paymentCollection.status - const paymentSessions = paymentCollection.payment_sessions - const captures = paymentCollection.payments - .map((pay) => [...pay.captures]) + const authorizedAmount = paymentCollection.payments.reduce( + (amount, { raw_amount }) => MathBN.add(amount, raw_amount), + MathBN.convert(0) + ) + const capturedAmount = paymentCollection.payments + .map(({ captures }) => captures.slice()) .flat() - const refunds = paymentCollection.payments - .map((pay) => [...pay.refunds]) + .reduce( + (amount, { raw_amount }) => MathBN.add(amount, raw_amount), + MathBN.convert(0) + ) + const refundedAmount = paymentCollection.payments + .map(({ refunds }) => refunds.slice()) .flat() + .reduce( + (amount, { raw_amount }) => MathBN.add(amount, raw_amount), + MathBN.convert(0) + ) - let authorizedAmount = MathBN.convert(0) - let capturedAmount = MathBN.convert(0) - let refundedAmount = MathBN.convert(0) - - for (const ps of paymentSessions) { - if (ps.status === PaymentSessionStatus.AUTHORIZED) { - authorizedAmount = MathBN.add(authorizedAmount, ps.amount) + if (MathBN.gt(capturedAmount, 0)) { + if (MathBN.gt(refundedAmount, 0)) { + if (MathBN.eq(capturedAmount, refundedAmount)) { + status = PaymentCollectionStatus.REFUNDED + } else { + status = PaymentCollectionStatus.PARTIALLY_REFUNDED + } + } else if (MathBN.eq(capturedAmount, paymentCollection.raw_amount)) { + status = PaymentCollectionStatus.PAID + } else { + status = PaymentCollectionStatus.PARTIALLY_PAID + } + } else if (MathBN.gt(authorizedAmount, 0)) { + if (MathBN.eq(authorizedAmount, paymentCollection.raw_amount)) { + status = PaymentCollectionStatus.AUTHORIZED + } else { + status = PaymentCollectionStatus.PARTIALLY_AUTHORIZED } } - for (const capture of captures) { - capturedAmount = MathBN.add(capturedAmount, capture.amount) + if (status !== paymentCollection.status) { + await this.paymentCollectionService_.update( + { + id: paymentCollectionId, + status, + authorized_amount: authorizedAmount, + captured_amount: capturedAmount, + refunded_amount: refundedAmount, + }, + sharedContext + ) } + } - for (const refund of refunds) { - refundedAmount = MathBN.add(refundedAmount, refund.amount) - } + @InjectTransactionManager("baseRepository_") + private async handleProviderSessionResponse_( + { + status, + capturedAmount, + refundedAmount, + data, + context, + event, + }: PaymentProviderSessionResponse, + @MedusaContext() sharedContext?: Context + ) { + const session_id = context.session_id! - let status = - paymentSessions.length === 0 - ? PaymentCollectionStatus.NOT_PAID - : PaymentCollectionStatus.AWAITING + switch (status) { + case "authorized": { + const payment = await this.authorizePaymentSession_( + session_id, + data, + sharedContext + ) + await this.maybeUpdatePaymentCollection_( + payment.payment_collection_id, + sharedContext + ) + break + } - if (MathBN.gt(authorizedAmount, 0)) { - status = MathBN.gte(authorizedAmount, paymentCollection.amount) - ? PaymentCollectionStatus.AUTHORIZED - : PaymentCollectionStatus.PARTIALLY_AUTHORIZED - } + case "captured": + case "partially_captured": + case "refunded": + case "partially_refunded": { + const session = await this.paymentSessionService_.retrieve( + session_id, + { + select: ["payment_collection_id"], + relations: [ + "payment.id", + "payment.raw_amount", + "payment.captures.raw_amount", + "payment.refunds.raw_amount", + ], + }, + sharedContext + ) - await this.paymentCollectionService_.update( - { - id: paymentCollectionId, - status, - authorized_amount: authorizedAmount, - captured_amount: capturedAmount, - refunded_amount: refundedAmount, - }, - sharedContext - ) + let payment = session.payment + + if (!payment) { + const { id } = await this.authorizePaymentSession_( + session_id, + data, + sharedContext + ) + payment = await this.paymentService_.retrieve( + id, + { + select: ["id", "raw_amount"], + relations: ["captures.raw_amount", "refunds.raw_amount"], + }, + sharedContext + ) + } + + const _capturedAmount = payment.captures.reduce( + (amount, { raw_amount }) => MathBN.add(amount, raw_amount), + MathBN.convert(0) + ) + const _refundedAmount = payment.refunds.reduce( + (amount, { raw_amount }) => MathBN.add(amount, raw_amount), + MathBN.convert(0) + ) + + if (MathBN.gt(capturedAmount, _capturedAmount)) { + await this.capturePayment_( + payment.id, + data, + { + amount: MathBN.sub(capturedAmount, _capturedAmount), + captured_by: event?.detail?.captured_by, + }, + sharedContext + ) + } + if (MathBN.gt(refundedAmount, _refundedAmount)) { + await this.refundPayment_( + payment.id, + data, + { + amount: MathBN.sub(refundedAmount, _refundedAmount), + refunded_by: event?.detail?.refunded_by, + }, + sharedContext + ) + } + await this.maybeUpdatePaymentSession_(session_id, data, sharedContext) + await this.maybeUpdatePaymentCollection_( + session.payment_collection_id, + sharedContext + ) + break + } + + case "pending": + case "requires_more": { + await this.paymentSessionService_.update( + { id: session_id, data, status }, + sharedContext + ) + break + } + case "canceled": { + const session = await this.paymentSessionService_.retrieve( + session_id, + { select: ["payment_collection_id"], relations: ["payment.id"] }, + sharedContext + ) + await this.paymentSessionService_.update( + { id: session_id, data, status }, + sharedContext + ) + if (session.payment) { + await this.paymentService_.update( + { id: session.payment.id, canceled_at: new Date() }, + sharedContext + ) + } + await this.maybeUpdatePaymentCollection_( + session.payment_collection_id, + sharedContext + ) + break + } + default: { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Received invalid payment status: ${status}` + ) + } + } } } diff --git a/packages/modules/payment/src/services/payment-provider.ts b/packages/modules/payment/src/services/payment-provider.ts index 1e465ec727c96..daafbe1744eb3 100644 --- a/packages/modules/payment/src/services/payment-provider.ts +++ b/packages/modules/payment/src/services/payment-provider.ts @@ -1,4 +1,5 @@ import { + AuthorizePaymentProviderSession, BigNumberInput, Context, CreatePaymentProviderDTO, @@ -8,15 +9,11 @@ import { FindConfig, InternalModuleDeclaration, IPaymentProvider, - PaymentProviderAuthorizeResponse, - PaymentProviderDataInput, PaymentProviderDTO, PaymentProviderError, PaymentProviderSessionResponse, - PaymentSessionStatus, ProviderWebhookPayload, UpdatePaymentProviderSession, - WebhookActionResult, } from "@medusajs/types" import { InjectManager, @@ -100,108 +97,122 @@ export default class PaymentProviderService { } } - async createSession( + async retrieveSession( providerId: string, - sessionInput: CreatePaymentProviderSession - ): Promise { + paymentSessionData: PaymentProviderSessionResponse["data"] + ): Promise { const provider = this.retrieveProvider(providerId) + const res = await provider.retrievePayment(paymentSessionData) + + if (isPaymentProviderError(res)) { + this.throwPaymentProviderError(res) + } + + return res as PaymentProviderSessionResponse + } - const paymentResponse = await provider.initiatePayment(sessionInput) + async createSession( + providerId: string, + data: CreatePaymentProviderSession + ): Promise { + const provider = this.retrieveProvider(providerId) + const res = await provider.initiatePayment(data) - if (isPaymentProviderError(paymentResponse)) { - this.throwPaymentProviderError(paymentResponse) + if (isPaymentProviderError(res)) { + this.throwPaymentProviderError(res) } - return (paymentResponse as PaymentProviderSessionResponse).data + return res as PaymentProviderSessionResponse } async updateSession( providerId: string, - sessionInput: UpdatePaymentProviderSession - ): Promise | undefined> { + data: UpdatePaymentProviderSession + ): Promise { const provider = this.retrieveProvider(providerId) + const res = await provider.updatePayment(data) - const paymentResponse = await provider.updatePayment(sessionInput) - - if (isPaymentProviderError(paymentResponse)) { - this.throwPaymentProviderError(paymentResponse) + if (isPaymentProviderError(res)) { + this.throwPaymentProviderError(res) } - return (paymentResponse as PaymentProviderSessionResponse)?.data + return res as PaymentProviderSessionResponse } - async deleteSession(input: PaymentProviderDataInput): Promise { - const provider = this.retrieveProvider(input.provider_id) + async deleteSession( + providerId: string, + paymentSessionData: PaymentProviderSessionResponse["data"] + ): Promise { + const provider = this.retrieveProvider(providerId) + const res = await provider.deletePayment(paymentSessionData) - const error = await provider.deletePayment(input.data) - if (isPaymentProviderError(error)) { - this.throwPaymentProviderError(error) + if (isPaymentProviderError(res)) { + this.throwPaymentProviderError(res) } } async authorizePayment( - input: PaymentProviderDataInput, - context: Record - ): Promise<{ data: Record; status: PaymentSessionStatus }> { - const provider = this.retrieveProvider(input.provider_id) + providerId: string, + data: AuthorizePaymentProviderSession + ): Promise { + const provider = this.retrieveProvider(providerId) + const res = await provider.authorizePayment(data) - const res = await provider.authorizePayment(input.data, context) if (isPaymentProviderError(res)) { this.throwPaymentProviderError(res) } - const { data, status } = res as PaymentProviderAuthorizeResponse - return { data, status } - } - - async getStatus( - input: PaymentProviderDataInput - ): Promise { - const provider = this.retrieveProvider(input.provider_id) - return await provider.getPaymentStatus(input.data) + return res as PaymentProviderSessionResponse } async capturePayment( - input: PaymentProviderDataInput, - amount?: BigNumberInput - ): Promise> { - const provider = this.retrieveProvider(input.provider_id) + providerId: string, + paymentSessionData: PaymentProviderSessionResponse["data"], + captureAmount?: BigNumberInput + ): Promise { + const provider = this.retrieveProvider(providerId) + const res = await provider.capturePayment(paymentSessionData, captureAmount) - const res = await provider.capturePayment(input.data, amount) if (isPaymentProviderError(res)) { this.throwPaymentProviderError(res) } - return res as Record + return res as PaymentProviderSessionResponse } - async cancelPayment(input: PaymentProviderDataInput): Promise { - const provider = this.retrieveProvider(input.provider_id) + async cancelPayment( + providerId: string, + paymentSessionData: PaymentProviderSessionResponse["data"] + ): Promise { + const provider = this.retrieveProvider(providerId) + const res = await provider.cancelPayment(paymentSessionData) - const error = await provider.cancelPayment(input.data) - if (isPaymentProviderError(error)) { - this.throwPaymentProviderError(error) + if (isPaymentProviderError(res)) { + this.throwPaymentProviderError(res) } + + return res as PaymentProviderSessionResponse } async refundPayment( - input: PaymentProviderDataInput, - amount: BigNumberInput - ): Promise> { - const provider = this.retrieveProvider(input.provider_id) + providerId: string, + paymentSessionData: PaymentProviderSessionResponse["data"], + refundAmount?: BigNumberInput + ): Promise { + const provider = this.retrieveProvider(providerId) + const res = await provider.refundPayment(paymentSessionData, refundAmount) - const res = await provider.refundPayment(input.data, amount) if (isPaymentProviderError(res)) { this.throwPaymentProviderError(res) } - return res as Record + return res as PaymentProviderSessionResponse } async getWebhookActionAndData( providerId: string, data: ProviderWebhookPayload["payload"] - ): Promise { + ): Promise { const provider = this.retrieveProvider(providerId) return await provider.getWebhookActionAndData(data) diff --git a/packages/modules/providers/payment-stripe/src/core/stripe-base.ts b/packages/modules/providers/payment-stripe/src/core/stripe-base.ts index 7094fa518f206..4ebb1f0bd307a 100644 --- a/packages/modules/providers/payment-stripe/src/core/stripe-base.ts +++ b/packages/modules/providers/payment-stripe/src/core/stripe-base.ts @@ -171,29 +171,24 @@ abstract class StripeBase extends AbstractPaymentProvider { ) } - return { - data: sessionData, - // TODO: REVISIT - // update_requests: customer?.metadata?.stripe_id - // ? undefined - // : { - // customer_metadata: { - // stripe_id: intentRequest.customer, - // }, - // }, - } + return sessionData + // TODO: REVISIT + // return { + // data: sessionData, + // update_requests: customer?.metadata?.stripe_id + // ? undefined + // : { + // customer_metadata: { + // stripe_id: intentRequest.customer, + // }, + // }, + // } } async authorizePayment( paymentSessionData: Record, context: Record - ): Promise< - | PaymentProviderError - | { - status: PaymentSessionStatus - data: PaymentProviderSessionResponse["data"] - } - > { + ): Promise { const status = await this.getPaymentStatus(paymentSessionData) return { data: paymentSessionData, status } }