diff --git a/.changeset/breezy-flowers-fly.md b/.changeset/breezy-flowers-fly.md new file mode 100644 index 0000000000..9838c28c94 --- /dev/null +++ b/.changeset/breezy-flowers-fly.md @@ -0,0 +1,8 @@ +--- +"@medusajs/notification": patch +"@medusajs/payment": patch +"@medusajs/types": patch +"@medusajs/utils": patch +--- + +add Medusa Cloud Email provider diff --git a/packages/core/types/src/common/config-module.ts b/packages/core/types/src/common/config-module.ts index a648dd7891..9c87aa2bd5 100644 --- a/packages/core/types/src/common/config-module.ts +++ b/packages/core/types/src/common/config-module.ts @@ -213,6 +213,26 @@ export type HttpCompressionOptions = { threshold?: number | string } +/** + * @interface + * + * Medusa Cloud configurations. + */ +export type MedusaCloudOptions = { + /** + * The environment handle of the Medusa Cloud environment. + */ + environmentHandle?: string + /** + * The API key used to access Medusa Cloud services. + */ + apiKey?: string + /** + * The endpoint of the Medusa Cloud email service. + */ + emailsEndpoint?: string +} + /** * @interface * @@ -865,6 +885,12 @@ export type ProjectConfigOptions = { /*admin?: string[]*/ } } + + /** + * This property holds configurations for running in Medusa Cloud. + * It gets automatically populated in the cloud, and is not needed outside of it. + */ + medusaCloudOptions?: MedusaCloudOptions } /** diff --git a/packages/core/utils/src/common/__tests__/define-config.spec.ts b/packages/core/utils/src/common/__tests__/define-config.spec.ts index 9e7bca9892..dbb9f24237 100644 --- a/packages/core/utils/src/common/__tests__/define-config.spec.ts +++ b/packages/core/utils/src/common/__tests__/define-config.spec.ts @@ -2010,4 +2010,371 @@ describe("defineConfig", function () { } `) }) + + it("should add cloud options to the project config and relevant modules if the environment variables are set", function () { + const originalEnv = { ...process.env } + process.env.MEDUSA_CLOUD_ENVIRONMENT_HANDLE = "test-environment" + process.env.MEDUSA_CLOUD_API_KEY = "test-api-key" + process.env.MEDUSA_CLOUD_EMAILS_ENDPOINT = "test-emails-endpoint" + const config = defineConfig() + process.env = { ...originalEnv } + + expect(config).toMatchInlineSnapshot(` + { + "admin": { + "backendUrl": "/", + "path": "/app", + }, + "featureFlags": {}, + "logger": undefined, + "modules": { + "api_key": { + "resolve": "@medusajs/medusa/api-key", + }, + "auth": { + "options": { + "providers": [ + { + "id": "emailpass", + "resolve": "@medusajs/medusa/auth-emailpass", + }, + ], + }, + "resolve": "@medusajs/medusa/auth", + }, + "cache": { + "resolve": "@medusajs/medusa/cache-inmemory", + }, + "cart": { + "resolve": "@medusajs/medusa/cart", + }, + "currency": { + "resolve": "@medusajs/medusa/currency", + }, + "customer": { + "resolve": "@medusajs/medusa/customer", + }, + "event_bus": { + "resolve": "@medusajs/medusa/event-bus-local", + }, + "file": { + "options": { + "providers": [ + { + "id": "local", + "resolve": "@medusajs/medusa/file-local", + }, + ], + }, + "resolve": "@medusajs/medusa/file", + }, + "fulfillment": { + "options": { + "providers": [ + { + "id": "manual", + "resolve": "@medusajs/medusa/fulfillment-manual", + }, + ], + }, + "resolve": "@medusajs/medusa/fulfillment", + }, + "inventory": { + "resolve": "@medusajs/medusa/inventory", + }, + "locking": { + "resolve": "@medusajs/medusa/locking", + }, + "notification": { + "options": { + "cloud": { + "api_key": "test-api-key", + "endpoint": "test-emails-endpoint", + "environment_handle": "test-environment", + }, + "providers": [ + { + "id": "local", + "options": { + "channels": [ + "feed", + ], + "name": "Local Notification Provider", + }, + "resolve": "@medusajs/medusa/notification-local", + }, + ], + }, + "resolve": "@medusajs/medusa/notification", + }, + "order": { + "resolve": "@medusajs/medusa/order", + }, + "payment": { + "resolve": "@medusajs/medusa/payment", + }, + "pricing": { + "resolve": "@medusajs/medusa/pricing", + }, + "product": { + "resolve": "@medusajs/medusa/product", + }, + "promotion": { + "resolve": "@medusajs/medusa/promotion", + }, + "region": { + "resolve": "@medusajs/medusa/region", + }, + "sales_channel": { + "resolve": "@medusajs/medusa/sales-channel", + }, + "settings": { + "resolve": "@medusajs/medusa/settings", + }, + "stock_location": { + "resolve": "@medusajs/medusa/stock-location", + }, + "store": { + "resolve": "@medusajs/medusa/store", + }, + "tax": { + "resolve": "@medusajs/medusa/tax", + }, + "user": { + "options": { + "jwt_options": undefined, + "jwt_public_key": undefined, + "jwt_secret": "supersecret", + "jwt_verify_options": undefined, + }, + "resolve": "@medusajs/medusa/user", + }, + "workflows": { + "resolve": "@medusajs/medusa/workflow-engine-inmemory", + }, + }, + "plugins": [ + { + "options": {}, + "resolve": "@medusajs/draft-order", + }, + ], + "projectConfig": { + "databaseUrl": "postgres://localhost/medusa-starter-default", + "http": { + "adminCors": "http://localhost:7000,http://localhost:7001,http://localhost:5173", + "authCors": "http://localhost:7000,http://localhost:7001,http://localhost:5173", + "cookieSecret": "supersecret", + "jwtPublicKey": undefined, + "jwtSecret": "supersecret", + "restrictedFields": { + "store": [ + ${DEFAULT_STORE_RESTRICTED_FIELDS.map((v) => `"${v}"`).join( + ",\n " + )}, + ], + }, + "storeCors": "http://localhost:8000", + }, + "medusaCloudOptions": { + "apiKey": "test-api-key", + "emailsEndpoint": "test-emails-endpoint", + "environmentHandle": "test-environment", + }, + "redisOptions": { + "retryStrategy": [Function], + }, + "sessionOptions": {}, + }, + } + `) + }) + + it("should merge custom projectConfig.medusaCloudOptions", function () { + const originalEnv = { ...process.env } + process.env.MEDUSA_CLOUD_ENVIRONMENT_HANDLE = "test-environment" + process.env.MEDUSA_CLOUD_API_KEY = "test-api-key" + process.env.MEDUSA_CLOUD_EMAILS_ENDPOINT = "test-emails-endpoint" + const config = defineConfig({ + projectConfig: { + http: {} as any, + medusaCloudOptions: { + environmentHandle: "overriden-environment", + apiKey: "overriden-api-key", + emailsEndpoint: "overriden-emails-endpoint", + }, + }, + }) + process.env = { ...originalEnv } + + expect(config).toMatchInlineSnapshot(` + { + "admin": { + "backendUrl": "/", + "path": "/app", + }, + "featureFlags": {}, + "logger": undefined, + "modules": { + "api_key": { + "resolve": "@medusajs/medusa/api-key", + }, + "auth": { + "options": { + "providers": [ + { + "id": "emailpass", + "resolve": "@medusajs/medusa/auth-emailpass", + }, + ], + }, + "resolve": "@medusajs/medusa/auth", + }, + "cache": { + "resolve": "@medusajs/medusa/cache-inmemory", + }, + "cart": { + "resolve": "@medusajs/medusa/cart", + }, + "currency": { + "resolve": "@medusajs/medusa/currency", + }, + "customer": { + "resolve": "@medusajs/medusa/customer", + }, + "event_bus": { + "resolve": "@medusajs/medusa/event-bus-local", + }, + "file": { + "options": { + "providers": [ + { + "id": "local", + "resolve": "@medusajs/medusa/file-local", + }, + ], + }, + "resolve": "@medusajs/medusa/file", + }, + "fulfillment": { + "options": { + "providers": [ + { + "id": "manual", + "resolve": "@medusajs/medusa/fulfillment-manual", + }, + ], + }, + "resolve": "@medusajs/medusa/fulfillment", + }, + "inventory": { + "resolve": "@medusajs/medusa/inventory", + }, + "locking": { + "resolve": "@medusajs/medusa/locking", + }, + "notification": { + "options": { + "cloud": { + "api_key": "overriden-api-key", + "endpoint": "overriden-emails-endpoint", + "environment_handle": "overriden-environment", + }, + "providers": [ + { + "id": "local", + "options": { + "channels": [ + "feed", + ], + "name": "Local Notification Provider", + }, + "resolve": "@medusajs/medusa/notification-local", + }, + ], + }, + "resolve": "@medusajs/medusa/notification", + }, + "order": { + "resolve": "@medusajs/medusa/order", + }, + "payment": { + "resolve": "@medusajs/medusa/payment", + }, + "pricing": { + "resolve": "@medusajs/medusa/pricing", + }, + "product": { + "resolve": "@medusajs/medusa/product", + }, + "promotion": { + "resolve": "@medusajs/medusa/promotion", + }, + "region": { + "resolve": "@medusajs/medusa/region", + }, + "sales_channel": { + "resolve": "@medusajs/medusa/sales-channel", + }, + "settings": { + "resolve": "@medusajs/medusa/settings", + }, + "stock_location": { + "resolve": "@medusajs/medusa/stock-location", + }, + "store": { + "resolve": "@medusajs/medusa/store", + }, + "tax": { + "resolve": "@medusajs/medusa/tax", + }, + "user": { + "options": { + "jwt_options": undefined, + "jwt_public_key": undefined, + "jwt_secret": "supersecret", + "jwt_verify_options": undefined, + }, + "resolve": "@medusajs/medusa/user", + }, + "workflows": { + "resolve": "@medusajs/medusa/workflow-engine-inmemory", + }, + }, + "plugins": [ + { + "options": {}, + "resolve": "@medusajs/draft-order", + }, + ], + "projectConfig": { + "databaseUrl": "postgres://localhost/medusa-starter-default", + "http": { + "adminCors": "http://localhost:7000,http://localhost:7001,http://localhost:5173", + "authCors": "http://localhost:7000,http://localhost:7001,http://localhost:5173", + "cookieSecret": "supersecret", + "jwtPublicKey": undefined, + "jwtSecret": "supersecret", + "restrictedFields": { + "store": [ + ${DEFAULT_STORE_RESTRICTED_FIELDS.map((v) => `"${v}"`).join( + ",\n " + )}, + ], + }, + "storeCors": "http://localhost:8000", + }, + "medusaCloudOptions": { + "apiKey": "overriden-api-key", + "emailsEndpoint": "overriden-emails-endpoint", + "environmentHandle": "overriden-environment", + }, + "redisOptions": { + "retryStrategy": [Function], + }, + "sessionOptions": {}, + }, + } + `) + }) }) diff --git a/packages/core/utils/src/common/define-config.ts b/packages/core/utils/src/common/define-config.ts index d5db7e8822..c67ab9f98b 100644 --- a/packages/core/utils/src/common/define-config.ts +++ b/packages/core/utils/src/common/define-config.ts @@ -3,6 +3,7 @@ import { InputConfig, InputConfigModules, InternalModuleDeclaration, + MedusaCloudOptions, } from "@medusajs/types" import { MODULE_PACKAGE_NAMES, @@ -49,6 +50,7 @@ export function defineConfig(config: InputConfig = {}): ConfigModule { const projectConfig = normalizeProjectConfig(config.projectConfig, options) const adminConfig = normalizeAdminConfig(config.admin) const modules = resolveModules(config.modules, options, config.projectConfig) + applyCloudOptionsToModules(modules, projectConfig?.medusaCloudOptions) const plugins = resolvePlugins(config.plugins, options) return { @@ -363,8 +365,23 @@ function normalizeProjectConfig( projectConfig: InputConfig["projectConfig"], { isCloud }: { isCloud: boolean } ): ConfigModule["projectConfig"] { - const { http, redisOptions, sessionOptions, ...restOfProjectConfig } = - projectConfig || {} + const { + http, + redisOptions, + sessionOptions, + medusaCloudOptions, + ...restOfProjectConfig + } = projectConfig || {} + + const mergedCloudOptions: MedusaCloudOptions = { + environmentHandle: process.env.MEDUSA_CLOUD_ENVIRONMENT_HANDLE, + apiKey: process.env.MEDUSA_CLOUD_API_KEY, + emailsEndpoint: process.env.MEDUSA_CLOUD_EMAILS_ENDPOINT, + ...medusaCloudOptions, + } + const hasCloudOptions = Object.values(mergedCloudOptions).some( + (value) => value !== undefined + ) /** * The defaults to use for the project config. They are shallow merged @@ -426,6 +443,8 @@ function normalizeProjectConfig( : {}), ...sessionOptions, }, + // If there are no cloud options, we better don't pollute the project config for people not using the cloud + ...(hasCloudOptions ? { medusaCloudOptions: mergedCloudOptions } : {}), ...restOfProjectConfig, } satisfies ConfigModule["projectConfig"] @@ -445,3 +464,35 @@ function normalizeAdminConfig( ...adminConfig, } } + +function applyCloudOptionsToModules( + modules: Exclude, + config?: MedusaCloudOptions +) { + if (!config) { + return + } + + for (const name in modules) { + const module = modules[name] + if (typeof module !== "object") { + continue + } + + switch (name) { + case Modules.NOTIFICATION: + module.options = { + cloud: { + api_key: config.apiKey, + endpoint: config.emailsEndpoint, + environment_handle: config.environmentHandle, + }, + ...(module.options ?? {}), + } + break + // Will add payment module soon + default: + break + } + } +} diff --git a/packages/core/utils/src/dal/mikro-orm/mikro-orm-create-connection.ts b/packages/core/utils/src/dal/mikro-orm/mikro-orm-create-connection.ts index 4c6c691b93..c5defbe026 100644 --- a/packages/core/utils/src/dal/mikro-orm/mikro-orm-create-connection.ts +++ b/packages/core/utils/src/dal/mikro-orm/mikro-orm-create-connection.ts @@ -1,6 +1,6 @@ -import { ModuleServiceInitializeOptions } from "@medusajs/types" import { Filter as MikroORMFilter } from "@medusajs/deps/mikro-orm/core" import { TSMigrationGenerator } from "@medusajs/deps/mikro-orm/migrations" +import { ModuleServiceInitializeOptions } from "@medusajs/types" import { isString, retryExecution, stringifyCircular } from "../../common" import { normalizeMigrationSQL } from "../utils" import { CustomDBMigrator } from "./custom-db-migrator" diff --git a/packages/modules/notification/integration-tests/__tests__/notification-module-service/index.spec.ts b/packages/modules/notification/integration-tests/__tests__/notification-module-service/index.spec.ts index 63d713f5a9..089c5db5c0 100644 --- a/packages/modules/notification/integration-tests/__tests__/notification-module-service/index.spec.ts +++ b/packages/modules/notification/integration-tests/__tests__/notification-module-service/index.spec.ts @@ -7,11 +7,11 @@ import { NotificationEvents, NotificationStatus, } from "@medusajs/framework/utils" -import { NotificationModuleService } from "@services" import { MockEventBusService, moduleIntegrationTestRunner, } from "@medusajs/test-utils" +import { NotificationModuleService } from "@services" import { resolve } from "path" let moduleOptions = { diff --git a/packages/modules/notification/integration-tests/__tests__/notification-module-service/medusa-cloud-email.spec.ts b/packages/modules/notification/integration-tests/__tests__/notification-module-service/medusa-cloud-email.spec.ts new file mode 100644 index 0000000000..50f0034ba2 --- /dev/null +++ b/packages/modules/notification/integration-tests/__tests__/notification-module-service/medusa-cloud-email.spec.ts @@ -0,0 +1,168 @@ +import { INotificationModuleService } from "@medusajs/framework/types" +import { Modules, NotificationStatus } from "@medusajs/framework/utils" +import { moduleIntegrationTestRunner } from "@medusajs/test-utils" +import { resolve } from "path" + +jest.setTimeout(30000) + +const successMedusaCloudEmailResponse = { + ok: true, + status: 200, + statusText: "OK", + json: () => Promise.resolve({ id: "external_id_1" }), +} + +const testNotification = { + to: "customer@test.com", + template: "some-template", + channel: "email", + data: { + link: "https://test.com", + }, +} + +moduleIntegrationTestRunner({ + moduleName: Modules.NOTIFICATION, + moduleOptions: { + cloud: { + api_key: "test-api-key", + endpoint: "https://medusacloud.com/emails", + environment_handle: "test-environment", + }, + }, + testSuite: ({ service }) => + describe("Medusa Cloud Email provider", () => { + let fetchMock: jest.SpyInstance + + beforeEach(() => { + fetchMock = jest + .spyOn(globalThis, "fetch") + .mockImplementation( + async () => successMedusaCloudEmailResponse as any + ) + }) + + afterEach(() => { + fetchMock.mockClear() + }) + + it("should send email notification to Medusa Cloud", async () => { + const result = await service.createNotifications(testNotification) + expect(result).toEqual( + expect.objectContaining({ + provider_id: "cloud", + external_id: "external_id_1", + status: NotificationStatus.SUCCESS, + }) + ) + + const [url, request] = fetchMock.mock.calls[0] + expect(url).toBe("https://medusacloud.com/emails/send") + expect(request.method).toBe("POST") + expect(request.headers["Content-Type"]).toBe("application/json") + expect(request.headers["Authorization"]).toBe("Basic test-api-key") + expect(request.headers["x-medusa-environment-handle"]).toBe( + "test-environment" + ) + expect(JSON.parse(request.body)).toEqual({ + to: "customer@test.com", + template: "some-template", + data: { + link: "https://test.com", + }, + }) + }) + + it("should return an error if the Medusa Cloud Email provider fails", async () => { + fetchMock.mockImplementation( + async () => + ({ + ok: false, + status: 500, + statusText: "Internal Server Error", + json: () => Promise.resolve({ message: "Internal Server Error" }), + } as any) + ) + + await expect( + service.createNotifications(testNotification) + ).rejects.toThrow() + }) + }), +}) + +moduleIntegrationTestRunner({ + moduleName: Modules.NOTIFICATION, + moduleOptions: { + cloud: { + api_key: "test-api-key", + endpoint: "https://medusacloud.com/emails", + environment_handle: "test-environment", + }, + providers: [ + { + resolve: resolve( + process.cwd() + + "/integration-tests/__fixtures__/providers/default-provider" + ), + id: "test-provider", + options: { + name: "Test provider", + channels: ["email"], + }, + }, + ], + }, + testSuite: ({ service }) => + describe("Medusa Cloud Email provider - when another email provider is configured", () => { + let fetchMock: jest.SpyInstance + + beforeEach(() => { + fetchMock = jest + .spyOn(globalThis, "fetch") + .mockImplementation( + async () => successMedusaCloudEmailResponse as any + ) + }) + + afterEach(() => { + fetchMock.mockClear() + }) + + it("should not enable Medusa Cloud Email provider", async () => { + const result = await service.createNotifications(testNotification) + expect(result).toMatchObject({ status: NotificationStatus.SUCCESS }) + + expect(fetchMock).not.toHaveBeenCalled() + }) + }), +}) + +moduleIntegrationTestRunner({ + moduleName: Modules.NOTIFICATION, + moduleOptions: {}, + testSuite: ({ service }) => + describe("Medusa Cloud Email provider - when cloud options are not provided", () => { + let fetchMock: jest.SpyInstance + + beforeEach(() => { + fetchMock = jest + .spyOn(globalThis, "fetch") + .mockImplementation( + async () => successMedusaCloudEmailResponse as any + ) + }) + + afterEach(() => { + fetchMock.mockClear() + }) + + it("should not enable Medusa Cloud Email provider", async () => { + await expect( + service.createNotifications(testNotification) + ).rejects.toThrow() + + expect(fetchMock).not.toHaveBeenCalled() + }) + }), +}) diff --git a/packages/modules/notification/src/loaders/providers.ts b/packages/modules/notification/src/loaders/providers.ts index 5edb614621..70252cc4cd 100644 --- a/packages/modules/notification/src/loaders/providers.ts +++ b/packages/modules/notification/src/loaders/providers.ts @@ -1,9 +1,6 @@ +import { Lifetime, asFunction, asValue } from "@medusajs/framework/awilix" import { moduleProviderLoader } from "@medusajs/framework/modules-sdk" -import { - LoaderOptions, - ModuleProvider, - ModulesSdkTypes, -} from "@medusajs/framework/types" +import { LoaderOptions, ModulesSdkTypes } from "@medusajs/framework/types" import { ContainerRegistrationKeys, lowerCaseFirst, @@ -13,9 +10,10 @@ import { NotificationProvider } from "@models" import { NotificationProviderService } from "@services" import { NotificationIdentifiersRegistrationName, + NotificationModuleOptions, NotificationProviderRegistrationPrefix, } from "@types" -import { Lifetime, asFunction, asValue } from "@medusajs/framework/awilix" +import { MedusaCloudEmailNotificationProvider } from "../providers/medusa-cloud-email" const registrationFn = async (klass, container, pluginOptions) => { container.register({ @@ -40,8 +38,34 @@ export default async ({ ( | ModulesSdkTypes.ModuleServiceInitializeOptions | ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions - ) & { providers: ModuleProvider[] } + ) & + NotificationModuleOptions >): Promise => { + let providers = options?.providers || [] + + // We add the Medusa Cloud Email provider if there is no other email provider configured + const hasEmailProvider = options?.providers?.some((provider) => + provider.options?.channels?.some((channel) => channel === "email") + ) + if (!hasEmailProvider) { + const { api_key, endpoint, environment_handle } = options?.cloud ?? {} + if (api_key && endpoint && environment_handle) { + await registrationFn(MedusaCloudEmailNotificationProvider, container, { + options: options?.cloud, + id: "cloud", + }) + const provider = { + id: "cloud", + resolve: "", + options: { + ...options?.cloud, + channels: ["email"], + }, + } + providers = [...providers, provider] + } + } + await moduleProviderLoader({ container, providers: options?.providers || [], @@ -50,7 +74,7 @@ export default async ({ await syncDatabaseProviders({ container, - providers: options?.providers || [], + providers: providers, }) } @@ -59,7 +83,7 @@ async function syncDatabaseProviders({ providers, }: { container: any - providers: ModuleProvider[] + providers: Exclude }) { const providerServiceRegistrationKey = lowerCaseFirst( NotificationProviderService.name @@ -76,13 +100,12 @@ async function syncDatabaseProviders({ ) } - const config = provider.options as { channels: string[] } return { id: provider.id, handle: provider.id, name: provider.id, is_enabled: true, - channels: config?.channels ?? [], + channels: provider.options?.channels ?? [], } }) diff --git a/packages/modules/notification/src/providers/medusa-cloud-email.ts b/packages/modules/notification/src/providers/medusa-cloud-email.ts new file mode 100644 index 0000000000..39cebcc776 --- /dev/null +++ b/packages/modules/notification/src/providers/medusa-cloud-email.ts @@ -0,0 +1,49 @@ +import { Logger, NotificationTypes } from "@medusajs/framework/types" +import { AbstractNotificationProviderService } from "@medusajs/framework/utils" +import { MedusaCloudEmailOptions } from "@types" + +export class MedusaCloudEmailNotificationProvider extends AbstractNotificationProviderService { + static identifier = "notification-medusa-cloud-email" + protected options_: MedusaCloudEmailOptions + protected logger_: Logger + + constructor({}, options: MedusaCloudEmailOptions) { + super() + + this.options_ = options + } + + async send( + notification: NotificationTypes.ProviderSendNotificationDTO + ): Promise { + try { + const response = await fetch(`${this.options_.endpoint}/send`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Basic ${this.options_.api_key}`, + "x-medusa-environment-handle": this.options_.environment_handle, + }, + body: JSON.stringify({ + to: notification.to, + from: notification.from, + attachments: notification.attachments, + template: notification.template, + data: notification.data, + content: notification.content, + }), + }) + const responseBody = await response.json() + + if (!response.ok) { + throw new Error( + `Failed to send email: ${response.status} - ${response.statusText}: ${responseBody.message}` + ) + } + + return { id: responseBody.id } + } catch (error) { + throw new Error(`Failed to send email: ${error.message}`) + } + } +} diff --git a/packages/modules/notification/src/types/index.ts b/packages/modules/notification/src/types/index.ts index f312c54952..649e172894 100644 --- a/packages/modules/notification/src/types/index.ts +++ b/packages/modules/notification/src/types/index.ts @@ -28,8 +28,19 @@ export type NotificationModuleOptions = */ id: string /** - * key value pair of the configuration to be passed to the provider constructor + * key value pair of the configuration to be passed to the provider constructor, plus the channels supported by the provider */ - options?: Record + options?: Record & { channels: string[] } }[] + /** + * Options for the default Medusa Cloud Email provider + * @private + */ + cloud?: MedusaCloudEmailOptions } + +export type MedusaCloudEmailOptions = { + api_key: string + endpoint: string + environment_handle: string +} diff --git a/packages/modules/payment/src/loaders/providers.ts b/packages/modules/payment/src/loaders/providers.ts index e5304f01f6..035a96a825 100644 --- a/packages/modules/payment/src/loaders/providers.ts +++ b/packages/modules/payment/src/loaders/providers.ts @@ -1,3 +1,4 @@ +import { asFunction, asValue, Lifetime } from "@medusajs/framework/awilix" import { moduleProviderLoader } from "@medusajs/framework/modules-sdk" import { CreatePaymentProviderDTO, @@ -5,7 +6,6 @@ import { ModuleProvider, ModulesSdkTypes, } from "@medusajs/framework/types" -import { asFunction, asValue, Lifetime } from "@medusajs/framework/awilix" import { MedusaError } from "@medusajs/framework/utils" import { PaymentProviderService } from "@services"