feat: Add support for providers to validate their options at loading time (#8853)
* feat: Add support for providers to validate their options at loading time * fix missing removal * fix integration tests * add tests
This commit is contained in:
committed by
GitHub
parent
b8572165cb
commit
77b874f272
@@ -0,0 +1,9 @@
|
||||
const service = class TestService {
|
||||
static validateOptions(options: Record<any, any>) {
|
||||
throw new Error("Wrong options")
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
services: [service],
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createMedusaContainer } from "@medusajs/utils"
|
||||
import { Lifetime, asFunction } from "awilix"
|
||||
import { asFunction, Lifetime } from "awilix"
|
||||
import { moduleProviderLoader } from "../module-provider-loader"
|
||||
|
||||
const logger = {
|
||||
@@ -34,6 +34,24 @@ describe("modules loader", () => {
|
||||
expect(testService.constructor.name).toEqual("TestService")
|
||||
})
|
||||
|
||||
it("should fail to register the provider service", async () => {
|
||||
const moduleProviders = [
|
||||
{
|
||||
resolve: "@providers/default-with-fail-validation",
|
||||
id: "default",
|
||||
options: {},
|
||||
},
|
||||
]
|
||||
|
||||
const err = await moduleProviderLoader({
|
||||
container,
|
||||
providers: moduleProviders,
|
||||
}).catch((e) => e)
|
||||
|
||||
expect(err).toBeTruthy()
|
||||
expect(err.message).toBe("Wrong options")
|
||||
})
|
||||
|
||||
it("should register the provider service with custom register fn", async () => {
|
||||
const fn = async (klass, container, details) => {
|
||||
container.register({
|
||||
@@ -64,6 +82,35 @@ describe("modules loader", () => {
|
||||
expect(testService.constructor.name).toEqual("TestService")
|
||||
})
|
||||
|
||||
it("should fail to register the provider service with custom register fn", async () => {
|
||||
const fn = async (klass, container, details) => {
|
||||
container.register({
|
||||
[`testServiceCustomRegistration`]: asFunction(
|
||||
(cradle) => new klass(cradle, details.options),
|
||||
{
|
||||
lifetime: Lifetime.SINGLETON,
|
||||
}
|
||||
),
|
||||
})
|
||||
}
|
||||
const moduleProviders = [
|
||||
{
|
||||
resolve: "@providers/default-with-fail-validation",
|
||||
id: "default",
|
||||
options: {},
|
||||
},
|
||||
]
|
||||
|
||||
const err = await moduleProviderLoader({
|
||||
container,
|
||||
providers: moduleProviders,
|
||||
registerServiceFn: fn,
|
||||
}).catch((e) => e)
|
||||
|
||||
expect(err).toBeTruthy()
|
||||
expect(err.message).toBe("Wrong options")
|
||||
})
|
||||
|
||||
it("should log the errors if no service is defined", async () => {
|
||||
const moduleProviders = [
|
||||
{
|
||||
|
||||
@@ -60,8 +60,11 @@ export async function loadModuleProvider(
|
||||
)
|
||||
}
|
||||
|
||||
const services = await promiseAll(
|
||||
return await promiseAll(
|
||||
loadedProvider.services.map(async (service) => {
|
||||
// Ask the provider to validate its options
|
||||
await service.validateOptions?.(provider.options)
|
||||
|
||||
const name = lowerCaseFirst(service.name)
|
||||
if (registerServiceFn) {
|
||||
// Used to register the specific type of service in the provider
|
||||
@@ -83,6 +86,4 @@ export async function loadModuleProvider(
|
||||
return service
|
||||
})
|
||||
)
|
||||
|
||||
return services
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
AuthIdentityProviderService,
|
||||
AuthenticationInput,
|
||||
AuthenticationResponse,
|
||||
AuthIdentityProviderService,
|
||||
IAuthProvider,
|
||||
} from "@medusajs/types"
|
||||
|
||||
@@ -74,6 +74,7 @@ export abstract class AbstractAuthModuleProvider implements IAuthProvider {
|
||||
* @ignore
|
||||
*/
|
||||
private static DISPLAY_NAME: string
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
@@ -91,6 +92,12 @@ export abstract class AbstractAuthModuleProvider implements IAuthProvider {
|
||||
return (this.constructor as typeof AbstractAuthModuleProvider).DISPLAY_NAME
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this static method in order for the loader to validate the options provided to the module provider.
|
||||
* @param options
|
||||
*/
|
||||
static validateOptions(options: Record<any, any>): void | never {}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*
|
||||
|
||||
@@ -2,46 +2,46 @@ import { FileTypes, IFileProvider } from "@medusajs/types"
|
||||
|
||||
/**
|
||||
* ### constructor
|
||||
*
|
||||
*
|
||||
* The constructor allows you to access resources from the module's container using the first parameter,
|
||||
* and the module's options using the second parameter.
|
||||
*
|
||||
*
|
||||
* If you're creating a client or establishing a connection with a third-party service, do it in the constructor.
|
||||
*
|
||||
*
|
||||
* #### Example
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* import { Logger } from "@medusajs/types"
|
||||
* import { AbstractFileProviderService } from "@medusajs/utils"
|
||||
*
|
||||
*
|
||||
* type InjectedDependencies = {
|
||||
* logger: Logger
|
||||
* }
|
||||
*
|
||||
*
|
||||
* type Options = {
|
||||
* apiKey: string
|
||||
* }
|
||||
*
|
||||
*
|
||||
* class MyFileProviderService extends AbstractFileProviderService {
|
||||
* protected logger_: Logger
|
||||
* protected options_: Options
|
||||
* // assuming you're initializing a client
|
||||
* protected client
|
||||
*
|
||||
*
|
||||
* constructor (
|
||||
* { logger }: InjectedDependencies,
|
||||
* options: Options
|
||||
* ) {
|
||||
* super()
|
||||
*
|
||||
*
|
||||
* this.logger_ = logger
|
||||
* this.options_ = options
|
||||
*
|
||||
*
|
||||
* // assuming you're initializing a client
|
||||
* this.client = new Client(options)
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* export default MyFileProviderService
|
||||
* ```
|
||||
*/
|
||||
@@ -51,6 +51,12 @@ export class AbstractFileProviderService implements IFileProvider {
|
||||
*/
|
||||
static identifier: string
|
||||
|
||||
/**
|
||||
* Override this static method in order for the loader to validate the options provided to the module provider.
|
||||
* @param options
|
||||
*/
|
||||
static validateOptions(options: Record<any, any>): void | never {}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
@@ -60,10 +66,10 @@ export class AbstractFileProviderService implements IFileProvider {
|
||||
|
||||
/**
|
||||
* This method uploads a file using your provider's custom logic.
|
||||
*
|
||||
*
|
||||
* @param {FileTypes.ProviderUploadFileDTO} file - The file to upload
|
||||
* @returns {Promise<FileTypes.ProviderFileResultDTO>} The uploaded file's details.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* class MyFileProviderService extends AbstractFileProviderService {
|
||||
* // ...
|
||||
@@ -72,7 +78,7 @@ export class AbstractFileProviderService implements IFileProvider {
|
||||
* ): Promise<ProviderFileResultDTO> {
|
||||
* // TODO upload file to third-party provider
|
||||
* // or using custom logic
|
||||
*
|
||||
*
|
||||
* return {
|
||||
* url: "some-url.com",
|
||||
* key: "file-name"
|
||||
@@ -88,10 +94,10 @@ export class AbstractFileProviderService implements IFileProvider {
|
||||
|
||||
/**
|
||||
* This method deletes the file from storage.
|
||||
*
|
||||
*
|
||||
* @param {FileTypes.ProviderDeleteFileDTO} file - The details of the file to delete.
|
||||
* @returns {Promise<void>} Resolves when the file is deleted.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* class MyFileProviderService extends AbstractFileProviderService {
|
||||
* // ...
|
||||
@@ -108,16 +114,16 @@ export class AbstractFileProviderService implements IFileProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to retrieve a download URL of the file. For some providers,
|
||||
* This method is used to retrieve a download URL of the file. For some providers,
|
||||
* such as S3, a presigned URL indicates a temporary URL to get access to a file.
|
||||
*
|
||||
* If your provider doesn’t perform or offer a similar functionality, you can
|
||||
*
|
||||
* If your provider doesn’t perform or offer a similar functionality, you can
|
||||
* return the URL to download the file.
|
||||
*
|
||||
* @param {FileTypes.ProviderGetFileDTO} fileData - The details of the file to get its
|
||||
*
|
||||
* @param {FileTypes.ProviderGetFileDTO} fileData - The details of the file to get its
|
||||
* presigned URL.
|
||||
* @returns {Promise<string>} The file's presigned URL.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* class MyFileProviderService extends AbstractFileProviderService {
|
||||
* // ...
|
||||
|
||||
@@ -11,6 +11,12 @@ export class AbstractFulfillmentProviderService
|
||||
return obj?.constructor?._isFulfillmentService
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this static method in order for the loader to validate the options provided to the module provider.
|
||||
* @param options
|
||||
*/
|
||||
static validateOptions(options: Record<any, any>): void | never {}
|
||||
|
||||
getIdentifier() {
|
||||
return (this.constructor as any).identifier
|
||||
}
|
||||
|
||||
@@ -1,68 +1,74 @@
|
||||
import { NotificationTypes, INotificationProvider } from "@medusajs/types"
|
||||
import { INotificationProvider, NotificationTypes } from "@medusajs/types"
|
||||
|
||||
/**
|
||||
* ### constructor
|
||||
*
|
||||
*
|
||||
* The constructor allows you to access resources from the module's container using the first parameter,
|
||||
* and the module's options using the second parameter.
|
||||
*
|
||||
*
|
||||
* If you're creating a client or establishing a connection with a third-party service, do it in the constructor.
|
||||
*
|
||||
*
|
||||
* #### Example
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* import { AbstractNotificationProviderService } from "@medusajs/utils"
|
||||
* import { Logger } from "@medusajs/types"
|
||||
*
|
||||
*
|
||||
* type InjectedDependencies = {
|
||||
* logger: Logger
|
||||
* }
|
||||
*
|
||||
*
|
||||
* type Options = {
|
||||
* apiKey: string
|
||||
* }
|
||||
*
|
||||
*
|
||||
* class MyNotificationProviderService extends AbstractNotificationProviderService {
|
||||
* protected logger_: Logger
|
||||
* protected options_: Options
|
||||
* // assuming you're initializing a client
|
||||
* protected client
|
||||
*
|
||||
*
|
||||
* constructor (
|
||||
* { logger }: InjectedDependencies,
|
||||
* options: Options
|
||||
* ) {
|
||||
* super()
|
||||
*
|
||||
*
|
||||
* this.logger_ = logger
|
||||
* this.options_ = options
|
||||
*
|
||||
*
|
||||
* // assuming you're initializing a client
|
||||
* this.client = new Client(options)
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* export default MyNotificationProviderService
|
||||
* ```
|
||||
*/
|
||||
export class AbstractNotificationProviderService
|
||||
implements INotificationProvider
|
||||
{
|
||||
/**
|
||||
* Override this static method in order for the loader to validate the options provided to the module provider.
|
||||
* @param options
|
||||
*/
|
||||
static validateOptions(options: Record<any, any>): void | never {}
|
||||
|
||||
/**
|
||||
* This method is used to send a notification using the third-party provider or your custom logic.
|
||||
*
|
||||
*
|
||||
* @param {NotificationTypes.ProviderSendNotificationDTO} notification - The details of the
|
||||
* notification to send.
|
||||
* @returns {Promise<NotificationTypes.ProviderSendNotificationResultsDTO>} The result of sending
|
||||
* the notification.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* // other imports...
|
||||
* import {
|
||||
* import {
|
||||
* ProviderSendNotificationDTO,
|
||||
* ProviderSendNotificationResultsDTO
|
||||
* } from "@medusajs/types"
|
||||
*
|
||||
*
|
||||
* class MyNotificationProviderService extends AbstractNotificationProviderService {
|
||||
* // ...
|
||||
* async send(
|
||||
|
||||
@@ -7,13 +7,18 @@ import {
|
||||
PaymentSessionStatus,
|
||||
ProviderWebhookPayload,
|
||||
UpdatePaymentProviderSession,
|
||||
WebhookActionResult
|
||||
WebhookActionResult,
|
||||
} from "@medusajs/types"
|
||||
|
||||
|
||||
export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
implements IPaymentProvider
|
||||
{
|
||||
/**
|
||||
* Override this static method in order for the loader to validate the options provided to the module provider.
|
||||
* @param options
|
||||
*/
|
||||
static validateOptions(options: Record<any, any>): void | never {}
|
||||
|
||||
/**
|
||||
* You can use the `constructor` of the provider's service to access resources in your module's container.
|
||||
*
|
||||
@@ -27,45 +32,45 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import {
|
||||
* import {
|
||||
* AbstractPaymentProvider
|
||||
* } from "@medusajs/utils"
|
||||
* import { Logger } from "@medusajs/types"
|
||||
*
|
||||
*
|
||||
* type InjectedDependencies = {
|
||||
* logger: Logger
|
||||
* }
|
||||
*
|
||||
*
|
||||
* type Options = {
|
||||
* apiKey: string
|
||||
* }
|
||||
*
|
||||
*
|
||||
* class MyPaymentProviderService extends AbstractPaymentProvider<
|
||||
* Options
|
||||
* > {
|
||||
* protected logger_: Logger
|
||||
* protected options_: Options
|
||||
* // Assuming you're using a client to integrate
|
||||
* // Assuming you're using a client to integrate
|
||||
* // with a third-party service
|
||||
* protected client
|
||||
*
|
||||
*
|
||||
* constructor(
|
||||
* { logger }: InjectedDependencies,
|
||||
* options: Options
|
||||
* ) {
|
||||
* // @ts-ignore
|
||||
* super(...arguments)
|
||||
*
|
||||
*
|
||||
* this.logger_ = logger
|
||||
* this.options_ = options
|
||||
*
|
||||
*
|
||||
* // Assuming you're initializing a client
|
||||
* this.client = new Client(options)
|
||||
* }
|
||||
*
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
*
|
||||
* export default MyPaymentProviderService
|
||||
* ```
|
||||
*/
|
||||
@@ -88,7 +93,7 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
|
||||
/**
|
||||
* Each payment provider has a unique identifier defined in its class.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* class MyPaymentProviderService extends AbstractPaymentProvider<
|
||||
* Options
|
||||
@@ -123,18 +128,18 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* - A webhook event occurred that instructs the payment provider to capture the payment session. Learn more about handing webhook events in [this guide](https://docs.medusajs.com/v2/resources/commerce-modules/payment/webhook-events).
|
||||
*
|
||||
* In this method, use the third-party provider to capture the payment.
|
||||
*
|
||||
*
|
||||
* @param paymentData - The `data` property of the payment. Make sure to store in it
|
||||
* any helpful identification for your third-party integration.
|
||||
* @returns The new data to store in the payment's `data` property, or an error object.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* // other imports...
|
||||
* import {
|
||||
* PaymentProviderError,
|
||||
* PaymentProviderSessionResponse,
|
||||
* import {
|
||||
* PaymentProviderError,
|
||||
* PaymentProviderSessionResponse,
|
||||
* } from "@medusajs/types"
|
||||
*
|
||||
*
|
||||
* class MyPaymentProviderService extends AbstractPaymentProvider<
|
||||
* Options
|
||||
* > {
|
||||
@@ -142,10 +147,10 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* paymentData: Record<string, unknown>
|
||||
* ): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]> {
|
||||
* const externalId = paymentData.id
|
||||
*
|
||||
*
|
||||
* try {
|
||||
* const newData = await this.client.capturePayment(externalId)
|
||||
*
|
||||
*
|
||||
* return {
|
||||
* ...newData,
|
||||
* id: externalId
|
||||
@@ -158,7 +163,7 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
@@ -169,45 +174,45 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
/**
|
||||
* This method authorizes a payment session. When authorized successfully, a payment is created by the Payment
|
||||
* Module which can be later captured using the {@link capturePayment} method.
|
||||
*
|
||||
*
|
||||
* Refer to [this guide](https://docs.medusajs.com/v2/resources/commerce-modules/payment/payment-flow#3-authorize-payment-session)
|
||||
* to learn more about how this fits into the payment flow and how to handle required actions.
|
||||
*
|
||||
*
|
||||
* To automatically capture the payment after authorization, return the status `captured`.
|
||||
*
|
||||
*
|
||||
* @param paymentSessionData - The `data` property of the payment session. Make sure to store in it
|
||||
* any helpful identification for your third-party integration.
|
||||
* @param context - The context in which the payment is being authorized. For example, in checkout,
|
||||
* @param context - The context in which the payment is being authorized. For example, in checkout,
|
||||
* the context has a `cart_id` property indicating the ID of the associated cart.
|
||||
* @returns Either an object of the new data to store in the created payment's `data` property and the
|
||||
* @returns Either an object of the new data to store in the created payment's `data` property and the
|
||||
* payment's status, or an error object. Make sure to set in `data` anything useful to later retrieve the session.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* // other imports...
|
||||
* import {
|
||||
* PaymentProviderError,
|
||||
* PaymentProviderSessionResponse,
|
||||
* import {
|
||||
* PaymentProviderError,
|
||||
* PaymentProviderSessionResponse,
|
||||
* PaymentSessionStatus
|
||||
* } from "@medusajs/types"
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* class MyPaymentProviderService extends AbstractPaymentProvider<
|
||||
* Options
|
||||
* > {
|
||||
* async authorizePayment(
|
||||
* paymentSessionData: Record<string, unknown>,
|
||||
* paymentSessionData: Record<string, unknown>,
|
||||
* context: Record<string, unknown>
|
||||
* ): Promise<
|
||||
* PaymentProviderError | {
|
||||
* PaymentProviderError | {
|
||||
* status: PaymentSessionStatus
|
||||
* data: PaymentProviderSessionResponse["data"]
|
||||
* }
|
||||
* > {
|
||||
* const externalId = paymentSessionData.id
|
||||
*
|
||||
*
|
||||
* try {
|
||||
* const paymentData = await this.client.authorizePayment(externalId)
|
||||
*
|
||||
*
|
||||
* return {
|
||||
* data: {
|
||||
* ...paymentData,
|
||||
@@ -223,7 +228,7 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
@@ -246,19 +251,19 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
|
||||
/**
|
||||
* This method cancels a payment.
|
||||
*
|
||||
*
|
||||
* @param paymentData - The `data` property of the payment. Make sure to store in it
|
||||
* any helpful identification for your third-party integration.
|
||||
* @returns An error object if an error occurs, or the data received from the integration.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* // other imports...
|
||||
* import {
|
||||
* PaymentProviderError,
|
||||
* PaymentProviderSessionResponse,
|
||||
* import {
|
||||
* PaymentProviderError,
|
||||
* PaymentProviderSessionResponse,
|
||||
* } from "@medusajs/types"
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* class MyPaymentProviderService extends AbstractPaymentProvider<
|
||||
* Options
|
||||
* > {
|
||||
@@ -266,7 +271,7 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* paymentData: Record<string, unknown>
|
||||
* ): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]> {
|
||||
* const externalId = paymentData.id
|
||||
*
|
||||
*
|
||||
* try {
|
||||
* const paymentData = await this.client.cancelPayment(externalId)
|
||||
* } catch (e) {
|
||||
@@ -277,7 +282,7 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
@@ -288,36 +293,36 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
/**
|
||||
* This method is used when a payment session is created. It can be used to initiate the payment
|
||||
* in the third-party session, before authorizing or capturing the payment later.
|
||||
*
|
||||
*
|
||||
* @param context - The details of the payment session and its context.
|
||||
* @returns An object whose `data` property is set in the created payment session, or an error
|
||||
* object. Make sure to set in `data` anything useful to later retrieve the session.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* // other imports...
|
||||
* import {
|
||||
* PaymentProviderError,
|
||||
* PaymentProviderSessionResponse,
|
||||
* import {
|
||||
* PaymentProviderError,
|
||||
* PaymentProviderSessionResponse,
|
||||
* } from "@medusajs/types"
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* class MyPaymentProviderService extends AbstractPaymentProvider<
|
||||
* Options
|
||||
* > {
|
||||
* async initiatePayment(
|
||||
* context: CreatePaymentProviderSession
|
||||
* ): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
|
||||
* const {
|
||||
* amount,
|
||||
* currency_code,
|
||||
* const {
|
||||
* amount,
|
||||
* currency_code,
|
||||
* context: customerDetails
|
||||
* } = context
|
||||
*
|
||||
*
|
||||
* try {
|
||||
* const response = await this.client.init(
|
||||
* amount, currency_code, customerDetails
|
||||
* )
|
||||
*
|
||||
*
|
||||
* return {
|
||||
* ...response,
|
||||
* data: {
|
||||
@@ -332,7 +337,7 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
@@ -342,21 +347,21 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
|
||||
/**
|
||||
* This method is used when a payment session is deleted, which can only happen if it isn't authorized, yet.
|
||||
*
|
||||
*
|
||||
* Use this to delete or cancel the payment in the third-party service.
|
||||
*
|
||||
*
|
||||
* @param paymentSessionData - The `data` property of the payment session. Make sure to store in it
|
||||
* any helpful identification for your third-party integration.
|
||||
* @returns An error object or the response from the third-party service.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* // other imports...
|
||||
* import {
|
||||
* PaymentProviderError,
|
||||
* PaymentProviderSessionResponse,
|
||||
* import {
|
||||
* PaymentProviderError,
|
||||
* PaymentProviderSessionResponse,
|
||||
* } from "@medusajs/types"
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* class MyPaymentProviderService extends AbstractPaymentProvider<
|
||||
* Options
|
||||
* > {
|
||||
@@ -366,7 +371,7 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* PaymentProviderError | PaymentProviderSessionResponse["data"]
|
||||
* > {
|
||||
* const externalId = paymentSessionData.id
|
||||
*
|
||||
*
|
||||
* try {
|
||||
* await this.client.cancelPayment(externalId)
|
||||
* } catch (e) {
|
||||
@@ -377,7 +382,7 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
@@ -387,18 +392,18 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
|
||||
/**
|
||||
* This method gets the status of a payment session based on the status in the third-party integration.
|
||||
*
|
||||
*
|
||||
* @param paymentSessionData - The `data` property of the payment session. Make sure to store in it
|
||||
* any helpful identification for your third-party integration.
|
||||
* @returns The payment session's status.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* // other imports...
|
||||
* import {
|
||||
* import {
|
||||
* PaymentSessionStatus
|
||||
* } from "@medusajs/types"
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* class MyPaymentProviderService extends AbstractPaymentProvider<
|
||||
* Options
|
||||
* > {
|
||||
@@ -406,10 +411,10 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* paymentSessionData: Record<string, unknown>
|
||||
* ): Promise<PaymentSessionStatus> {
|
||||
* const externalId = paymentSessionData.id
|
||||
*
|
||||
*
|
||||
* try {
|
||||
* const status = await this.client.getStatus(externalId)
|
||||
*
|
||||
*
|
||||
* switch (status) {
|
||||
* case "requires_capture":
|
||||
* return "authorized"
|
||||
@@ -424,7 +429,7 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* return "error"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
@@ -434,37 +439,37 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
|
||||
/**
|
||||
* This method refunds an amount of a payment previously captured.
|
||||
*
|
||||
*
|
||||
* @param paymentData - The `data` property of the payment. Make sure to store in it
|
||||
* any helpful identification for your third-party integration.
|
||||
* @param refundAmount The amount to refund.
|
||||
* @returns The new data to store in the payment's `data` property, or an error object.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* // other imports...
|
||||
* import {
|
||||
* PaymentProviderError,
|
||||
* PaymentProviderSessionResponse,
|
||||
* import {
|
||||
* PaymentProviderError,
|
||||
* PaymentProviderSessionResponse,
|
||||
* } from "@medusajs/types"
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* class MyPaymentProviderService extends AbstractPaymentProvider<
|
||||
* Options
|
||||
* > {
|
||||
* async refundPayment(
|
||||
* paymentData: Record<string, unknown>,
|
||||
* paymentData: Record<string, unknown>,
|
||||
* refundAmount: number
|
||||
* ): Promise<
|
||||
* PaymentProviderError | PaymentProviderSessionResponse["data"]
|
||||
* > {
|
||||
* const externalId = paymentData.id
|
||||
*
|
||||
*
|
||||
* try {
|
||||
* const newData = await this.client.refund(
|
||||
* externalId,
|
||||
* refundAmount
|
||||
* )
|
||||
*
|
||||
*
|
||||
* return {
|
||||
* ...newData,
|
||||
* id: externalId
|
||||
@@ -477,7 +482,7 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
@@ -488,19 +493,19 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
|
||||
/**
|
||||
* Retrieves the payment's data from the third-party service.
|
||||
*
|
||||
*
|
||||
* @param paymentSessionData - The `data` property of the payment. Make sure to store in it
|
||||
* any helpful identification for your third-party integration.
|
||||
* @returns An object to be stored in the payment's `data` property, or an error object.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* // other imports...
|
||||
* import {
|
||||
* PaymentProviderError,
|
||||
* PaymentProviderSessionResponse,
|
||||
* import {
|
||||
* PaymentProviderError,
|
||||
* PaymentProviderSessionResponse,
|
||||
* } from "@medusajs/types"
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* class MyPaymentProviderService extends AbstractPaymentProvider<
|
||||
* Options
|
||||
* > {
|
||||
@@ -510,7 +515,7 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* PaymentProviderError | PaymentProviderSessionResponse["data"]
|
||||
* > {
|
||||
* const externalId = paymentSessionData.id
|
||||
*
|
||||
*
|
||||
* try {
|
||||
* return await this.client.retrieve(externalId)
|
||||
* } catch (e) {
|
||||
@@ -521,7 +526,7 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
@@ -531,34 +536,34 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
|
||||
/**
|
||||
* Update a payment in the third-party service that was previously initiated with the {@link initiatePayment} method.
|
||||
*
|
||||
*
|
||||
* @param context - The details of the payment session and its context.
|
||||
* @returns An object whose `data` property is set in the updated payment session, or an error
|
||||
* object. Make sure to set in `data` anything useful to later retrieve the session.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* // other imports...
|
||||
* import {
|
||||
* import {
|
||||
* UpdatePaymentProviderSession,
|
||||
* PaymentProviderError,
|
||||
* PaymentProviderSessionResponse,
|
||||
* PaymentProviderError,
|
||||
* PaymentProviderSessionResponse,
|
||||
* } from "@medusajs/types"
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* class MyPaymentProviderService extends AbstractPaymentProvider<
|
||||
* Options
|
||||
* > {
|
||||
* async updatePayment(
|
||||
* context: UpdatePaymentProviderSession
|
||||
* ): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
|
||||
* const {
|
||||
* amount,
|
||||
* currency_code,
|
||||
* const {
|
||||
* amount,
|
||||
* currency_code,
|
||||
* context: customerDetails,
|
||||
* data
|
||||
* } = context
|
||||
* const externalId = data.id
|
||||
*
|
||||
*
|
||||
* try {
|
||||
* const response = await this.client.update(
|
||||
* externalId,
|
||||
@@ -568,7 +573,7 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* customerDetails
|
||||
* }
|
||||
* )
|
||||
*
|
||||
*
|
||||
* return {
|
||||
* ...response,
|
||||
* data: {
|
||||
@@ -583,7 +588,7 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
@@ -594,24 +599,24 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* Learn more in [this documentation](https://docs.medusajs.com/v2/resources/commerce-modules/payment/webhook-events)
|
||||
*
|
||||
*
|
||||
* @param data - The webhook event's data
|
||||
* @returns The webhook result. If the `action`'s value is `captured`, the payment is captured within Medusa as well.
|
||||
* If the `action`'s value is `authorized`, the associated payment session is authorized within Medusa.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* // other imports...
|
||||
* import {
|
||||
* import {
|
||||
* BigNumber
|
||||
* } from "@medusajs/utils"
|
||||
* import {
|
||||
* import {
|
||||
* ProviderWebhookPayload,
|
||||
* WebhookActionResult
|
||||
* } from "@medusajs/types"
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* class MyPaymentProviderService extends AbstractPaymentProvider<
|
||||
* Options
|
||||
* > {
|
||||
@@ -623,7 +628,7 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* rawData,
|
||||
* headers
|
||||
* } = payload
|
||||
*
|
||||
*
|
||||
* try {
|
||||
* switch(data.event_type) {
|
||||
* case "authorized_amount":
|
||||
@@ -657,7 +662,7 @@ export abstract class AbstractPaymentProvider<TConfig = Record<string, unknown>>
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { generateJwtToken, MedusaError } from "@medusajs/utils"
|
||||
import { GoogleAuthService } from "../../src/services/google"
|
||||
jest.setTimeout(100000)
|
||||
import { http, HttpResponse } from "msw"
|
||||
import { setupServer } from "msw/node"
|
||||
|
||||
jest.setTimeout(100000)
|
||||
|
||||
const sampleIdPayload = {
|
||||
iss: "https://accounts.google.com",
|
||||
azp: "199301612397-l1lrg08vd6dvu98r43l7ul0ri2rd2b6r.apps.googleusercontent.com",
|
||||
@@ -97,15 +98,10 @@ describe("Google auth provider", () => {
|
||||
it("throw an error if required options are not passed", async () => {
|
||||
let msg = ""
|
||||
try {
|
||||
new GoogleAuthService(
|
||||
{
|
||||
logger: console as any,
|
||||
},
|
||||
{
|
||||
clientID: "test",
|
||||
clientSecret: "test",
|
||||
} as any
|
||||
)
|
||||
GoogleAuthService.validateOptions({
|
||||
clientID: "test",
|
||||
clientSecret: "test",
|
||||
} as any)
|
||||
} catch (e) {
|
||||
msg = e.message
|
||||
}
|
||||
|
||||
@@ -19,12 +19,25 @@ export class GoogleAuthService extends AbstractAuthModuleProvider {
|
||||
protected config_: LocalServiceConfig
|
||||
protected logger_: Logger
|
||||
|
||||
static validateOptions(options: GoogleAuthProviderOptions) {
|
||||
if (!options.clientID) {
|
||||
throw new Error("Google clientID is required")
|
||||
}
|
||||
|
||||
if (!options.clientSecret) {
|
||||
throw new Error("Google clientSecret is required")
|
||||
}
|
||||
|
||||
if (!options.callbackURL) {
|
||||
throw new Error("Google callbackUrl is required")
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
{ logger }: InjectedDependencies,
|
||||
options: GoogleAuthProviderOptions
|
||||
) {
|
||||
super({}, { provider: "google", displayName: "Google Authentication" })
|
||||
this.validateConfig(options)
|
||||
this.config_ = options
|
||||
this.logger_ = logger
|
||||
}
|
||||
@@ -173,18 +186,4 @@ export class GoogleAuthService extends AbstractAuthModuleProvider {
|
||||
|
||||
return { success: true, location: authUrl.toString() }
|
||||
}
|
||||
|
||||
private validateConfig(config: LocalServiceConfig) {
|
||||
if (!config.clientID) {
|
||||
throw new Error("Google clientID is required")
|
||||
}
|
||||
|
||||
if (!config.clientSecret) {
|
||||
throw new Error("Google clientSecret is required")
|
||||
}
|
||||
|
||||
if (!config.callbackURL) {
|
||||
throw new Error("Google callbackUrl is required")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ import {
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
AbstractPaymentProvider,
|
||||
MedusaError,
|
||||
PaymentActions,
|
||||
PaymentSessionStatus,
|
||||
isDefined,
|
||||
isPaymentProviderError,
|
||||
isPresent,
|
||||
MedusaError,
|
||||
PaymentActions,
|
||||
PaymentSessionStatus,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
ErrorCodes,
|
||||
@@ -36,6 +36,12 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
protected stripe_: Stripe
|
||||
protected container_: MedusaContainer
|
||||
|
||||
static validateOptions(options: StripeOptions): void {
|
||||
if (!isDefined(options.apiKey)) {
|
||||
throw new Error("Required option `apiKey` is missing in Stripe plugin")
|
||||
}
|
||||
}
|
||||
|
||||
protected constructor(container: MedusaContainer, options: StripeOptions) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
@@ -43,23 +49,11 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
this.container_ = container
|
||||
this.options_ = options
|
||||
|
||||
this.stripe_ = this.init()
|
||||
}
|
||||
|
||||
protected init() {
|
||||
this.validateOptions(this.config)
|
||||
|
||||
return new Stripe(this.config.apiKey)
|
||||
this.stripe_ = new Stripe(options.apiKey)
|
||||
}
|
||||
|
||||
abstract get paymentIntentOptions(): PaymentIntentOptions
|
||||
|
||||
private validateOptions(options: StripeOptions): void {
|
||||
if (!isDefined(options.apiKey)) {
|
||||
throw new Error("Required option `apiKey` is missing in Stripe plugin")
|
||||
}
|
||||
}
|
||||
|
||||
get options(): StripeOptions {
|
||||
return this.options_
|
||||
}
|
||||
@@ -370,7 +364,7 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
return this.stripe_.webhooks.constructEvent(
|
||||
data.rawData as string | Buffer,
|
||||
signature,
|
||||
this.config.webhookSecret
|
||||
this.options_.webhookSecret
|
||||
)
|
||||
}
|
||||
protected buildError(
|
||||
|
||||
Reference in New Issue
Block a user