chore(): Reorganize modules (#7210)
**What** Move all modules to the modules directory
This commit is contained in:
committed by
GitHub
parent
7a351eef09
commit
4eae25e1ef
11
packages/modules/payment/src/index.ts
Normal file
11
packages/modules/payment/src/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import {
|
||||
moduleDefinition,
|
||||
revertMigration,
|
||||
runMigrations,
|
||||
} from "./module-definition"
|
||||
|
||||
export default moduleDefinition
|
||||
export { revertMigration, runMigrations }
|
||||
|
||||
export * from "./initialize"
|
||||
export * from "./types"
|
||||
38
packages/modules/payment/src/initialize/index.ts
Normal file
38
packages/modules/payment/src/initialize/index.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
ExternalModuleDeclaration,
|
||||
InternalModuleDeclaration,
|
||||
MedusaModule,
|
||||
MODULE_PACKAGE_NAMES,
|
||||
Modules,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import {
|
||||
IPaymentModuleService,
|
||||
ModuleProvider,
|
||||
ModulesSdkTypes,
|
||||
} from "@medusajs/types"
|
||||
|
||||
import { moduleDefinition } from "../module-definition"
|
||||
import { InitializeModuleInjectableDependencies } from "../types"
|
||||
|
||||
export const initialize = async (
|
||||
options?:
|
||||
| (
|
||||
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
||||
| ExternalModuleDeclaration
|
||||
| InternalModuleDeclaration
|
||||
) & { providers: ModuleProvider[] },
|
||||
injectedDependencies?: InitializeModuleInjectableDependencies
|
||||
): Promise<IPaymentModuleService> => {
|
||||
const loaded = await MedusaModule.bootstrap<IPaymentModuleService>({
|
||||
moduleKey: Modules.PAYMENT,
|
||||
defaultPath: MODULE_PACKAGE_NAMES[Modules.PAYMENT],
|
||||
declaration: options as
|
||||
| InternalModuleDeclaration
|
||||
| ExternalModuleDeclaration,
|
||||
injectedDependencies,
|
||||
moduleExports: moduleDefinition,
|
||||
})
|
||||
|
||||
return loaded[Modules.PAYMENT]
|
||||
}
|
||||
61
packages/modules/payment/src/joiner-config.ts
Normal file
61
packages/modules/payment/src/joiner-config.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { MapToConfig } from "@medusajs/utils"
|
||||
import {
|
||||
Payment,
|
||||
PaymentCollection,
|
||||
PaymentProvider,
|
||||
PaymentSession,
|
||||
} from "@models"
|
||||
|
||||
export const LinkableKeys = {
|
||||
payment_id: Payment.name,
|
||||
payment_collection_id: PaymentCollection.name,
|
||||
payment_provider_id: PaymentProvider.name,
|
||||
}
|
||||
|
||||
const entityLinkableKeysMap: MapToConfig = {}
|
||||
Object.entries(LinkableKeys).forEach(([key, value]) => {
|
||||
entityLinkableKeysMap[value] ??= []
|
||||
entityLinkableKeysMap[value].push({
|
||||
mapTo: key,
|
||||
valueFrom: key.split("_").pop()!,
|
||||
})
|
||||
})
|
||||
|
||||
export const entityNameToLinkableKeysMap: MapToConfig = entityLinkableKeysMap
|
||||
|
||||
export const joinerConfig: ModuleJoinerConfig = {
|
||||
serviceName: Modules.PAYMENT,
|
||||
primaryKeys: ["id"],
|
||||
linkableKeys: LinkableKeys,
|
||||
alias: [
|
||||
{
|
||||
name: ["payment", "payments"],
|
||||
args: {
|
||||
entity: Payment.name,
|
||||
methodSuffix: "Payments",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["payment_collection", "payment_collections"],
|
||||
args: {
|
||||
entity: PaymentCollection.name,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["payment_session", "payment_sessions"],
|
||||
args: {
|
||||
entity: PaymentSession.name,
|
||||
methodSuffix: "PaymentSessions",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["payment_provider", "payment_providers"],
|
||||
args: {
|
||||
entity: PaymentProvider.name,
|
||||
methodSuffix: "PaymentProviders",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
34
packages/modules/payment/src/loaders/connection.ts
Normal file
34
packages/modules/payment/src/loaders/connection.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
InternalModuleDeclaration,
|
||||
LoaderOptions,
|
||||
Modules,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import { ModulesSdkTypes } from "@medusajs/types"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { EntitySchema } from "@mikro-orm/core"
|
||||
import * as PaymentModels from "../models"
|
||||
|
||||
export default async (
|
||||
{
|
||||
options,
|
||||
container,
|
||||
logger,
|
||||
}: LoaderOptions<
|
||||
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
||||
>,
|
||||
moduleDeclaration?: InternalModuleDeclaration
|
||||
): Promise<void> => {
|
||||
const entities = Object.values(PaymentModels) as unknown as EntitySchema[]
|
||||
const pathToMigrations = __dirname + "/../migrations"
|
||||
|
||||
await ModulesSdkUtils.mikroOrmConnectionLoader({
|
||||
moduleName: Modules.PAYMENT,
|
||||
entities,
|
||||
container,
|
||||
options,
|
||||
moduleDeclaration,
|
||||
logger,
|
||||
pathToMigrations,
|
||||
})
|
||||
}
|
||||
10
packages/modules/payment/src/loaders/container.ts
Normal file
10
packages/modules/payment/src/loaders/container.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import * as ModuleModels from "@models"
|
||||
import * as ModuleRepositories from "@repositories"
|
||||
import * as ModuleServices from "@services"
|
||||
|
||||
export default ModulesSdkUtils.moduleContainerLoaderFactory({
|
||||
moduleModels: ModuleModels,
|
||||
moduleRepositories: ModuleRepositories,
|
||||
moduleServices: ModuleServices,
|
||||
})
|
||||
26
packages/modules/payment/src/loaders/defaults.ts
Normal file
26
packages/modules/payment/src/loaders/defaults.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
CreatePaymentProviderDTO,
|
||||
LoaderOptions
|
||||
} from "@medusajs/types"
|
||||
|
||||
export default async ({ container }: LoaderOptions): Promise<void> => {
|
||||
const providersToLoad = container.resolve("payment_providers")
|
||||
const paymentProviderService = container.resolve("paymentProviderService")
|
||||
|
||||
const providers = await paymentProviderService.list({
|
||||
id: providersToLoad,
|
||||
})
|
||||
|
||||
const loadedProvidersMap = new Map(providers.map((p) => [p.id, p]))
|
||||
|
||||
const providersToCreate: CreatePaymentProviderDTO[] = []
|
||||
for (const id of providersToLoad) {
|
||||
if (loadedProvidersMap.has(id)) {
|
||||
continue
|
||||
}
|
||||
|
||||
providersToCreate.push({ id })
|
||||
}
|
||||
|
||||
await paymentProviderService.create(providersToCreate)
|
||||
}
|
||||
4
packages/modules/payment/src/loaders/index.ts
Normal file
4
packages/modules/payment/src/loaders/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./connection"
|
||||
export * from "./container"
|
||||
export * from "./providers"
|
||||
export * from "./defaults"
|
||||
40
packages/modules/payment/src/loaders/providers.ts
Normal file
40
packages/modules/payment/src/loaders/providers.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { moduleProviderLoader } from "@medusajs/modules-sdk"
|
||||
import { LoaderOptions, ModuleProvider, ModulesSdkTypes } from "@medusajs/types"
|
||||
import { Lifetime, asFunction, asValue } from "awilix"
|
||||
|
||||
import * as providers from "../providers"
|
||||
|
||||
const registrationFn = async (klass, container, pluginOptions) => {
|
||||
Object.entries(pluginOptions.config || []).map(([name, config]) => {
|
||||
const key = `pp_${klass.PROVIDER}_${name}`
|
||||
|
||||
container.register({
|
||||
[key]: asFunction((cradle) => new klass(cradle, config), {
|
||||
lifetime: klass.LIFE_TIME || Lifetime.SINGLETON,
|
||||
}),
|
||||
})
|
||||
|
||||
container.registerAdd("payment_providers", asValue(key))
|
||||
})
|
||||
}
|
||||
|
||||
export default async ({
|
||||
container,
|
||||
options,
|
||||
}: LoaderOptions<
|
||||
(
|
||||
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
||||
) & { providers: ModuleProvider[] }
|
||||
>): Promise<void> => {
|
||||
// Local providers
|
||||
for (const provider of Object.values(providers)) {
|
||||
await registrationFn(provider, container, { config: { default: {} } })
|
||||
}
|
||||
|
||||
await moduleProviderLoader({
|
||||
container,
|
||||
providers: options?.providers || [],
|
||||
registerServiceFn: registrationFn,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
import { generatePostgresAlterColummnIfExistStatement } from "@medusajs/utils"
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class Migration20240225134525 extends Migration {
|
||||
async up(): Promise<void> {
|
||||
const paymentCollectionExists = await this.execute(
|
||||
`SELECT * FROM information_schema.tables where table_name = 'payment_collection' and table_schema = 'public';`
|
||||
)
|
||||
|
||||
if (paymentCollectionExists.length) {
|
||||
this.addSql(`
|
||||
${generatePostgresAlterColummnIfExistStatement(
|
||||
"payment_collection",
|
||||
["type", "created_by"],
|
||||
"DROP NOT NULL"
|
||||
)}
|
||||
|
||||
ALTER TABLE IF EXISTS "payment_collection" ADD COLUMN IF NOT EXISTS "completed_at" TIMESTAMPTZ NULL;
|
||||
ALTER TABLE IF EXISTS "payment_collection" ADD COLUMN IF NOT EXISTS "raw_amount" JSONB NOT NULL;
|
||||
ALTER TABLE IF EXISTS "payment_collection" ADD COLUMN IF NOT EXISTS "deleted_at" TIMESTAMPTZ NULL;
|
||||
ALTER TABLE "payment_collection" DROP CONSTRAINT "FK_payment_collection_region_id";
|
||||
|
||||
ALTER TABLE IF EXISTS "payment_provider" ADD COLUMN IF NOT EXISTS "is_enabled" BOOLEAN NOT NULL DEFAULT TRUE;
|
||||
|
||||
ALTER TABLE IF EXISTS "payment_session" ADD COLUMN IF NOT EXISTS "payment_collection_id" TEXT NOT NULL;
|
||||
ALTER TABLE IF EXISTS "payment_session" ADD COLUMN IF NOT EXISTS "currency_code" TEXT NOT NULL;
|
||||
ALTER TABLE IF EXISTS "payment_session" ADD COLUMN IF NOT EXISTS "authorized_at" TEXT NULL;
|
||||
ALTER TABLE IF EXISTS "payment_session" ADD COLUMN IF NOT EXISTS "payment_authorized_at" TIMESTAMPTZ NULL;
|
||||
ALTER TABLE IF EXISTS "payment_session" ADD COLUMN IF NOT EXISTS "raw_amount" JSONB NOT NULL;
|
||||
ALTER TABLE IF EXISTS "payment_session" ADD COLUMN IF NOT EXISTS "deleted_at" TIMESTAMPTZ NULL;
|
||||
ALTER TABLE IF EXISTS "payment_session" ADD COLUMN IF NOT EXISTS "context" JSONB NULL;
|
||||
|
||||
ALTER TABLE IF EXISTS "payment" ADD COLUMN IF NOT EXISTS "deleted_at" TIMESTAMPTZ NULL;
|
||||
ALTER TABLE IF EXISTS "payment" ADD COLUMN IF NOT EXISTS "payment_collection_id" TEXT NOT NULL;
|
||||
ALTER TABLE IF EXISTS "payment" ADD COLUMN IF NOT EXISTS "provider_id" TEXT NOT NULL;
|
||||
ALTER TABLE IF EXISTS "payment" ADD COLUMN IF NOT EXISTS "raw_amount" JSONB NOT NULL;
|
||||
ALTER TABLE IF EXISTS "payment" ADD COLUMN IF NOT EXISTS "deleted_at" TIMESTAMPTZ NULL;
|
||||
ALTER TABLE IF EXISTS "payment" ADD COLUMN IF NOT EXISTS "payment_session_id" TEXT NOT NULL;
|
||||
ALTER TABLE IF EXISTS "payment" ADD COLUMN IF NOT EXISTS "customer_id" TEXT NULL;
|
||||
|
||||
ALTER TABLE IF EXISTS "refund" ADD COLUMN IF NOT EXISTS "raw_amount" JSONB NOT NULL;
|
||||
ALTER TABLE IF EXISTS "refund" ADD COLUMN IF NOT EXISTS "deleted_at" TIMESTAMPTZ NULL;
|
||||
ALTER TABLE IF EXISTS "refund" ADD COLUMN IF NOT EXISTS "created_by" TEXT NULL;
|
||||
${generatePostgresAlterColummnIfExistStatement(
|
||||
"refund",
|
||||
["reason"],
|
||||
"DROP NOT NULL"
|
||||
)}
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "capture" (
|
||||
"id" TEXT NOT NULL,
|
||||
"amount" NUMERIC NOT NULL,
|
||||
"raw_amount" JSONB NOT NULL,
|
||||
"payment_id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"deleted_at" TIMESTAMPTZ NULL,
|
||||
"created_by" TEXT NULL,
|
||||
"metadata" JSONB NULL,
|
||||
CONSTRAINT "capture_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payment_method_token" (
|
||||
"id" TEXT NOT NULL,
|
||||
"provider_id" TEXT NOT NULL,
|
||||
"data" JSONB NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"type_detail" TEXT NULL,
|
||||
"description_detail" TEXT NULL,
|
||||
"metadata" JSONB NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"deleted_at" TIMESTAMPTZ NULL,
|
||||
CONSTRAINT "payment_method_token_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payment_collection_payment_providers" (
|
||||
"payment_collection_id" TEXT NOT NULL,
|
||||
"payment_provider_id" TEXT NOT NULL,
|
||||
CONSTRAINT "payment_collection_payment_providers_pkey" PRIMARY KEY ("payment_collection_id", "payment_provider_id")
|
||||
);
|
||||
|
||||
ALTER TABLE IF EXISTS "payment_collection_payment_providers"
|
||||
ADD CONSTRAINT "payment_collection_payment_providers_payment_coll_aa276_foreign" FOREIGN KEY ("payment_collection_id") REFERENCES "payment_collection" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE IF EXISTS "payment_collection_payment_providers"
|
||||
ADD CONSTRAINT "payment_collection_payment_providers_payment_provider_id_foreign" FOREIGN KEY ("payment_provider_id") REFERENCES "payment_provider" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE IF EXISTS "capture"
|
||||
ADD CONSTRAINT "capture_payment_id_foreign" FOREIGN KEY ("payment_id") REFERENCES "payment" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE IF EXISTS "refund"
|
||||
ADD CONSTRAINT "refund_payment_id_foreign" FOREIGN KEY ("payment_id") REFERENCES "payment" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_deleted_at" ON "payment" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_payment_collection_id" ON "payment" ("payment_collection_id") WHERE "deleted_at" IS NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_method_token_deleted_at" ON "payment_method_token" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_provider_id" ON "payment" ("provider_id") WHERE "deleted_at" IS NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_collection_region_id" ON "payment_collection" ("region_id") WHERE "deleted_at" IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_collection_deleted_at" ON "payment_collection" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_refund_payment_id" ON "refund" ("payment_id") WHERE "deleted_at" IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS "IDX_refund_deleted_at" ON "payment" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_capture_payment_id" ON "capture" ("payment_id") WHERE "deleted_at" IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS "IDX_capture_deleted_at" ON "payment" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_session_payment_collection_id" ON "payment_session" ("payment_collection_id") WHERE "deleted_at" IS NULL;
|
||||
|
||||
`)
|
||||
} else {
|
||||
this.addSql(`
|
||||
CREATE TABLE IF NOT EXISTS "payment_collection" (
|
||||
"id" TEXT NOT NULL,
|
||||
"currency_code" TEXT NOT NULL,
|
||||
"amount" NUMERIC NOT NULL,
|
||||
"raw_amount" JSONB NOT NULL,
|
||||
"region_id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"deleted_at" TIMESTAMPTZ NULL,
|
||||
"completed_at" TIMESTAMPTZ NULL,
|
||||
"status" TEXT CHECK ("status" IN ('not_paid', 'awaiting', 'authorized', 'partially_authorized', 'canceled')) NOT NULL DEFAULT 'not_paid',
|
||||
"metadata" JSONB NULL,
|
||||
CONSTRAINT "payment_collection_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payment_method_token" (
|
||||
"id" TEXT NOT NULL,
|
||||
"provider_id" TEXT NOT NULL,
|
||||
"data" JSONB NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"type_detail" TEXT NULL,
|
||||
"description_detail" TEXT NULL,
|
||||
"metadata" JSONB NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"deleted_at" TIMESTAMPTZ NULL,
|
||||
CONSTRAINT "payment_method_token_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payment_provider" (
|
||||
"id" TEXT NOT NULL,
|
||||
"is_enabled" BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
CONSTRAINT "payment_provider_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payment_collection_payment_providers" (
|
||||
"payment_collection_id" TEXT NOT NULL,
|
||||
"payment_provider_id" TEXT NOT NULL,
|
||||
CONSTRAINT "payment_collection_payment_providers_pkey" PRIMARY KEY ("payment_collection_id", "payment_provider_id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payment_session" (
|
||||
"id" TEXT NOT NULL,
|
||||
"currency_code" TEXT NOT NULL,
|
||||
"amount" NUMERIC NOT NULL,
|
||||
"raw_amount" JSONB NOT NULL,
|
||||
"provider_id" TEXT NOT NULL,
|
||||
"data" JSONB NOT NULL,
|
||||
"context" JSONB NULL,
|
||||
"status" TEXT CHECK ("status" IN ('authorized', 'pending', 'requires_more', 'error', 'canceled')) NOT NULL DEFAULT 'pending',
|
||||
"authorized_at" TIMESTAMPTZ NULL,
|
||||
"payment_collection_id" TEXT NOT NULL,
|
||||
"metadata" JSONB NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"deleted_at" TIMESTAMPTZ NULL,
|
||||
CONSTRAINT "payment_session_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payment" (
|
||||
"id" TEXT NOT NULL,
|
||||
"amount" NUMERIC NOT NULL,
|
||||
"raw_amount" JSONB NOT NULL,
|
||||
"currency_code" TEXT NOT NULL,
|
||||
"provider_id" TEXT NOT NULL,
|
||||
"cart_id" TEXT NULL,
|
||||
"order_id" TEXT NULL,
|
||||
"customer_id" TEXT NULL,
|
||||
"data" JSONB NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"deleted_at" TIMESTAMPTZ NULL,
|
||||
"captured_at" TIMESTAMPTZ NULL,
|
||||
"canceled_at" TIMESTAMPTZ NULL,
|
||||
"payment_collection_id" TEXT NOT NULL,
|
||||
"payment_session_id" TEXT NOT NULL,
|
||||
"metadata" JSONB NULL,
|
||||
CONSTRAINT "payment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "refund" (
|
||||
"id" TEXT NOT NULL,
|
||||
"amount" NUMERIC NOT NULL,
|
||||
"raw_amount" JSONB NOT NULL,
|
||||
"payment_id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"deleted_at" TIMESTAMPTZ NULL,
|
||||
"created_by" TEXT NULL,
|
||||
"metadata" JSONB NULL,
|
||||
CONSTRAINT "refund_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "capture" (
|
||||
"id" TEXT NOT NULL,
|
||||
"amount" NUMERIC NOT NULL,
|
||||
"raw_amount" JSONB NOT NULL,
|
||||
"payment_id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"deleted_at" TIMESTAMPTZ NULL,
|
||||
"created_by" TEXT NULL,
|
||||
"metadata" JSONB NULL,
|
||||
CONSTRAINT "capture_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_deleted_at" ON "payment" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_payment_collection_id" ON "payment" ("payment_collection_id") WHERE "deleted_at" IS NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_method_token_deleted_at" ON "payment_method_token" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_provider_id" ON "payment" ("provider_id") WHERE "deleted_at" IS NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_collection_region_id" ON "payment_collection" ("region_id") WHERE "deleted_at" IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_collection_deleted_at" ON "payment_collection" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_refund_payment_id" ON "refund" ("payment_id") WHERE "deleted_at" IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS "IDX_refund_deleted_at" ON "payment" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_capture_payment_id" ON "capture" ("payment_id") WHERE "deleted_at" IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS "IDX_capture_deleted_at" ON "payment" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_session_payment_collection_id" ON "payment_session" ("payment_collection_id") WHERE "deleted_at" IS NULL;
|
||||
|
||||
ALTER TABLE IF EXISTS "payment_collection_payment_providers"
|
||||
ADD CONSTRAINT "payment_collection_payment_providers_payment_coll_aa276_foreign" FOREIGN KEY ("payment_collection_id") REFERENCES "payment_collection" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE IF EXISTS "payment_collection_payment_providers"
|
||||
ADD CONSTRAINT "payment_collection_payment_providers_payment_provider_id_foreign" FOREIGN KEY ("payment_provider_id") REFERENCES "payment_provider" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE IF EXISTS "payment_session"
|
||||
ADD CONSTRAINT "payment_session_payment_collection_id_foreign" FOREIGN KEY ("payment_collection_id") REFERENCES "payment_collection" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE IF EXISTS "payment"
|
||||
ADD CONSTRAINT "payment_payment_collection_id_foreign" FOREIGN KEY ("payment_collection_id") REFERENCES "payment_collection" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE IF EXISTS "capture"
|
||||
ADD CONSTRAINT "capture_payment_id_foreign" FOREIGN KEY ("payment_id") REFERENCES "payment" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE IF EXISTS "refund"
|
||||
ADD CONSTRAINT "refund_payment_id_foreign" FOREIGN KEY ("payment_id") REFERENCES "payment" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
`)
|
||||
}
|
||||
}
|
||||
}
|
||||
74
packages/modules/payment/src/models/capture.ts
Normal file
74
packages/modules/payment/src/models/capture.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { BigNumberRawValue } from "@medusajs/types"
|
||||
import {
|
||||
BigNumber,
|
||||
MikroOrmBigNumberProperty,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import Payment from "./payment"
|
||||
|
||||
type OptionalCaptureProps = "created_at"
|
||||
|
||||
@Entity({ tableName: "capture" })
|
||||
export default class Capture {
|
||||
[OptionalProps]?: OptionalCaptureProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
amount: BigNumber | number
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_amount: BigNumberRawValue
|
||||
|
||||
@ManyToOne(() => Payment, {
|
||||
onDelete: "cascade",
|
||||
index: "IDX_capture_payment_id",
|
||||
fieldName: "payment_id",
|
||||
})
|
||||
payment!: Payment
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
index: "IDX_capture_deleted_at",
|
||||
})
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
created_by: string | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "capt")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "capt")
|
||||
}
|
||||
}
|
||||
7
packages/modules/payment/src/models/index.ts
Normal file
7
packages/modules/payment/src/models/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export { default as Capture } from "./capture"
|
||||
export { default as Payment } from "./payment"
|
||||
export { default as PaymentCollection } from "./payment-collection"
|
||||
export { default as PaymentMethodToken } from "./payment-method-token"
|
||||
export { default as PaymentProvider } from "./payment-provider"
|
||||
export { default as PaymentSession } from "./payment-session"
|
||||
export { default as Refund } from "./refund"
|
||||
108
packages/modules/payment/src/models/payment-collection.ts
Normal file
108
packages/modules/payment/src/models/payment-collection.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { BigNumberRawValue, DAL } from "@medusajs/types"
|
||||
import {
|
||||
BigNumber,
|
||||
DALUtils,
|
||||
MikroOrmBigNumberProperty,
|
||||
PaymentCollectionStatus,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Cascade,
|
||||
Collection,
|
||||
Entity,
|
||||
Enum,
|
||||
Filter,
|
||||
ManyToMany,
|
||||
OnInit,
|
||||
OneToMany,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import Payment from "./payment"
|
||||
import PaymentProvider from "./payment-provider"
|
||||
import PaymentSession from "./payment-session"
|
||||
|
||||
type OptionalPaymentCollectionProps = "status" | DAL.EntityDateColumns
|
||||
|
||||
@Entity({ tableName: "payment_collection" })
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class PaymentCollection {
|
||||
[OptionalProps]?: OptionalPaymentCollectionProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
currency_code: string
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
amount: BigNumber | number
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_amount: BigNumberRawValue
|
||||
|
||||
@Property({ columnType: "text", index: "IDX_payment_collection_region_id" })
|
||||
region_id: string
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
index: "IDX_payment_collection_deleted_at",
|
||||
})
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
})
|
||||
completed_at: Date | null = null
|
||||
|
||||
@Enum({
|
||||
items: () => PaymentCollectionStatus,
|
||||
default: PaymentCollectionStatus.NOT_PAID,
|
||||
})
|
||||
status: PaymentCollectionStatus = PaymentCollectionStatus.NOT_PAID
|
||||
|
||||
@ManyToMany(() => PaymentProvider)
|
||||
payment_providers = new Collection<PaymentProvider>(this)
|
||||
|
||||
@OneToMany(() => PaymentSession, (ps) => ps.payment_collection, {
|
||||
cascade: [Cascade.PERSIST, "soft-remove"] as any,
|
||||
})
|
||||
payment_sessions = new Collection<PaymentSession>(this)
|
||||
|
||||
@OneToMany(() => Payment, (payment) => payment.payment_collection, {
|
||||
cascade: [Cascade.PERSIST, "soft-remove"] as any,
|
||||
})
|
||||
payments = new Collection<Payment>(this)
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "pay_col")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "pay_col")
|
||||
}
|
||||
}
|
||||
64
packages/modules/payment/src/models/payment-method-token.ts
Normal file
64
packages/modules/payment/src/models/payment-method-token.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { generateEntityId } from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
OnInit,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
|
||||
@Entity({ tableName: "payment_method_token" })
|
||||
export default class PaymentMethodToken {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
provider_id: string
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
data: Record<string, unknown> | null = null
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
name: string
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
type_detail: string | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
description_detail: string | null = null
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
index: "IDX_payment_metod_token_deleted_at",
|
||||
})
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "paymttok")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "paymttok")
|
||||
}
|
||||
}
|
||||
15
packages/modules/payment/src/models/payment-provider.ts
Normal file
15
packages/modules/payment/src/models/payment-provider.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Entity, OptionalProps, PrimaryKey, Property } from "@mikro-orm/core"
|
||||
|
||||
@Entity({ tableName: "payment_provider" })
|
||||
export default class PaymentProvider {
|
||||
[OptionalProps]?: "is_enabled"
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({
|
||||
default: true,
|
||||
columnType: "boolean",
|
||||
})
|
||||
is_enabled: boolean = true
|
||||
}
|
||||
112
packages/modules/payment/src/models/payment-session.ts
Normal file
112
packages/modules/payment/src/models/payment-session.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { BigNumberRawValue } from "@medusajs/types"
|
||||
import {
|
||||
BigNumber,
|
||||
generateEntityId,
|
||||
MikroOrmBigNumberProperty,
|
||||
PaymentSessionStatus,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
Enum,
|
||||
ManyToOne,
|
||||
OneToOne,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import Payment from "./payment"
|
||||
import PaymentCollection from "./payment-collection"
|
||||
|
||||
@Entity({ tableName: "payment_session" })
|
||||
export default class PaymentSession {
|
||||
[OptionalProps]?: "status" | "data"
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
currency_code: string
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
amount: BigNumber | number
|
||||
|
||||
@Property({
|
||||
columnType: "jsonb",
|
||||
})
|
||||
raw_amount: BigNumberRawValue
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
provider_id: string
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
data: Record<string, unknown> = {}
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
context: Record<string, unknown> | null
|
||||
|
||||
@Enum({
|
||||
items: () => PaymentSessionStatus,
|
||||
})
|
||||
status: PaymentSessionStatus = PaymentSessionStatus.PENDING
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
})
|
||||
authorized_at: Date | null = null
|
||||
|
||||
@ManyToOne(() => PaymentCollection, {
|
||||
persist: false,
|
||||
})
|
||||
payment_collection: PaymentCollection
|
||||
|
||||
@ManyToOne({
|
||||
entity: () => PaymentCollection,
|
||||
columnType: "text",
|
||||
index: "IDX_payment_session_payment_collection_id",
|
||||
fieldName: "payment_collection_id",
|
||||
mapToPk: true,
|
||||
})
|
||||
payment_collection_id: string
|
||||
|
||||
@OneToOne({
|
||||
entity: () => Payment,
|
||||
nullable: true,
|
||||
mappedBy: "payment_session",
|
||||
})
|
||||
payment?: Payment | null
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
index: "IDX_payment_session_deleted_at",
|
||||
})
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "payses")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "payses")
|
||||
}
|
||||
}
|
||||
150
packages/modules/payment/src/models/payment.ts
Normal file
150
packages/modules/payment/src/models/payment.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { BigNumberRawValue, DAL } from "@medusajs/types"
|
||||
import {
|
||||
BigNumber,
|
||||
DALUtils,
|
||||
MikroOrmBigNumberProperty,
|
||||
Searchable,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Cascade,
|
||||
Collection,
|
||||
Entity,
|
||||
Filter,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import Capture from "./capture"
|
||||
import PaymentCollection from "./payment-collection"
|
||||
import PaymentSession from "./payment-session"
|
||||
import Refund from "./refund"
|
||||
|
||||
type OptionalPaymentProps = DAL.EntityDateColumns
|
||||
|
||||
@Entity({ tableName: "payment" })
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class Payment {
|
||||
[OptionalProps]?: OptionalPaymentProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
amount: BigNumber | number
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_amount: BigNumberRawValue
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
currency_code: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
provider_id: string
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
cart_id: string | null = null
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
order_id: string | null = null
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
customer_id: string | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
data: Record<string, unknown> | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
index: "IDX_payment_deleted_at",
|
||||
})
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
})
|
||||
captured_at: Date | null = null
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
})
|
||||
canceled_at: Date | null = null
|
||||
|
||||
@OneToMany(() => Refund, (refund) => refund.payment, {
|
||||
cascade: [Cascade.REMOVE],
|
||||
})
|
||||
refunds = new Collection<Refund>(this)
|
||||
|
||||
@OneToMany(() => Capture, (capture) => capture.payment, {
|
||||
cascade: [Cascade.REMOVE],
|
||||
})
|
||||
captures = new Collection<Capture>(this)
|
||||
|
||||
@ManyToOne({
|
||||
entity: () => PaymentCollection,
|
||||
persist: false,
|
||||
})
|
||||
payment_collection: PaymentCollection
|
||||
|
||||
@ManyToOne({
|
||||
entity: () => PaymentCollection,
|
||||
columnType: "text",
|
||||
index: "IDX_payment_payment_collection_id",
|
||||
fieldName: "payment_collection_id",
|
||||
mapToPk: true,
|
||||
})
|
||||
payment_collection_id: string
|
||||
|
||||
@OneToOne({
|
||||
owner: true,
|
||||
fieldName: "payment_session_id",
|
||||
index: "IDX_payment_payment_session_id",
|
||||
})
|
||||
payment_session: PaymentSession
|
||||
|
||||
/** COMPUTED PROPERTIES START **/
|
||||
|
||||
captured_amount: number // sum of the associated captures
|
||||
refunded_amount: number // sum of the associated refunds
|
||||
|
||||
/** COMPUTED PROPERTIES END **/
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "pay")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "pay")
|
||||
}
|
||||
}
|
||||
71
packages/modules/payment/src/models/refund.ts
Normal file
71
packages/modules/payment/src/models/refund.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { BigNumberRawValue } from "@medusajs/types"
|
||||
import {
|
||||
BigNumber,
|
||||
MikroOrmBigNumberProperty,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import Payment from "./payment"
|
||||
|
||||
@Entity({ tableName: "refund" })
|
||||
export default class Refund {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
amount: BigNumber | number
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_amount: BigNumberRawValue
|
||||
|
||||
@ManyToOne(() => Payment, {
|
||||
onDelete: "cascade",
|
||||
index: "IDX_refund_payment_id",
|
||||
fieldName: "payment_id",
|
||||
})
|
||||
payment!: Payment
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
index: "IDX_refund_deleted_at",
|
||||
})
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
created_by: string | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "ref")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "ref")
|
||||
}
|
||||
}
|
||||
41
packages/modules/payment/src/module-definition.ts
Normal file
41
packages/modules/payment/src/module-definition.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ModuleExports } from "@medusajs/types"
|
||||
|
||||
import { PaymentModuleService } from "@services"
|
||||
|
||||
import loadConnection from "./loaders/connection"
|
||||
import loadContainer from "./loaders/container"
|
||||
import loadProviders from "./loaders/providers"
|
||||
import loadDefaults from "./loaders/defaults"
|
||||
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
|
||||
import * as PaymentModels from "@models"
|
||||
|
||||
const migrationScriptOptions = {
|
||||
moduleName: Modules.PAYMENT,
|
||||
models: PaymentModels,
|
||||
pathToMigrations: __dirname + "/migrations",
|
||||
}
|
||||
|
||||
export const runMigrations = ModulesSdkUtils.buildMigrationScript(
|
||||
migrationScriptOptions
|
||||
)
|
||||
export const revertMigration = ModulesSdkUtils.buildRevertMigrationScript(
|
||||
migrationScriptOptions
|
||||
)
|
||||
|
||||
const service = PaymentModuleService
|
||||
const loaders = [
|
||||
loadContainer,
|
||||
loadConnection,
|
||||
loadProviders,
|
||||
loadDefaults,
|
||||
] as any
|
||||
|
||||
export const moduleDefinition: ModuleExports = {
|
||||
service,
|
||||
loaders,
|
||||
runMigrations,
|
||||
revertMigration,
|
||||
}
|
||||
1
packages/modules/payment/src/providers/index.ts
Normal file
1
packages/modules/payment/src/providers/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as SystemPaymentProvider } from "./system"
|
||||
80
packages/modules/payment/src/providers/system.ts
Normal file
80
packages/modules/payment/src/providers/system.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
CreatePaymentProviderSession,
|
||||
PaymentProviderError,
|
||||
PaymentProviderSessionResponse,
|
||||
PaymentSessionStatus,
|
||||
ProviderWebhookPayload,
|
||||
WebhookActionResult,
|
||||
} from "@medusajs/types"
|
||||
import { AbstractPaymentProvider, PaymentActions } from "@medusajs/utils"
|
||||
|
||||
export class SystemProviderService extends AbstractPaymentProvider {
|
||||
static identifier = "system"
|
||||
static PROVIDER = "system"
|
||||
|
||||
async getStatus(_): Promise<string> {
|
||||
return "authorized"
|
||||
}
|
||||
|
||||
async getPaymentData(_): Promise<Record<string, unknown>> {
|
||||
return {}
|
||||
}
|
||||
|
||||
async initiatePayment(
|
||||
context: CreatePaymentProviderSession
|
||||
): Promise<PaymentProviderSessionResponse> {
|
||||
return { data: {} }
|
||||
}
|
||||
|
||||
async getPaymentStatus(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<PaymentSessionStatus> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
|
||||
async retrievePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProviderError> {
|
||||
return {}
|
||||
}
|
||||
|
||||
async authorizePayment(_): Promise<
|
||||
| PaymentProviderError
|
||||
| {
|
||||
status: PaymentSessionStatus
|
||||
data: PaymentProviderSessionResponse["data"]
|
||||
}
|
||||
> {
|
||||
return { data: {}, status: PaymentSessionStatus.AUTHORIZED }
|
||||
}
|
||||
|
||||
async updatePayment(
|
||||
_
|
||||
): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
|
||||
return { data: {} } as PaymentProviderSessionResponse
|
||||
}
|
||||
|
||||
async deletePayment(_): Promise<Record<string, unknown>> {
|
||||
return {}
|
||||
}
|
||||
|
||||
async capturePayment(_): Promise<Record<string, unknown>> {
|
||||
return {}
|
||||
}
|
||||
|
||||
async refundPayment(_): Promise<Record<string, unknown>> {
|
||||
return {}
|
||||
}
|
||||
|
||||
async cancelPayment(_): Promise<Record<string, unknown>> {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getWebhookActionAndData(
|
||||
data: ProviderWebhookPayload["payload"]
|
||||
): Promise<WebhookActionResult> {
|
||||
return { action: PaymentActions.NOT_SUPPORTED }
|
||||
}
|
||||
}
|
||||
|
||||
export default SystemProviderService
|
||||
1
packages/modules/payment/src/repositories/index.ts
Normal file
1
packages/modules/payment/src/repositories/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
|
||||
33
packages/modules/payment/src/scripts/bin/run-seed.ts
Normal file
33
packages/modules/payment/src/scripts/bin/run-seed.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { EOL } from "os"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
|
||||
import * as PaymentModels from "@models"
|
||||
|
||||
import { createPayments } from "../seed-utils"
|
||||
|
||||
const args = process.argv
|
||||
const path = args.pop() as string
|
||||
|
||||
export default (async () => {
|
||||
const { config } = await import("dotenv")
|
||||
config()
|
||||
if (!path) {
|
||||
throw new Error(
|
||||
`filePath is required.${EOL}Example: medusa-payment-seed <filePath>`
|
||||
)
|
||||
}
|
||||
|
||||
const run = ModulesSdkUtils.buildSeedScript({
|
||||
moduleName: Modules.PAYMENT,
|
||||
models: PaymentModels,
|
||||
pathToMigrations: __dirname + "/../../migrations",
|
||||
seedHandler: async ({ manager, data }) => {
|
||||
const { paymentsData } = data
|
||||
await createPayments(manager, paymentsData)
|
||||
},
|
||||
})
|
||||
await run({ path })
|
||||
})()
|
||||
8
packages/modules/payment/src/scripts/seed-utils.ts
Normal file
8
packages/modules/payment/src/scripts/seed-utils.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
|
||||
export async function createPayments(
|
||||
manager: SqlEntityManager,
|
||||
paymentsData: any[]
|
||||
): Promise<any[]> {
|
||||
return []
|
||||
}
|
||||
3
packages/modules/payment/src/services/index.ts
Normal file
3
packages/modules/payment/src/services/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as PaymentModuleService } from "./payment-module"
|
||||
export { default as PaymentProviderService } from "./payment-provider"
|
||||
|
||||
759
packages/modules/payment/src/services/payment-module.ts
Normal file
759
packages/modules/payment/src/services/payment-module.ts
Normal file
@@ -0,0 +1,759 @@
|
||||
import {
|
||||
CaptureDTO,
|
||||
Context,
|
||||
CreateCaptureDTO,
|
||||
CreatePaymentCollectionDTO,
|
||||
CreatePaymentSessionDTO,
|
||||
CreateRefundDTO,
|
||||
DAL,
|
||||
FilterablePaymentCollectionProps,
|
||||
FilterablePaymentProviderProps,
|
||||
FindConfig,
|
||||
InternalModuleDeclaration,
|
||||
IPaymentModuleService,
|
||||
ModuleJoinerConfig,
|
||||
ModulesSdkTypes,
|
||||
PaymentCollectionDTO,
|
||||
PaymentCollectionUpdatableFields,
|
||||
PaymentDTO,
|
||||
PaymentProviderDTO,
|
||||
PaymentSessionDTO,
|
||||
PaymentSessionStatus,
|
||||
ProviderWebhookPayload,
|
||||
RefundDTO,
|
||||
UpdatePaymentCollectionDTO,
|
||||
UpdatePaymentDTO,
|
||||
UpdatePaymentSessionDTO,
|
||||
UpsertPaymentCollectionDTO,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
BigNumber,
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
isString,
|
||||
MathBN,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
ModulesSdkUtils,
|
||||
PaymentActions,
|
||||
promiseAll,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
Capture,
|
||||
Payment,
|
||||
PaymentCollection,
|
||||
PaymentSession,
|
||||
Refund,
|
||||
} from "@models"
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||
import PaymentProviderService from "./payment-provider"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
paymentService: ModulesSdkTypes.InternalModuleService<any>
|
||||
captureService: ModulesSdkTypes.InternalModuleService<any>
|
||||
refundService: ModulesSdkTypes.InternalModuleService<any>
|
||||
paymentSessionService: ModulesSdkTypes.InternalModuleService<any>
|
||||
paymentCollectionService: ModulesSdkTypes.InternalModuleService<any>
|
||||
paymentProviderService: PaymentProviderService
|
||||
}
|
||||
|
||||
const generateMethodForModels = [
|
||||
PaymentCollection,
|
||||
Payment,
|
||||
PaymentSession,
|
||||
Capture,
|
||||
Refund,
|
||||
]
|
||||
|
||||
export default class PaymentModuleService<
|
||||
TPaymentCollection extends PaymentCollection = PaymentCollection,
|
||||
TPayment extends Payment = Payment,
|
||||
TCapture extends Capture = Capture,
|
||||
TRefund extends Refund = Refund,
|
||||
TPaymentSession extends PaymentSession = PaymentSession
|
||||
>
|
||||
extends ModulesSdkUtils.abstractModuleServiceFactory<
|
||||
InjectedDependencies,
|
||||
PaymentCollectionDTO,
|
||||
{
|
||||
PaymentCollection: { dto: PaymentCollectionDTO }
|
||||
PaymentSession: { dto: PaymentSessionDTO }
|
||||
Payment: { dto: PaymentDTO }
|
||||
Capture: { dto: CaptureDTO }
|
||||
Refund: { dto: RefundDTO }
|
||||
}
|
||||
>(PaymentCollection, generateMethodForModels, entityNameToLinkableKeysMap)
|
||||
implements IPaymentModuleService
|
||||
{
|
||||
protected baseRepository_: DAL.RepositoryService
|
||||
|
||||
protected paymentService_: ModulesSdkTypes.InternalModuleService<TPayment>
|
||||
protected captureService_: ModulesSdkTypes.InternalModuleService<TCapture>
|
||||
protected refundService_: ModulesSdkTypes.InternalModuleService<TRefund>
|
||||
protected paymentSessionService_: ModulesSdkTypes.InternalModuleService<TPaymentSession>
|
||||
protected paymentCollectionService_: ModulesSdkTypes.InternalModuleService<TPaymentCollection>
|
||||
protected paymentProviderService_: PaymentProviderService
|
||||
|
||||
constructor(
|
||||
{
|
||||
baseRepository,
|
||||
paymentService,
|
||||
captureService,
|
||||
refundService,
|
||||
paymentSessionService,
|
||||
paymentProviderService,
|
||||
paymentCollectionService,
|
||||
}: InjectedDependencies,
|
||||
protected readonly moduleDeclaration: InternalModuleDeclaration
|
||||
) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
|
||||
this.baseRepository_ = baseRepository
|
||||
|
||||
this.refundService_ = refundService
|
||||
this.captureService_ = captureService
|
||||
this.paymentService_ = paymentService
|
||||
this.paymentSessionService_ = paymentSessionService
|
||||
this.paymentProviderService_ = paymentProviderService
|
||||
this.paymentCollectionService_ = paymentCollectionService
|
||||
}
|
||||
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
return joinerConfig
|
||||
}
|
||||
|
||||
createPaymentCollections(
|
||||
data: CreatePaymentCollectionDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO>
|
||||
|
||||
createPaymentCollections(
|
||||
data: CreatePaymentCollectionDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO[]>
|
||||
@InjectManager("baseRepository_")
|
||||
async createPaymentCollections(
|
||||
data: CreatePaymentCollectionDTO | CreatePaymentCollectionDTO[],
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO | PaymentCollectionDTO[]> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
|
||||
const collections = await this.createPaymentCollections_(
|
||||
input,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize<PaymentCollectionDTO[]>(
|
||||
Array.isArray(data) ? collections : collections[0],
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async createPaymentCollections_(
|
||||
data: CreatePaymentCollectionDTO[],
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentCollection[]> {
|
||||
return this.paymentCollectionService_.create(data, sharedContext)
|
||||
}
|
||||
|
||||
updatePaymentCollections(
|
||||
paymentCollectionId: string,
|
||||
data: PaymentCollectionUpdatableFields,
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO>
|
||||
updatePaymentCollections(
|
||||
selector: FilterablePaymentCollectionProps,
|
||||
data: PaymentCollectionUpdatableFields,
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO[]>
|
||||
@InjectManager("baseRepository_")
|
||||
async updatePaymentCollections(
|
||||
idOrSelector: string | FilterablePaymentCollectionProps,
|
||||
data: PaymentCollectionUpdatableFields,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO | PaymentCollectionDTO[]> {
|
||||
let updateData: UpdatePaymentCollectionDTO[] = []
|
||||
|
||||
if (isString(idOrSelector)) {
|
||||
updateData = [
|
||||
{
|
||||
id: idOrSelector,
|
||||
...data,
|
||||
},
|
||||
]
|
||||
} else {
|
||||
const collections = await this.paymentCollectionService_.list(
|
||||
idOrSelector,
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
updateData = collections.map((c) => ({
|
||||
id: c.id,
|
||||
...data,
|
||||
}))
|
||||
}
|
||||
|
||||
const result = await this.updatePaymentCollections_(
|
||||
updateData,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize<PaymentCollectionDTO[]>(
|
||||
Array.isArray(data) ? result : result[0],
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async updatePaymentCollections_(
|
||||
data: UpdatePaymentCollectionDTO[],
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentCollection[]> {
|
||||
return await this.paymentCollectionService_.update(data, sharedContext)
|
||||
}
|
||||
|
||||
upsertPaymentCollections(
|
||||
data: UpsertPaymentCollectionDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO[]>
|
||||
upsertPaymentCollections(
|
||||
data: UpsertPaymentCollectionDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO>
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async upsertPaymentCollections(
|
||||
data: UpsertPaymentCollectionDTO | UpsertPaymentCollectionDTO[],
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO | PaymentCollectionDTO[]> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
const forUpdate = input.filter(
|
||||
(collection): collection is UpdatePaymentCollectionDTO => !!collection.id
|
||||
)
|
||||
const forCreate = input.filter(
|
||||
(collection): collection is CreatePaymentCollectionDTO => !collection.id
|
||||
)
|
||||
|
||||
const operations: Promise<PaymentCollection[]>[] = []
|
||||
|
||||
if (forCreate.length) {
|
||||
operations.push(this.createPaymentCollections_(forCreate, sharedContext))
|
||||
}
|
||||
if (forUpdate.length) {
|
||||
operations.push(this.updatePaymentCollections_(forUpdate, sharedContext))
|
||||
}
|
||||
|
||||
const result = (await promiseAll(operations)).flat()
|
||||
|
||||
return await this.baseRepository_.serialize<
|
||||
PaymentCollectionDTO[] | PaymentCollectionDTO
|
||||
>(Array.isArray(data) ? result : result[0])
|
||||
}
|
||||
|
||||
completePaymentCollections(
|
||||
paymentCollectionId: string,
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO>
|
||||
completePaymentCollections(
|
||||
paymentCollectionId: string[],
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO[]>
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async completePaymentCollections(
|
||||
paymentCollectionId: string | string[],
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO | PaymentCollectionDTO[]> {
|
||||
const input = Array.isArray(paymentCollectionId)
|
||||
? paymentCollectionId.map((id) => ({
|
||||
id,
|
||||
completed_at: new Date(),
|
||||
}))
|
||||
: [{ id: paymentCollectionId, completed_at: new Date() }]
|
||||
|
||||
// TODO: what checks should be done here? e.g. captured_amount === amount?
|
||||
|
||||
const updated = await this.paymentCollectionService_.update(
|
||||
input,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize(
|
||||
Array.isArray(paymentCollectionId) ? updated : updated[0],
|
||||
{ populate: true }
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async createPaymentSession(
|
||||
paymentCollectionId: string,
|
||||
input: CreatePaymentSessionDTO,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentSessionDTO> {
|
||||
let paymentSession: PaymentSession
|
||||
|
||||
try {
|
||||
const providerSessionSession =
|
||||
await this.paymentProviderService_.createSession(input.provider_id, {
|
||||
context: input.context ?? {},
|
||||
amount: input.amount,
|
||||
currency_code: input.currency_code,
|
||||
})
|
||||
|
||||
input.data = {
|
||||
...input.data,
|
||||
...providerSessionSession,
|
||||
}
|
||||
|
||||
paymentSession = await this.createPaymentSession_(
|
||||
paymentCollectionId,
|
||||
input,
|
||||
sharedContext
|
||||
)
|
||||
} catch (error) {
|
||||
// In case the session is created at the provider, but fails to be created in Medusa,
|
||||
// we catch the error and delete the session at the provider and rethrow.
|
||||
await this.paymentProviderService_.deleteSession({
|
||||
provider_id: input.provider_id,
|
||||
data: input.data,
|
||||
})
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
return await this.baseRepository_.serialize(paymentSession, {
|
||||
populate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async createPaymentSession_(
|
||||
paymentCollectionId: string,
|
||||
data: CreatePaymentSessionDTO,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentSession> {
|
||||
const paymentSession = await 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
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async updatePaymentSession(
|
||||
data: UpdatePaymentSessionDTO,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentSessionDTO> {
|
||||
const session = await this.paymentSessionService_.retrieve(
|
||||
data.id,
|
||||
{ select: ["id", "data", "provider_id"] },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const updated = await this.paymentSessionService_.update(
|
||||
{
|
||||
id: session.id,
|
||||
amount: data.amount,
|
||||
currency_code: data.currency_code,
|
||||
data: data.data,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize(updated[0], { populate: true })
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async deletePaymentSession(
|
||||
id: string,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<void> {
|
||||
const session = await this.paymentSessionService_.retrieve(
|
||||
id,
|
||||
{ select: ["id", "data", "provider_id"] },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
await this.paymentProviderService_.deleteSession({
|
||||
provider_id: session.provider_id,
|
||||
data: session.data,
|
||||
})
|
||||
|
||||
await this.paymentSessionService_.delete(id, sharedContext)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async authorizePaymentSession(
|
||||
id: string,
|
||||
context: Record<string, unknown>,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentDTO> {
|
||||
const session = await this.paymentSessionService_.retrieve(
|
||||
id,
|
||||
{
|
||||
select: [
|
||||
"id",
|
||||
"data",
|
||||
"provider_id",
|
||||
"amount",
|
||||
"currency_code",
|
||||
"payment_collection_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 })
|
||||
}
|
||||
|
||||
const { data, status } =
|
||||
await this.paymentProviderService_.authorizePayment(
|
||||
{
|
||||
provider_id: session.provider_id,
|
||||
data: session.data,
|
||||
},
|
||||
context
|
||||
)
|
||||
|
||||
await this.paymentSessionService_.update(
|
||||
{
|
||||
id: session.id,
|
||||
data,
|
||||
status,
|
||||
authorized_at:
|
||||
status === PaymentSessionStatus.AUTHORIZED ? new Date() : null,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if (status !== PaymentSessionStatus.AUTHORIZED) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Session: ${session.id} is not authorized with the provider.`
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: update status on payment collection if authorized_amount === amount - depends on the BigNumber PR
|
||||
|
||||
const payment = await this.paymentService_.create(
|
||||
{
|
||||
amount: session.amount,
|
||||
currency_code: session.currency_code,
|
||||
payment_session: session.id,
|
||||
payment_collection_id: session.payment_collection_id,
|
||||
provider_id: session.provider_id,
|
||||
// customer_id: context.customer.id,
|
||||
data,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.retrievePayment(
|
||||
payment.id,
|
||||
{ relations: ["payment_collection"] },
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async updatePayment(
|
||||
data: UpdatePaymentDTO,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentDTO> {
|
||||
// NOTE: currently there is no update with the provider but maybe data could be updated
|
||||
const result = await this.paymentService_.update(data, sharedContext)
|
||||
|
||||
return await this.baseRepository_.serialize<PaymentDTO>(result[0], {
|
||||
populate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async capturePayment(
|
||||
data: CreateCaptureDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<PaymentDTO> {
|
||||
const payment = await this.paymentService_.retrieve(
|
||||
data.payment_id,
|
||||
{
|
||||
select: [
|
||||
"id",
|
||||
"data",
|
||||
"provider_id",
|
||||
"amount",
|
||||
"raw_amount",
|
||||
"canceled_at",
|
||||
],
|
||||
relations: ["captures.raw_amount"],
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
// If no custom amount is passed, we assume the full amount needs to be captured
|
||||
if (!data.amount) {
|
||||
data.amount = payment.amount as number
|
||||
}
|
||||
|
||||
if (payment.canceled_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`The payment: ${payment.id} has been canceled.`
|
||||
)
|
||||
}
|
||||
|
||||
if (payment.captured_at) {
|
||||
return await this.retrievePayment(
|
||||
data.payment_id,
|
||||
{ relations: ["captures"] },
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
const capturedAmount = payment.captures.reduce((captureAmount, next) => {
|
||||
return MathBN.add(captureAmount, next.raw_amount)
|
||||
}, MathBN.convert(0))
|
||||
|
||||
const authorizedAmount = new BigNumber(payment.raw_amount)
|
||||
const newCaptureAmount = new BigNumber(data.amount)
|
||||
const remainingToCapture = MathBN.sub(authorizedAmount, capturedAmount)
|
||||
|
||||
if (MathBN.gt(newCaptureAmount, remainingToCapture)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`You cannot capture more than the authorized amount substracted by what is already captured.`
|
||||
)
|
||||
}
|
||||
|
||||
const paymentData = await this.paymentProviderService_.capturePayment({
|
||||
data: payment.data!,
|
||||
provider_id: payment.provider_id,
|
||||
})
|
||||
|
||||
await this.captureService_.create(
|
||||
{
|
||||
payment: data.payment_id,
|
||||
amount: data.amount,
|
||||
captured_by: data.captured_by,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
await this.paymentService_.update(
|
||||
{ id: payment.id, data: paymentData },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
// When the entire authorized amount has been captured, we mark it fully capture by setting the captured_at field
|
||||
const totalCaptured = MathBN.convert(
|
||||
MathBN.add(capturedAmount, newCaptureAmount)
|
||||
)
|
||||
if (MathBN.gte(totalCaptured, authorizedAmount)) {
|
||||
await this.paymentService_.update(
|
||||
{ id: payment.id, captured_at: new Date() },
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
return await this.retrievePayment(
|
||||
payment.id,
|
||||
{ relations: ["captures"] },
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async refundPayment(
|
||||
data: CreateRefundDTO,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentDTO> {
|
||||
const payment = await this.paymentService_.retrieve(
|
||||
data.payment_id,
|
||||
{
|
||||
select: ["id", "data", "provider_id", "amount", "raw_amount"],
|
||||
relations: ["captures.raw_amount"],
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if (!data.amount) {
|
||||
data.amount = payment.amount as number
|
||||
}
|
||||
|
||||
const capturedAmount = payment.captures.reduce((captureAmount, next) => {
|
||||
const amountAsBigNumber = new BigNumber(next.raw_amount)
|
||||
return MathBN.add(captureAmount, amountAsBigNumber)
|
||||
}, MathBN.convert(0))
|
||||
const refundAmount = new BigNumber(data.amount)
|
||||
|
||||
if (MathBN.lt(capturedAmount, refundAmount)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`You cannot refund more than what is captured on the payment.`
|
||||
)
|
||||
}
|
||||
|
||||
const paymentData = await this.paymentProviderService_.refundPayment(
|
||||
{
|
||||
data: payment.data!,
|
||||
provider_id: payment.provider_id,
|
||||
},
|
||||
data.amount as number
|
||||
)
|
||||
|
||||
await this.refundService_.create(
|
||||
{
|
||||
payment: data.payment_id,
|
||||
amount: data.amount,
|
||||
created_by: data.created_by,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
await this.paymentService_.update(
|
||||
{ id: payment.id, data: paymentData },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.retrievePayment(
|
||||
payment.id,
|
||||
{ relations: ["refunds"] },
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async cancelPayment(
|
||||
paymentId: string,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentDTO> {
|
||||
const payment = await this.paymentService_.retrieve(
|
||||
paymentId,
|
||||
{ select: ["id", "data", "provider_id"] },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
// TODO: revisit when totals are implemented
|
||||
// if (payment.captured_amount !== 0) {
|
||||
// throw new MedusaError(
|
||||
// MedusaError.Types.INVALID_DATA,
|
||||
// `Cannot cancel a payment: ${payment.id} that has been captured.`
|
||||
// )
|
||||
// }
|
||||
|
||||
await this.paymentProviderService_.cancelPayment({
|
||||
data: payment.data!,
|
||||
provider_id: payment.provider_id,
|
||||
})
|
||||
|
||||
await this.paymentService_.update(
|
||||
{ id: paymentId, canceled_at: new Date() },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.retrievePayment(payment.id, {}, sharedContext)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async processEvent(
|
||||
eventData: ProviderWebhookPayload,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<void> {
|
||||
const providerId = `pp_${eventData.provider}`
|
||||
|
||||
const event = 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(
|
||||
{
|
||||
session_id: event.data.resource_id,
|
||||
},
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
await this.capturePayment(
|
||||
{ payment_id: payment.id, amount: event.data.amount },
|
||||
sharedContext
|
||||
)
|
||||
break
|
||||
}
|
||||
case PaymentActions.AUTHORIZED:
|
||||
await this.authorizePaymentSession(
|
||||
event.data.resource_id as string,
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listPaymentProviders(
|
||||
filters: FilterablePaymentProviderProps = {},
|
||||
config: FindConfig<PaymentProviderDTO> = {},
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentProviderDTO[]> {
|
||||
const providers = await this.paymentProviderService_.list(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize<PaymentProviderDTO[]>(
|
||||
providers,
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listAndCountPaymentProviders(
|
||||
filters: FilterablePaymentProviderProps = {},
|
||||
config: FindConfig<PaymentProviderDTO> = {},
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<[PaymentProviderDTO[], number]> {
|
||||
const [providers, count] = await this.paymentProviderService_.listAndCount(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return [
|
||||
await this.baseRepository_.serialize<PaymentProviderDTO[]>(providers, {
|
||||
populate: true,
|
||||
}),
|
||||
count,
|
||||
]
|
||||
}
|
||||
}
|
||||
215
packages/modules/payment/src/services/payment-provider.ts
Normal file
215
packages/modules/payment/src/services/payment-provider.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
import {
|
||||
Context,
|
||||
CreatePaymentProviderDTO,
|
||||
CreatePaymentProviderSession,
|
||||
DAL,
|
||||
FilterablePaymentProviderProps,
|
||||
FindConfig,
|
||||
InternalModuleDeclaration,
|
||||
IPaymentProvider,
|
||||
PaymentProviderAuthorizeResponse,
|
||||
PaymentProviderDataInput,
|
||||
PaymentProviderDTO,
|
||||
PaymentProviderError,
|
||||
PaymentProviderSessionResponse,
|
||||
PaymentSessionStatus,
|
||||
ProviderWebhookPayload,
|
||||
UpdatePaymentProviderSession,
|
||||
WebhookActionResult,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
isPaymentProviderError,
|
||||
MedusaContext,
|
||||
ModulesSdkUtils,
|
||||
} from "@medusajs/utils"
|
||||
import { PaymentProvider } from "@models"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { EOL } from "os"
|
||||
|
||||
type InjectedDependencies = {
|
||||
paymentProviderRepository: DAL.RepositoryService
|
||||
[key: `pp_${string}`]: IPaymentProvider
|
||||
}
|
||||
|
||||
export default class PaymentProviderService {
|
||||
protected readonly container_: InjectedDependencies
|
||||
protected readonly paymentProviderRepository_: DAL.RepositoryService
|
||||
|
||||
constructor(
|
||||
container: InjectedDependencies,
|
||||
|
||||
protected readonly moduleDeclaration: InternalModuleDeclaration
|
||||
) {
|
||||
this.container_ = container
|
||||
this.paymentProviderRepository_ = container.paymentProviderRepository
|
||||
}
|
||||
|
||||
@InjectTransactionManager("paymentProviderRepository_")
|
||||
async create(
|
||||
data: CreatePaymentProviderDTO[],
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentProvider[]> {
|
||||
return await this.paymentProviderRepository_.create(data, sharedContext)
|
||||
}
|
||||
|
||||
@InjectManager("paymentProviderRepository_")
|
||||
async list(
|
||||
filters: FilterablePaymentProviderProps,
|
||||
config: FindConfig<PaymentProviderDTO>,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentProvider[]> {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<PaymentProvider>(
|
||||
filters,
|
||||
config
|
||||
)
|
||||
|
||||
return await this.paymentProviderRepository_.find(
|
||||
queryOptions,
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("paymentProviderRepository_")
|
||||
async listAndCount(
|
||||
filters: FilterablePaymentProviderProps,
|
||||
config: FindConfig<PaymentProviderDTO>,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<[PaymentProvider[], number]> {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<PaymentProvider>(
|
||||
filters,
|
||||
config
|
||||
)
|
||||
|
||||
return await this.paymentProviderRepository_.findAndCount(
|
||||
queryOptions,
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
retrieveProvider(providerId: string): IPaymentProvider {
|
||||
try {
|
||||
return this.container_[providerId] as IPaymentProvider
|
||||
} catch (e) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Could not find a payment provider with id: ${providerId}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async createSession(
|
||||
providerId: string,
|
||||
sessionInput: CreatePaymentProviderSession
|
||||
): Promise<PaymentProviderSessionResponse["data"]> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
|
||||
const paymentResponse = await provider.initiatePayment(sessionInput)
|
||||
|
||||
if (isPaymentProviderError(paymentResponse)) {
|
||||
this.throwPaymentProviderError(paymentResponse)
|
||||
}
|
||||
|
||||
return (paymentResponse as PaymentProviderSessionResponse).data
|
||||
}
|
||||
|
||||
async updateSession(
|
||||
providerId: string,
|
||||
sessionInput: UpdatePaymentProviderSession
|
||||
): Promise<Record<string, unknown> | undefined> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
|
||||
const paymentResponse = await provider.updatePayment(sessionInput)
|
||||
|
||||
if (isPaymentProviderError(paymentResponse)) {
|
||||
this.throwPaymentProviderError(paymentResponse)
|
||||
}
|
||||
|
||||
return (paymentResponse as PaymentProviderSessionResponse)?.data
|
||||
}
|
||||
|
||||
async deleteSession(input: PaymentProviderDataInput): Promise<void> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
|
||||
const error = await provider.deletePayment(input.data)
|
||||
if (isPaymentProviderError(error)) {
|
||||
this.throwPaymentProviderError(error)
|
||||
}
|
||||
}
|
||||
|
||||
async authorizePayment(
|
||||
input: PaymentProviderDataInput,
|
||||
context: Record<string, unknown>
|
||||
): Promise<{ data: Record<string, unknown>; status: PaymentSessionStatus }> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
|
||||
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<PaymentSessionStatus> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
return await provider.getPaymentStatus(input.data)
|
||||
}
|
||||
|
||||
async capturePayment(
|
||||
input: PaymentProviderDataInput
|
||||
): Promise<Record<string, unknown>> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
|
||||
const res = await provider.capturePayment(input.data)
|
||||
if (isPaymentProviderError(res)) {
|
||||
this.throwPaymentProviderError(res)
|
||||
}
|
||||
|
||||
return res as Record<string, unknown>
|
||||
}
|
||||
|
||||
async cancelPayment(input: PaymentProviderDataInput): Promise<void> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
|
||||
const error = await provider.cancelPayment(input.data)
|
||||
if (isPaymentProviderError(error)) {
|
||||
this.throwPaymentProviderError(error)
|
||||
}
|
||||
}
|
||||
|
||||
async refundPayment(
|
||||
input: PaymentProviderDataInput,
|
||||
amount: number
|
||||
): Promise<Record<string, unknown>> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
|
||||
const res = await provider.refundPayment(input.data, amount)
|
||||
if (isPaymentProviderError(res)) {
|
||||
this.throwPaymentProviderError(res)
|
||||
}
|
||||
|
||||
return res as Record<string, unknown>
|
||||
}
|
||||
|
||||
async getWebhookActionAndData(
|
||||
providerId: string,
|
||||
data: ProviderWebhookPayload["payload"]
|
||||
): Promise<WebhookActionResult> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
|
||||
return await provider.getWebhookActionAndData(data)
|
||||
}
|
||||
|
||||
private throwPaymentProviderError(errObj: PaymentProviderError) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`${errObj.error}${errObj.detail ? `:${EOL}${errObj.detail}` : ""}`,
|
||||
errObj.code
|
||||
)
|
||||
}
|
||||
}
|
||||
27
packages/modules/payment/src/types/index.ts
Normal file
27
packages/modules/payment/src/types/index.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
Logger,
|
||||
ModuleProviderExports,
|
||||
ModuleServiceInitializeOptions,
|
||||
} from "@medusajs/types"
|
||||
|
||||
export type InitializeModuleInjectableDependencies = {
|
||||
logger?: Logger
|
||||
}
|
||||
|
||||
export type PaymentModuleOptions = Partial<ModuleServiceInitializeOptions> & {
|
||||
/**
|
||||
* Providers to be registered
|
||||
*/
|
||||
providers?: {
|
||||
/**
|
||||
* The module provider to be registered
|
||||
*/
|
||||
resolve: string | ModuleProviderExports
|
||||
options: {
|
||||
/**
|
||||
* key value pair of the provider name and the configuration to be passed to the provider constructor
|
||||
*/
|
||||
config: Record<string, unknown>
|
||||
}
|
||||
}[]
|
||||
}
|
||||
Reference in New Issue
Block a user