feat(payment): provider service (#6308)

This commit is contained in:
Frane Polić authored and GitHub committed 2024-02-12 20:57:41 +01:00
1 parent 869dc751a0
commit a6a4b3f01a
21 files changed
+1439 -565

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
---
"@medusajs/types": patch
"@medusajs/utils": patch
---
feat(types, utils): payment module - provider service
@@ -24,21 +24,21 @@ export const defaultPaymentSessionData = [
id: "pay-sess-id-1", id: "pay-sess-id-1",
amount: 100, amount: 100,
currency_code: "usd", currency_code: "usd",
provider_id: "manual", provider_id: "system",
payment_collection: "pay-col-id-1", payment_collection: "pay-col-id-1",
}, },
{ {
id: "pay-sess-id-2", id: "pay-sess-id-2",
amount: 100, amount: 100,
currency_code: "usd", currency_code: "usd",
provider_id: "manual", provider_id: "system",
payment_collection: "pay-col-id-2", payment_collection: "pay-col-id-2",
}, },
{ {
id: "pay-sess-id-3", id: "pay-sess-id-3",
amount: 100, amount: 100,
currency_code: "usd", currency_code: "usd",
provider_id: "manual", provider_id: "system",
payment_collection: "pay-col-id-2", payment_collection: "pay-col-id-2",
}, },
] ]
@@ -50,7 +50,7 @@ export const defaultPaymentData = [
currency_code: "usd", currency_code: "usd",
payment_collection: "pay-col-id-1", payment_collection: "pay-col-id-1",
payment_session: "pay-sess-id-1", payment_session: "pay-sess-id-1",
provider_id: "manual", provider_id: "system",
authorized_amount: 100, authorized_amount: 100,
data: {}, data: {},
}, },
@@ -61,7 +61,7 @@ export const defaultPaymentData = [
currency_code: "usd", currency_code: "usd",
payment_collection: "pay-col-id-2", payment_collection: "pay-col-id-2",
payment_session: "pay-sess-id-2", payment_session: "pay-sess-id-2",
provider_id: "manual", provider_id: "system",
data: {}, data: {},
}, },
] ]
@@ -1,50 +1,35 @@
import { IPaymentModuleService } from "@medusajs/types" import { IPaymentModuleService } from "@medusajs/types"
import { SqlEntityManager } from "@mikro-orm/postgresql" import { initModules } from "medusa-test-utils"
import { Modules } from "@medusajs/modules-sdk"
import { initialize } from "../../../../src" import { MikroOrmWrapper } from "../../../utils"
import { DB_URL, MikroOrmWrapper } from "../../../utils"
import { import {
createPaymentCollections, createPaymentCollections,
createPaymentSessions, createPaymentSessions,
createPayments, createPayments,
} from "../../../__fixtures__" } from "../../../__fixtures__"
import { getInitModuleConfig } from "../../../utils/get-init-module-config" import { getInitModuleConfig } from "../../../utils/get-init-module-config"
import { initModules } from "medusa-test-utils"
import { Modules } from "@medusajs/modules-sdk"
jest.setTimeout(30000) jest.setTimeout(30000)
describe("Payment Module Service", () => { describe("Payment Module Service", () => {
describe("Payment Flow", () => {
let service: IPaymentModuleService let service: IPaymentModuleService
describe("PaymentCollection", () => {
let repositoryManager: SqlEntityManager
let shutdownFunc: () => Promise<void> let shutdownFunc: () => Promise<void>
beforeAll(async () => {
const initModulesConfig = getInitModuleConfig()
const { medusaApp, shutdown } = await initModules(initModulesConfig)
service = medusaApp.modules[Modules.PAYMENT]
shutdownFunc = shutdown
})
afterAll(async () => { afterAll(async () => {
await shutdownFunc() await shutdownFunc()
}) })
beforeEach(async () => { beforeEach(async () => {
await MikroOrmWrapper.setupDatabase() await MikroOrmWrapper.setupDatabase()
repositoryManager = await MikroOrmWrapper.forkManager() const repositoryManager = await MikroOrmWrapper.forkManager()
service = await initialize({ const initModulesConfig = getInitModuleConfig()
database: { const { medusaApp, shutdown } = await initModules(initModulesConfig)
clientUrl: DB_URL, service = medusaApp.modules[Modules.PAYMENT]
schema: process.env.MEDUSA_PAYMNET_DB_SCHEMA,
}, shutdownFunc = shutdown
})
await createPaymentCollections(repositoryManager) await createPaymentCollections(repositoryManager)
await createPaymentSessions(repositoryManager) await createPaymentSessions(repositoryManager)
@@ -53,12 +38,120 @@ describe("Payment Module Service", () => {
afterEach(async () => { afterEach(async () => {
await MikroOrmWrapper.clearDatabase() await MikroOrmWrapper.clearDatabase()
await shutdownFunc()
})
it("complete payment flow successfully", async () => {
let paymentCollection = await service.createPaymentCollections({
currency_code: "USD",
amount: 200,
region_id: "reg_123",
})
const paymentSession = await service.createPaymentSession(
paymentCollection.id,
{
provider_id: "system",
providerContext: {
amount: 200,
currency_code: "USD",
payment_session_data: {},
context: {},
customer: {},
billing_address: {},
email: "test@test.test.com",
resource_id: "cart_test",
},
}
)
const payment = await service.authorizePaymentSession(
paymentSession.id,
{}
)
await service.capturePayment({
amount: 200,
payment_id: payment.id,
})
await service.completePaymentCollections(paymentCollection.id)
paymentCollection = await service.retrievePaymentCollection(
paymentCollection.id,
{ relations: ["payment_sessions", "payments.captures"] }
)
expect(paymentCollection).toEqual(
expect.objectContaining({
id: expect.any(String),
currency_code: "USD",
amount: 200,
// TODO
// authorized_amount: 200,
// status: "authorized",
region_id: "reg_123",
deleted_at: null,
completed_at: expect.any(Date),
payment_sessions: [
expect.objectContaining({
id: expect.any(String),
currency_code: "USD",
amount: 200,
provider_id: "system",
status: "authorized",
authorized_at: expect.any(Date),
}),
],
payments: [
expect.objectContaining({
id: expect.any(String),
amount: 200,
currency_code: "USD",
provider_id: "system",
captures: [
expect.objectContaining({
amount: 200,
}),
],
}),
],
})
)
})
})
describe("PaymentCollection", () => {
let service: IPaymentModuleService
let shutdownFunc: () => Promise<void>
afterAll(async () => {
await shutdownFunc()
})
beforeEach(async () => {
await MikroOrmWrapper.setupDatabase()
const repositoryManager = await MikroOrmWrapper.forkManager()
const initModulesConfig = getInitModuleConfig()
const { medusaApp, shutdown } = await initModules(initModulesConfig)
service = medusaApp.modules[Modules.PAYMENT]
shutdownFunc = shutdown
await createPaymentCollections(repositoryManager)
await createPaymentSessions(repositoryManager)
await createPayments(repositoryManager)
})
afterEach(async () => {
await MikroOrmWrapper.clearDatabase()
await shutdownFunc()
}) })
describe("create", () => { describe("create", () => {
it("should throw an error when required params are not passed", async () => { it("should throw an error when required params are not passed", async () => {
let error = await service let error = await service
.createPaymentCollection([ .createPaymentCollections([
{ {
amount: 200, amount: 200,
region_id: "req_123", region_id: "req_123",
@@ -71,7 +164,7 @@ describe("Payment Module Service", () => {
) )
error = await service error = await service
.createPaymentCollection([ .createPaymentCollections([
{ {
currency_code: "USD", currency_code: "USD",
region_id: "req_123", region_id: "req_123",
@@ -84,7 +177,7 @@ describe("Payment Module Service", () => {
) )
error = await service error = await service
.createPaymentCollection([ .createPaymentCollections([
{ {
currency_code: "USD", currency_code: "USD",
amount: 200, amount: 200,
@@ -99,7 +192,7 @@ describe("Payment Module Service", () => {
it("should create a payment collection successfully", async () => { it("should create a payment collection successfully", async () => {
const [createdPaymentCollection] = const [createdPaymentCollection] =
await service.createPaymentCollection([ await service.createPaymentCollections([
{ currency_code: "USD", amount: 200, region_id: "reg_123" }, { currency_code: "USD", amount: 200, region_id: "reg_123" },
]) ])
@@ -220,10 +313,10 @@ describe("Payment Module Service", () => {
describe("update", () => { describe("update", () => {
it("should update a Payment Collection", async () => { it("should update a Payment Collection", async () => {
await service.updatePaymentCollection({ await service.updatePaymentCollections({
id: "pay-col-id-2", id: "pay-col-id-2",
currency_code: "eur", currency_code: "eur",
authorized_amount: 200, region_id: "reg-2",
}) })
const collection = await service.retrievePaymentCollection( const collection = await service.retrievePaymentCollection(
@@ -233,84 +326,48 @@ describe("Payment Module Service", () => {
expect(collection).toEqual( expect(collection).toEqual(
expect.objectContaining({ expect.objectContaining({
id: "pay-col-id-2", id: "pay-col-id-2",
authorized_amount: 200, region_id: "reg-2",
currency_code: "eur", currency_code: "eur",
}) })
) )
}) })
}) })
describe("complete", () => {
it("should complete a Payment Collection", async () => {
await service.completePaymentCollections("pay-col-id-1")
const collection = await service.retrievePaymentCollection(
"pay-col-id-1"
)
expect(collection).toEqual(
expect.objectContaining({
id: "pay-col-id-1",
completed_at: expect.any(Date),
})
)
})
})
}) })
describe("PaymentSession", () => { describe("PaymentSession", () => {
let repositoryManager: SqlEntityManager let service: IPaymentModuleService
let shutdownFunc: () => Promise<void>
afterAll(async () => {
await shutdownFunc()
})
beforeEach(async () => { beforeEach(async () => {
await MikroOrmWrapper.setupDatabase() await MikroOrmWrapper.setupDatabase()
repositoryManager = await MikroOrmWrapper.forkManager() const repositoryManager = await MikroOrmWrapper.forkManager()
service = await initialize({ const initModulesConfig = getInitModuleConfig()
database: { const { medusaApp, shutdown } = await initModules(initModulesConfig)
clientUrl: DB_URL, service = medusaApp.modules[Modules.PAYMENT]
schema: process.env.MEDUSA_PAYMNET_DB_SCHEMA,
},
})
await createPaymentCollections(repositoryManager) shutdownFunc = shutdown
await createPaymentSessions(repositoryManager)
})
afterEach(async () => {
await MikroOrmWrapper.clearDatabase()
})
describe("create", () => {
it("should create a payment session successfully", async () => {
const paymentCollection = await service.createPaymentSession(
"pay-col-id-1",
{
amount: 200,
provider_id: "manual",
currency_code: "usd",
}
)
expect(paymentCollection).toEqual(
expect.objectContaining({
id: "pay-col-id-1",
status: "not_paid",
payment_sessions: expect.arrayContaining([
{
id: expect.any(String),
data: null,
status: "pending",
authorized_at: null,
currency_code: "usd",
amount: 200,
provider_id: "manual",
payment_collection: expect.objectContaining({
id: paymentCollection.id,
}),
},
]),
})
)
})
})
})
describe("Payment", () => {
let repositoryManager: SqlEntityManager
beforeEach(async () => {
await MikroOrmWrapper.setupDatabase()
repositoryManager = await MikroOrmWrapper.forkManager()
service = await initialize({
database: {
clientUrl: DB_URL,
schema: process.env.MEDUSA_PAYMNET_DB_SCHEMA,
},
})
await createPaymentCollections(repositoryManager) await createPaymentCollections(repositoryManager)
await createPaymentSessions(repositoryManager) await createPaymentSessions(repositoryManager)
@@ -319,38 +376,149 @@ describe("Payment Module Service", () => {
afterEach(async () => { afterEach(async () => {
await MikroOrmWrapper.clearDatabase() await MikroOrmWrapper.clearDatabase()
await shutdownFunc()
}) })
describe("create", () => { describe("create", () => {
it("should create a payment successfully", async () => { it("should create a payment session successfully", async () => {
let paymentCollection = await service.createPaymentCollection({ await service.createPaymentSession("pay-col-id-1", {
currency_code: "usd", provider_id: "system",
providerContext: {
amount: 200, amount: 200,
region_id: "reg", currency_code: "usd",
payment_session_data: {},
context: {},
customer: {},
billing_address: {},
email: "test@test.test.com",
resource_id: "cart_test",
},
}) })
paymentCollection = await service.createPaymentSession( const paymentCollection = await service.retrievePaymentCollection(
paymentCollection.id, "pay-col-id-1",
{ { relations: ["payment_sessions"] }
amount: 200,
provider_id: "manual",
currency_code: "usd",
}
) )
const createdPayment = await service.createPayment({ expect(paymentCollection).toEqual(
data: {}, expect.objectContaining({
amount: 200, id: "pay-col-id-1",
provider_id: "manual", status: "not_paid",
currency_code: "usd", payment_sessions: expect.arrayContaining([
payment_collection_id: paymentCollection.id,
payment_session_id: paymentCollection.payment_sessions![0].id,
})
expect(createdPayment).toEqual(
expect.objectContaining({ expect.objectContaining({
id: expect.any(String), id: expect.any(String),
authorized_amount: null, data: {},
status: "pending",
authorized_at: null,
currency_code: "usd",
amount: 200,
provider_id: "system",
}),
]),
})
)
})
})
describe("update", () => {
it("should update a payment session successfully", async () => {
let session = await service.createPaymentSession("pay-col-id-1", {
provider_id: "system",
providerContext: {
amount: 200,
currency_code: "usd",
payment_session_data: {},
context: {},
customer: {},
billing_address: {},
email: "test@test.test.com",
resource_id: "cart_test",
},
})
session = await service.updatePaymentSession({
id: session.id,
providerContext: {
amount: 200,
currency_code: "eur",
resource_id: "res_id",
context: {},
customer: {},
billing_address: {},
email: "new@test.tsst",
payment_session_data: {},
},
})
expect(session).toEqual(
expect.objectContaining({
id: expect.any(String),
status: "pending",
currency_code: "eur",
amount: 200,
})
)
})
})
describe("authorize", () => {
it("should authorize a payment session", async () => {
const collection = await service.createPaymentCollections({
amount: 200,
region_id: "test-region",
currency_code: "usd",
})
const session = await service.createPaymentSession(collection.id, {
provider_id: "system",
providerContext: {
amount: 100,
currency_code: "usd",
payment_session_data: {},
context: {},
resource_id: "test",
email: "test@test.com",
billing_address: {},
customer: {},
},
})
const payment = await service.authorizePaymentSession(session.id, {})
expect(payment).toEqual(
expect.objectContaining({
id: expect.any(String),
amount: 100,
authorized_amount: 100,
currency_code: "usd",
provider_id: "system",
refunds: [],
captures: [],
data: {},
cart_id: null,
order_id: null,
order_edit_id: null,
customer_id: null,
deleted_at: null,
captured_at: null,
canceled_at: null,
payment_collection: expect.objectContaining({
id: expect.any(String),
}),
payment_session: {
id: expect.any(String),
currency_code: "usd",
amount: 100,
provider_id: "system",
data: {},
status: "authorized",
authorized_at: expect.any(Date),
payment_collection: expect.objectContaining({
id: expect.any(String),
}),
payment: expect.objectContaining({
authorized_amount: 100,
cart_id: null, cart_id: null,
order_id: null, order_id: null,
order_edit_id: null, order_edit_id: null,
@@ -361,19 +529,44 @@ describe("Payment Module Service", () => {
canceled_at: null, canceled_at: null,
refunds: [], refunds: [],
captures: [], captures: [],
amount: 200, amount: 100,
currency_code: "usd", currency_code: "usd",
provider_id: "manual", provider_id: "system",
payment_collection: expect.objectContaining({
id: paymentCollection.id,
}),
payment_session: expect.objectContaining({
id: paymentCollection.payment_sessions![0].id,
}), }),
},
}) })
) )
}) })
}) })
})
describe("Payment", () => {
let service: IPaymentModuleService
let shutdownFunc: () => Promise<void>
afterAll(async () => {
await shutdownFunc()
})
beforeEach(async () => {
await MikroOrmWrapper.setupDatabase()
const repositoryManager = await MikroOrmWrapper.forkManager()
const initModulesConfig = getInitModuleConfig()
const { medusaApp, shutdown } = await initModules(initModulesConfig)
service = medusaApp.modules[Modules.PAYMENT]
shutdownFunc = shutdown
await createPaymentCollections(repositoryManager)
await createPaymentSessions(repositoryManager)
await createPayments(repositoryManager)
})
afterEach(async () => {
await MikroOrmWrapper.clearDatabase()
await shutdownFunc()
})
describe("update", () => { describe("update", () => {
it("should update a payment successfully", async () => { it("should update a payment successfully", async () => {
@@ -417,69 +610,7 @@ describe("Payment Module Service", () => {
) )
}) })
it("should capture payments in bulk successfully", async () => {
const capturedPayments = await service.capturePayment([
{
amount: 50, // partially captured
payment_id: "pay-id-1",
},
{
amount: 100, // fully captured
payment_id: "pay-id-2",
},
])
expect(capturedPayments).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "pay-id-1",
amount: 100,
authorized_amount: 100,
captured_at: null,
captures: [
expect.objectContaining({
created_by: null,
amount: 50,
}),
],
// captured_amount: 50,
}),
expect.objectContaining({
id: "pay-id-2",
amount: 100,
authorized_amount: 100,
// captured_at: expect.any(Date),
captures: [
expect.objectContaining({
created_by: null,
amount: 100,
}),
],
// captured_amount: 100,
}),
])
)
})
// TODO: uncomment when totals are implemented // TODO: uncomment when totals are implemented
// it("should fail to capture payments in bulk if one of the captures fail", async () => {
// const error = await service
// .capturePayment([
// {
// amount: 50,
// payment_id: "pay-id-1",
// },
// {
// amount: 200, // exceeds authorized amount
// payment_id: "pay-id-2",
// },
// ])
// .catch((e) => e)
//
// expect(error.message).toEqual(
// "Total captured amount for payment: pay-id-2 exceeds authorized amount."
// )
// })
// it("should fail to capture amount greater than authorized", async () => { // it("should fail to capture amount greater than authorized", async () => {
// const error = await service // const error = await service
@@ -490,10 +621,10 @@ describe("Payment Module Service", () => {
// .catch((e) => e) // .catch((e) => e)
// //
// expect(error.message).toEqual( // expect(error.message).toEqual(
// "Total captured amount for payment: pay-id-1 exceeds authorized amount." // "Total captured amount for payment: pay-id-1 exceeds authorised amount."
// ) // )
// }) // })
//
// it("should fail to capture already captured payment", async () => { // it("should fail to capture already captured payment", async () => {
// await service.capturePayment({ // await service.capturePayment({
// amount: 100, // amount: 100,
@@ -507,47 +638,40 @@ describe("Payment Module Service", () => {
// }) // })
// .catch((e) => e) // .catch((e) => e)
// //
// expect(error.message).toEqual("The payment is already fully captured.") // expect(error.message).toEqual(
// "The payment: pay-id-1 is already fully captured."
// )
// })
//
// it("should fail to capture a canceled payment", async () => {
// await service.cancelPayment("pay-id-1")
//
// const error = await service
// .capturePayment({
// amount: 100,
// payment_id: "pay-id-1",
// })
// .catch((e) => e)
//
// expect(error.message).toEqual(
// "The payment: pay-id-1 has been canceled."
// )
// }) // })
}) })
describe("refund", () => { describe("refund", () => {
it("should refund a payments in bulk successfully", async () => { it("should refund a payments successfully", async () => {
await service.capturePayment({
amount: 100,
payment_id: "pay-id-1",
})
await service.capturePayment({ await service.capturePayment({
amount: 100, amount: 100,
payment_id: "pay-id-2", payment_id: "pay-id-2",
}) })
const refundedPayment = await service.refundPayment([ const refundedPayment = await service.refundPayment({
{
amount: 100,
payment_id: "pay-id-1",
},
{
amount: 100, amount: 100,
payment_id: "pay-id-2", payment_id: "pay-id-2",
}, })
])
expect(refundedPayment).toEqual( expect(refundedPayment).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "pay-id-1",
amount: 100,
refunds: [
expect.objectContaining({
created_by: null,
amount: 100,
}),
],
// captured_amount: 100,
// refunded_amount: 100,
}),
expect.objectContaining({ expect.objectContaining({
id: "pay-id-2", id: "pay-id-2",
amount: 100, amount: 100,
@@ -559,8 +683,7 @@ describe("Payment Module Service", () => {
], ],
// captured_amount: 100, // captured_amount: 100,
// refunded_amount: 100, // refunded_amount: 100,
}), })
])
) )
}) })
@@ -582,5 +705,31 @@ describe("Payment Module Service", () => {
// ) // )
// }) // })
}) })
describe("cancel", () => {
it("should cancel a payment", async () => {
const payment = await service.cancelPayment("pay-id-2")
expect(payment).toEqual(
expect.objectContaining({
id: "pay-id-2",
canceled_at: expect.any(Date),
})
)
})
// TODO: revisit when totals are implemented
// it("should throw if trying to cancel a captured payment", async () => {
// await service.capturePayment({ payment_id: "pay-id-2", amount: 100 })
//
// const error = await service
// .cancelPayment("pay-id-2")
// .catch((e) => e.message)
//
// expect(error).toEqual(
// "Cannot cancel a payment: pay-id-2 that has been captured."
// )
// })
})
}) })
}) })
+10
View File
@@ -0,0 +1,10 @@
import { IPaymentModuleService, LoaderOptions } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
export default async ({ container }: LoaderOptions): Promise<void> => {
const paymentModuleService: IPaymentModuleService = container.resolve(
ModuleRegistrationName.PAYMENT
)
await paymentModuleService.createProvidersOnLoad()
}
+1 -1
View File
@@ -1,4 +1,4 @@
export * from "./connection" export * from "./connection"
export * from "./container" export * from "./container"
export * from "./providers" export * from "./providers"
export * from "./defaults"
+8 -4
View File
@@ -3,9 +3,11 @@ import { moduleProviderLoader } from "@medusajs/modules-sdk"
import { LoaderOptions, ModuleProvider, ModulesSdkTypes } from "@medusajs/types" import { LoaderOptions, ModuleProvider, ModulesSdkTypes } from "@medusajs/types"
import { Lifetime, asFunction } from "awilix" import { Lifetime, asFunction } from "awilix"
import * as providers from "../providers"
const registrationFn = async (klass, container, pluginOptions) => { const registrationFn = async (klass, container, pluginOptions) => {
container.register({ container.register({
[`payment_provider_${klass.prototype}`]: asFunction( [`pp_${klass.identifier}`]: asFunction(
(cradle) => new klass(cradle, pluginOptions), (cradle) => new klass(cradle, pluginOptions),
{ {
lifetime: klass.LIFE_TIME || Lifetime.SINGLETON, lifetime: klass.LIFE_TIME || Lifetime.SINGLETON,
@@ -30,12 +32,14 @@ export default async ({
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions | ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
) & { providers: ModuleProvider[] } ) & { providers: ModuleProvider[] }
>): Promise<void> => { >): Promise<void> => {
const pluginProviders = // Local providers
options?.providers?.filter((provider) => provider.resolve) || [] for (const provider of Object.values(providers)) {
await registrationFn(provider, container, {})
}
await moduleProviderLoader({ await moduleProviderLoader({
container, container,
providers: pluginProviders, providers: options?.providers || [],
registerServiceFn: registrationFn, registerServiceFn: registrationFn,
}) })
} }
@@ -43,19 +43,21 @@ export default class PaymentCollection {
}) })
amount: number amount: number
@Property({ // TODO: make this computed properties
columnType: "numeric",
nullable: true,
serializer: optionalNumericSerializer,
})
authorized_amount: number | null = null
@Property({ // @Property({
columnType: "numeric", // columnType: "numeric",
nullable: true, // nullable: true,
serializer: optionalNumericSerializer, // serializer: optionalNumericSerializer,
}) // })
refunded_amount: number | null = null // authorized_amount: number | null = null
//
// @Property({
// columnType: "numeric",
// nullable: true,
// serializer: optionalNumericSerializer,
// })
// refunded_amount: number | null = null
@Property({ columnType: "text", index: "IDX_payment_collection_region_id" }) @Property({ columnType: "text", index: "IDX_payment_collection_region_id" })
region_id: string region_id: string
@@ -16,7 +16,7 @@ import Payment from "./payment"
@Entity({ tableName: "payment_session" }) @Entity({ tableName: "payment_session" })
export default class PaymentSession { export default class PaymentSession {
[OptionalProps]?: "status" [OptionalProps]?: "status" | "data"
@PrimaryKey({ columnType: "text" }) @PrimaryKey({ columnType: "text" })
id: string id: string
@@ -33,8 +33,8 @@ export default class PaymentSession {
@Property({ columnType: "text" }) @Property({ columnType: "text" })
provider_id: string provider_id: string
@Property({ columnType: "jsonb", nullable: true }) @Property({ columnType: "jsonb" })
data: Record<string, unknown> | null = null data: Record<string, unknown> = {}
@Enum({ @Enum({
items: () => PaymentSessionStatus, items: () => PaymentSessionStatus,
+7 -1
View File
@@ -5,6 +5,7 @@ import { PaymentModuleService } from "@services"
import loadConnection from "./loaders/connection" import loadConnection from "./loaders/connection"
import loadContainer from "./loaders/container" import loadContainer from "./loaders/container"
import loadProviders from "./loaders/providers" import loadProviders from "./loaders/providers"
import loadDefaults from "./loaders/defaults"
import { Modules } from "@medusajs/modules-sdk" import { Modules } from "@medusajs/modules-sdk"
import { ModulesSdkUtils } from "@medusajs/utils" import { ModulesSdkUtils } from "@medusajs/utils"
@@ -25,7 +26,12 @@ export const revertMigration = ModulesSdkUtils.buildRevertMigrationScript(
) )
const service = PaymentModuleService const service = PaymentModuleService
const loaders = [loadContainer, loadConnection, loadProviders] as any const loaders = [
loadContainer,
loadConnection,
loadProviders,
loadDefaults,
] as any
export const moduleDefinition: ModuleExports = { export const moduleDefinition: ModuleExports = {
service, service,
+1
View File
@@ -0,0 +1 @@
export { default as SystemPaymentProvider } from "./system"
+71
View File
@@ -0,0 +1,71 @@
import {
PaymentProviderContext,
PaymentProviderError,
PaymentProviderSessionResponse,
PaymentSessionStatus,
} from "@medusajs/types"
import { AbstractPaymentProvider } from "@medusajs/utils"
export class SystemProviderService extends AbstractPaymentProvider {
static identifier = "system"
async getStatus(_): Promise<string> {
return "authorized"
}
async getPaymentData(_): Promise<Record<string, unknown>> {
return {}
}
async initiatePayment(
context: PaymentProviderContext
): 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 {}
}
}
export default SystemProviderService
+1
View File
@@ -1 +1,2 @@
export { default as PaymentModuleService } from "./payment-module" export { default as PaymentModuleService } from "./payment-module"
export { default as PaymentProviderService } from "./payment-provider"
+296 -234
View File
@@ -4,6 +4,7 @@ import {
CreateCaptureDTO, CreateCaptureDTO,
CreatePaymentCollectionDTO, CreatePaymentCollectionDTO,
CreatePaymentDTO, CreatePaymentDTO,
CreatePaymentProviderDTO,
CreatePaymentSessionDTO, CreatePaymentSessionDTO,
CreateRefundDTO, CreateRefundDTO,
DAL, DAL,
@@ -14,20 +15,18 @@ import {
PaymentCollectionDTO, PaymentCollectionDTO,
PaymentDTO, PaymentDTO,
PaymentSessionDTO, PaymentSessionDTO,
PaymentSessionStatus,
RefundDTO, RefundDTO,
SetPaymentSessionsDTO,
UpdatePaymentCollectionDTO, UpdatePaymentCollectionDTO,
UpdatePaymentDTO, UpdatePaymentDTO,
UpdatePaymentSessionDTO,
} from "@medusajs/types" } from "@medusajs/types"
import { import {
InjectTransactionManager, InjectTransactionManager,
MedusaContext, MedusaContext,
ModulesSdkUtils,
MedusaError, MedusaError,
InjectManager, ModulesSdkUtils,
} from "@medusajs/utils" } from "@medusajs/utils"
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
import { import {
Capture, Capture,
Payment, Payment,
@@ -36,6 +35,9 @@ import {
Refund, Refund,
} from "@models" } from "@models"
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
import PaymentProviderService from "./payment-provider"
type InjectedDependencies = { type InjectedDependencies = {
baseRepository: DAL.RepositoryService baseRepository: DAL.RepositoryService
paymentService: ModulesSdkTypes.InternalModuleService<any> paymentService: ModulesSdkTypes.InternalModuleService<any>
@@ -43,9 +45,10 @@ type InjectedDependencies = {
refundService: ModulesSdkTypes.InternalModuleService<any> refundService: ModulesSdkTypes.InternalModuleService<any>
paymentSessionService: ModulesSdkTypes.InternalModuleService<any> paymentSessionService: ModulesSdkTypes.InternalModuleService<any>
paymentCollectionService: ModulesSdkTypes.InternalModuleService<any> paymentCollectionService: ModulesSdkTypes.InternalModuleService<any>
paymentProviderService: PaymentProviderService
} }
const generateMethodForModels = [PaymentCollection, PaymentSession] const generateMethodForModels = [PaymentCollection, Payment]
export default class PaymentModuleService< export default class PaymentModuleService<
TPaymentCollection extends PaymentCollection = PaymentCollection, TPaymentCollection extends PaymentCollection = PaymentCollection,
@@ -74,6 +77,7 @@ export default class PaymentModuleService<
protected refundService_: ModulesSdkTypes.InternalModuleService<TRefund> protected refundService_: ModulesSdkTypes.InternalModuleService<TRefund>
protected paymentSessionService_: ModulesSdkTypes.InternalModuleService<TPaymentSession> protected paymentSessionService_: ModulesSdkTypes.InternalModuleService<TPaymentSession>
protected paymentCollectionService_: ModulesSdkTypes.InternalModuleService<TPaymentCollection> protected paymentCollectionService_: ModulesSdkTypes.InternalModuleService<TPaymentCollection>
protected paymentProviderService_: PaymentProviderService
constructor( constructor(
{ {
@@ -82,6 +86,7 @@ export default class PaymentModuleService<
captureService, captureService,
refundService, refundService,
paymentSessionService, paymentSessionService,
paymentProviderService,
paymentCollectionService, paymentCollectionService,
}: InjectedDependencies, }: InjectedDependencies,
protected readonly moduleDeclaration: InternalModuleDeclaration protected readonly moduleDeclaration: InternalModuleDeclaration
@@ -95,6 +100,7 @@ export default class PaymentModuleService<
this.captureService_ = captureService this.captureService_ = captureService
this.paymentService_ = paymentService this.paymentService_ = paymentService
this.paymentSessionService_ = paymentSessionService this.paymentSessionService_ = paymentSessionService
this.paymentProviderService_ = paymentProviderService
this.paymentCollectionService_ = paymentCollectionService this.paymentCollectionService_ = paymentCollectionService
} }
@@ -102,18 +108,18 @@ export default class PaymentModuleService<
return joinerConfig return joinerConfig
} }
createPaymentCollection( createPaymentCollections(
data: CreatePaymentCollectionDTO, data: CreatePaymentCollectionDTO,
sharedContext?: Context sharedContext?: Context
): Promise<PaymentCollectionDTO> ): Promise<PaymentCollectionDTO>
createPaymentCollection( createPaymentCollections(
data: CreatePaymentCollectionDTO[], data: CreatePaymentCollectionDTO[],
sharedContext?: Context sharedContext?: Context
): Promise<PaymentCollectionDTO[]> ): Promise<PaymentCollectionDTO[]>
@InjectTransactionManager("baseRepository_") @InjectTransactionManager("baseRepository_")
async createPaymentCollection( async createPaymentCollections(
data: CreatePaymentCollectionDTO | CreatePaymentCollectionDTO[], data: CreatePaymentCollectionDTO | CreatePaymentCollectionDTO[],
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
): Promise<PaymentCollectionDTO | PaymentCollectionDTO[]> { ): Promise<PaymentCollectionDTO | PaymentCollectionDTO[]> {
@@ -132,17 +138,17 @@ export default class PaymentModuleService<
) )
} }
updatePaymentCollection( updatePaymentCollections(
data: UpdatePaymentCollectionDTO[], data: UpdatePaymentCollectionDTO[],
sharedContext?: Context sharedContext?: Context
): Promise<PaymentCollectionDTO[]> ): Promise<PaymentCollectionDTO[]>
updatePaymentCollection( updatePaymentCollections(
data: UpdatePaymentCollectionDTO, data: UpdatePaymentCollectionDTO,
sharedContext?: Context sharedContext?: Context
): Promise<PaymentCollectionDTO> ): Promise<PaymentCollectionDTO>
@InjectTransactionManager("baseRepository_") @InjectTransactionManager("baseRepository_")
async updatePaymentCollection( async updatePaymentCollections(
data: UpdatePaymentCollectionDTO | UpdatePaymentCollectionDTO[], data: UpdatePaymentCollectionDTO | UpdatePaymentCollectionDTO[],
sharedContext?: Context sharedContext?: Context
): Promise<PaymentCollectionDTO | PaymentCollectionDTO[]> { ): Promise<PaymentCollectionDTO | PaymentCollectionDTO[]> {
@@ -160,106 +166,218 @@ export default class PaymentModuleService<
) )
} }
createPayment( completePaymentCollections(
data: CreatePaymentDTO, paymentCollectionId: string,
sharedContext?: Context sharedContext?: Context
): Promise<PaymentDTO> ): Promise<PaymentCollectionDTO>
createPayment( completePaymentCollections(
data: CreatePaymentDTO[], paymentCollectionId: string[],
sharedContext?: Context sharedContext?: Context
): Promise<PaymentDTO[]> ): Promise<PaymentCollectionDTO[]>
@InjectTransactionManager("baseRepository_") @InjectTransactionManager("baseRepository_")
async createPayment( async completePaymentCollections(
data: CreatePaymentDTO | CreatePaymentDTO[], paymentCollectionId: string | string[],
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
): Promise<PaymentDTO | PaymentDTO[]> { ): Promise<PaymentCollectionDTO | PaymentCollectionDTO[]> {
let input = Array.isArray(data) ? data : [data] const input = Array.isArray(paymentCollectionId)
? paymentCollectionId.map((id) => ({
input = input.map((inputData) => ({ id,
payment_collection: inputData.payment_collection_id, completed_at: new Date(),
payment_session: inputData.payment_session_id,
...inputData,
})) }))
: [{ id: paymentCollectionId, completed_at: new Date() }]
const payments = await this.paymentService_.create(input, sharedContext) // TODO: what checks should be done here? e.g. captured_amount === amount?
return await this.baseRepository_.serialize<PaymentDTO[]>( const updated = await this.paymentCollectionService_.update(
Array.isArray(data) ? payments : payments[0], input,
{ sharedContext
populate: true,
}
) )
}
updatePayment(
data: UpdatePaymentDTO,
sharedContext?: Context | undefined
): Promise<PaymentDTO>
updatePayment(
data: UpdatePaymentDTO[],
sharedContext?: Context | undefined
): Promise<PaymentDTO[]>
@InjectTransactionManager("baseRepository_")
async updatePayment(
data: UpdatePaymentDTO | UpdatePaymentDTO[],
@MedusaContext() sharedContext?: Context
): Promise<PaymentDTO | PaymentDTO[]> {
const input = Array.isArray(data) ? data : [data]
const result = await this.paymentService_.update(input, sharedContext)
return await this.baseRepository_.serialize<PaymentDTO[]>(
Array.isArray(data) ? result : result[0],
{
populate: true,
}
)
}
capturePayment(
data: CreateCaptureDTO,
sharedContext?: Context
): Promise<PaymentDTO>
capturePayment(
data: CreateCaptureDTO[],
sharedContext?: Context
): Promise<PaymentDTO[]>
@InjectManager("baseRepository_")
async capturePayment(
data: CreateCaptureDTO | CreateCaptureDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<PaymentDTO | PaymentDTO[]> {
const input = Array.isArray(data) ? data : [data]
const payments = await this.capturePaymentBulk_(input, sharedContext)
return await this.baseRepository_.serialize( return await this.baseRepository_.serialize(
Array.isArray(data) ? payments : payments[0], Array.isArray(paymentCollectionId) ? updated : updated[0],
{ populate: true } { populate: true }
) )
} }
@InjectTransactionManager("baseRepository_") @InjectTransactionManager("baseRepository_")
protected async capturePaymentBulk_( async createPaymentSession(
data: CreateCaptureDTO[], paymentCollectionId: string,
data: CreatePaymentSessionDTO,
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
): Promise<Payment[]> { ): Promise<PaymentSessionDTO> {
let payments = await this.paymentService_.list( const sessionData = await this.paymentProviderService_.createSession(
{ id: data.map((d) => d.payment_id) }, data.provider_id,
data.providerContext
)
const created = await this.paymentSessionService_.create(
{
provider_id: data.provider_id,
amount: data.providerContext.amount,
currency_code: data.providerContext.currency_code,
payment_collection: paymentCollectionId,
data: sessionData,
},
sharedContext
)
return await this.baseRepository_.serialize(created, { populate: true })
}
@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 sessionData = await this.paymentProviderService_.updateSession(
session.provider_id,
data.providerContext
)
const updated = await this.paymentSessionService_.update(
{
id: session.id,
amount: data.providerContext.amount,
currency_code: data.providerContext.currency_code,
data: sessionData,
},
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"],
relations: ["payment_collection"],
},
sharedContext
)
if (session.authorized_at) {
const payment = await this.paymentService_.retrieve(
{ session_id: session.id },
{}, {},
sharedContext sharedContext
) )
const inputMap = new Map(data.map((d) => [d.payment_id, d])) return await this.baseRepository_.serialize(payment, { populate: true })
}
for (const payment of payments) { const { data, status } =
const input = inputMap.get(payment.id)! 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,
authorized_amount: session.amount,
payment_session: session.id,
payment_collection: session.payment_collection!.id,
provider_id: session.provider_id,
// customer_id: context.customer.id,
data,
},
sharedContext
)
return await this.retrievePayment(payment.id, {}, 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"] },
sharedContext
)
if (payment.canceled_at) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`The payment: ${payment.id} has been canceled.`
)
}
if (payment.captured_at) { if (payment.captured_at) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.INVALID_DATA, MedusaError.Types.INVALID_DATA,
"The payment is already fully captured." `The payment: ${payment.id} is already fully captured.`
) )
} }
@@ -270,195 +388,139 @@ export default class PaymentModuleService<
// `Total captured amount for payment: ${payment.id} exceeds authorized amount.` // `Total captured amount for payment: ${payment.id} exceeds authorized amount.`
// ) // )
// } // }
}
const paymentData = await this.paymentProviderService_.capturePayment({
data: payment.data!,
provider_id: payment.provider_id,
})
await this.captureService_.create( await this.captureService_.create(
data.map((d) => ({
payment: d.payment_id,
amount: d.amount,
captured_by: d.captured_by,
})),
sharedContext
)
let fullyCapturedPaymentsId: string[] = []
for (const payment of payments) {
const input = inputMap.get(payment.id)!
// TODO: revisit when https://github.com/medusajs/medusa/pull/6253 is merged
// if (payment.captured_amount + input.amount === payment.amount) {
// fullyCapturedPaymentsId.push(payment.id)
// }
}
if (fullyCapturedPaymentsId.length) {
await this.paymentService_.update(
fullyCapturedPaymentsId.map((id) => ({ id, captured_at: new Date() })),
sharedContext
)
}
// TODO: set PaymentCollection status if fully captured
return await this.paymentService_.list(
{ id: data.map((d) => d.payment_id) },
{ {
relations: ["captures"], payment: data.payment_id,
amount: data.amount,
captured_by: data.captured_by,
}, },
sharedContext sharedContext
) )
}
refundPayment( await this.paymentService_.update(
data: CreateRefundDTO, { id: payment.id, data: paymentData },
sharedContext?: Context sharedContext
): Promise<PaymentDTO> )
refundPayment(
data: CreateRefundDTO[],
sharedContext?: Context
): Promise<PaymentDTO[]>
@InjectManager("baseRepository_") // TODO: revisit when https://github.com/medusajs/medusa/pull/6253 is merged
async refundPayment( // if (payment.captured_amount + data.amount === payment.amount) {
data: CreateRefundDTO | CreateRefundDTO[], // await this.paymentService_.update(
@MedusaContext() sharedContext?: Context // { id: payment.id, captured_at: new Date() },
): Promise<PaymentDTO | PaymentDTO[]> { // sharedContext
const input = Array.isArray(data) ? data : [data] // )
// }
const payments = await this.refundPaymentBulk_(input, sharedContext) return await this.retrievePayment(
payment.id,
return await this.baseRepository_.serialize( { relations: ["captures"] },
Array.isArray(data) ? payments : payments[0], sharedContext
{ populate: true }
) )
} }
@InjectTransactionManager("baseRepository_") @InjectTransactionManager("baseRepository_")
async refundPaymentBulk_( async refundPayment(
data: CreateRefundDTO[], data: CreateRefundDTO,
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
): Promise<Payment[]> { ): Promise<PaymentDTO> {
const payments = await this.paymentService_.list( const payment = await this.paymentService_.retrieve(
{ id: data.map(({ payment_id }) => payment_id) }, data.payment_id,
{}, { select: ["id", "data", "provider_id"] },
sharedContext sharedContext
) )
const inputMap = new Map(data.map((d) => [d.payment_id, d]))
// TODO: revisit when https://github.com/medusajs/medusa/pull/6253 is merged // TODO: revisit when https://github.com/medusajs/medusa/pull/6253 is merged
// for (const payment of payments) {
// const input = inputMap.get(payment.id)!
// if (payment.captured_amount < input.amount) { // if (payment.captured_amount < input.amount) {
// throw new MedusaError( // throw new MedusaError(
// MedusaError.Types.INVALID_DATA, // MedusaError.Types.INVALID_DATA,
// `Refund amount for payment: ${payment.id} cannot be greater than the amount captured on the payment.` // `Refund amount for payment: ${payment.id} cannot be greater than the amount captured on the payment.`
// ) // )
// } // }
// }
const paymentData = await this.paymentProviderService_.refundPayment(
{
data: payment.data!,
provider_id: payment.provider_id,
},
data.amount
)
await this.refundService_.create( await this.refundService_.create(
data.map((d) => ({
payment: d.payment_id,
amount: d.amount,
captured_by: d.created_by,
})),
sharedContext
)
return await this.paymentService_.list(
{ id: data.map(({ payment_id }) => payment_id) },
{ {
relations: ["refunds"], payment: data.payment_id,
amount: data.amount,
created_by: data.created_by,
}, },
sharedContext sharedContext
) )
}
createPaymentSession( await this.paymentService_.update({ id: payment.id, data: paymentData })
paymentCollectionId: string,
data: CreatePaymentSessionDTO, return await this.retrievePayment(
sharedContext?: Context | undefined payment.id,
): Promise<PaymentCollectionDTO> { relations: ["refunds"] },
createPaymentSession( sharedContext
paymentCollectionId: string, )
data: CreatePaymentSessionDTO[], }
sharedContext?: Context | undefined
): Promise<PaymentCollectionDTO>
@InjectTransactionManager("baseRepository_") @InjectTransactionManager("baseRepository_")
async createPaymentSession( async cancelPayment(
paymentCollectionId: string, paymentId: string,
data: CreatePaymentSessionDTO | CreatePaymentSessionDTO[],
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
): Promise<PaymentCollectionDTO> { ): Promise<PaymentDTO> {
let input = Array.isArray(data) ? data : [data] const payment = await this.paymentService_.retrieve(
paymentId,
input = input.map((inputData) => ({ { select: ["id", "data", "provider_id"] },
payment_collection: paymentCollectionId,
...inputData,
}))
await this.paymentSessionService_.create(input, sharedContext)
return await this.retrievePaymentCollection(
paymentCollectionId,
{
relations: ["payment_sessions"],
},
sharedContext 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)
} }
/** async createProvidersOnLoad() {
* TODO const providersToLoad = this.__container__["payment_providers"]
*/
authorizePaymentCollection( const providers = await this.paymentProviderService_.list({
paymentCollectionId: string, // @ts-ignore TODO
sharedContext?: Context | undefined id: providersToLoad.map((p) => p.getIdentifier()),
): Promise<PaymentCollectionDTO> { })
throw new Error("Method not implemented.")
} const loadedProvidersMap = new Map(providers.map((p) => [p.id, p]))
completePaymentCollection(
paymentCollectionId: string, const providersToCreate: CreatePaymentProviderDTO[] = []
sharedContext?: Context | undefined for (const provider of providersToLoad) {
): Promise<PaymentCollectionDTO> { if (loadedProvidersMap.has(provider.getIdentifier())) {
throw new Error("Method not implemented.") continue
} }
cancelPayment(paymentId: string, sharedContext?: Context): Promise<PaymentDTO> providersToCreate.push({
cancelPayment( id: provider.getIdentifier(),
paymentId: string[], })
sharedContext?: Context
): Promise<PaymentDTO[]>
cancelPayment(
paymentId: string | string[],
sharedContext?: Context
): Promise<PaymentDTO | PaymentDTO[]> {
throw new Error("Method not implemented.")
} }
authorizePaymentSessions( await this.paymentProviderService_.create(providersToCreate)
paymentCollectionId: string,
sessionIds: string[],
sharedContext?: Context | undefined
): Promise<PaymentCollectionDTO> {
throw new Error("Method not implemented.")
}
completePaymentSessions(
paymentCollectionId: string,
sessionIds: string[],
sharedContext?: Context | undefined
): Promise<PaymentCollectionDTO> {
throw new Error("Method not implemented.")
}
setPaymentSessions(
paymentCollectionId: string,
data: SetPaymentSessionsDTO[],
sharedContext?: Context | undefined
): Promise<PaymentCollectionDTO> {
throw new Error("Method not implemented.")
} }
} }
@@ -0,0 +1,183 @@
import { EOL } from "os"
import { isDefined, MedusaError } from "medusa-core-utils"
import {
Context,
CreatePaymentProviderDTO,
DAL,
InternalModuleDeclaration,
IPaymentProvider,
PaymentProviderAuthorizeResponse,
PaymentProviderContext,
PaymentProviderDataInput,
PaymentProviderError,
PaymentProviderSessionResponse,
PaymentSessionStatus,
} from "@medusajs/types"
import {
InjectManager,
InjectTransactionManager,
isPaymentProviderError,
MedusaContext,
} from "@medusajs/utils"
import { PaymentProvider } from "@models"
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(
@MedusaContext() sharedContext?: Context
): Promise<PaymentProvider[]> {
return await this.paymentProviderRepository_.find(undefined, sharedContext)
}
retrieveProvider(providerId: string): IPaymentProvider {
try {
return this.container_[`pp_${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: PaymentProviderContext
): Promise<PaymentProviderSessionResponse["data"]> {
const provider = this.retrieveProvider(providerId)
if (
!isDefined(sessionInput.currency_code) ||
!isDefined(sessionInput.amount)
) {
throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
"`currency_code` and `amount` are required to create payment session."
)
}
const paymentResponse = await provider.initiatePayment(sessionInput)
if (isPaymentProviderError(paymentResponse)) {
this.throwPaymentProviderError(paymentResponse)
}
return (paymentResponse as PaymentProviderSessionResponse).data
}
async updateSession(
providerId: string,
sessionInput: PaymentProviderContext
): 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>
}
private throwPaymentProviderError(errObj: PaymentProviderError) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`${errObj.error}${errObj.detail ? `:${EOL}${errObj.detail}` : ""}`,
errObj.code
)
}
}
+70
View File
@@ -31,6 +31,34 @@ export enum PaymentCollectionStatus {
CANCELED = "canceled", CANCELED = "canceled",
} }
/**
* @enum
*
* The status of a payment session.
*/
export enum PaymentSessionStatus {
/**
* The payment is authorized.
*/
AUTHORIZED = "authorized",
/**
* The payment is pending.
*/
PENDING = "pending",
/**
* The payment requires an action.
*/
REQUIRES_MORE = "requires_more",
/**
* An error occurred while processing the payment.
*/
ERROR = "error",
/**
* The payment is canceled.
*/
CANCELED = "canceled",
}
export interface PaymentCollectionDTO { export interface PaymentCollectionDTO {
/** /**
* The ID of the Payment Collection * The ID of the Payment Collection
@@ -255,6 +283,48 @@ export interface PaymentSessionDTO {
* The ID of the Payment Session * The ID of the Payment Session
*/ */
id: string id: string
/**
* The amount
*/
amount: number
/**
* Payment session currency
*/
currency_code: string
/**
* The ID of payment provider
*/
provider_id: string
/**
* Payment provider data
*/
data: Record<string, unknown>
/**
* The status of the payment session
*/
status: PaymentSessionStatus
/**
* When the session was authorized
*/
authorized_at?: Date
/**
* The payment collection the session is associated with
* @expandable
*/
payment_collection?: PaymentCollectionDTO
/**
* The payment created from the session
* @expandable
*/
payment?: PaymentDTO
} }
export interface PaymentProviderDTO { export interface PaymentProviderDTO {
+1 -1
View File
@@ -1,4 +1,4 @@
export * from "./common" export * from "./common"
export * from "./mutations" export * from "./mutations"
export * from "./provider"
export * from "./service" export * from "./service"
+14 -12
View File
@@ -1,4 +1,5 @@
import { PaymentCollectionStatus } from "./common" import { PaymentCollectionStatus } from "./common"
import { PaymentProviderContext } from "./provider"
/** /**
* Payment Collection * Payment Collection
@@ -26,6 +27,7 @@ export interface UpdatePaymentCollectionDTO
export interface CreatePaymentDTO { export interface CreatePaymentDTO {
amount: number amount: number
currency_code: string currency_code: string
provider_id: string provider_id: string
data: Record<string, unknown> data: Record<string, unknown>
@@ -46,8 +48,6 @@ export interface UpdatePaymentDTO {
order_id?: string order_id?: string
order_edit_id?: string order_edit_id?: string
customer_id?: string customer_id?: string
data?: Record<string, unknown>
} }
export interface CreateCaptureDTO { export interface CreateCaptureDTO {
@@ -69,17 +69,19 @@ export interface CreateRefundDTO {
*/ */
export interface CreatePaymentSessionDTO { export interface CreatePaymentSessionDTO {
amount: number
currency_code: string
provider_id: string provider_id: string
providerContext: PaymentProviderContext
cart_id?: string
resource_id?: string
customer_id?: string
} }
export interface SetPaymentSessionsDTO { export interface UpdatePaymentSessionDTO {
provider_id: string id: string
amount: number providerContext: PaymentProviderContext
session_id?: string }
/**
* Payment Provider
*/
export interface CreatePaymentProviderDTO {
id: string
is_enabled?: boolean
} }
+212
View File
@@ -0,0 +1,212 @@
import { PaymentSessionStatus } from "./common"
/**
* @interface
*
* A payment's context.
*/
export type PaymentProviderContext = {
/**
* The payment's billing address.
*/
billing_address?: Record<string, unknown> | null // TODO: revisit types
/**
* The customer's email.
*/
email?: string
/**
* The selected currency code.
*/
currency_code: string
/**
* The payment's amount.
*/
amount: number
/**
* The ID of the resource the payment is associated with. For example, the cart's ID.
*/
resource_id: string
/**
* The customer associated with this payment.
*/
customer?: Record<string, unknown> // TODO: type
/**
* The context.
*/
context: Record<string, unknown>
/**
* If the payment session hasn't been created or initiated yet, it'll be an empty object.
* If the payment session exists, it'll be the value of the payment session's `data` field.
*/
payment_session_data: Record<string, unknown>
}
/**
* @interface
*
* The response of operations on a payment.
*/
export type PaymentProviderSessionResponse = {
/**
* The data to be stored in the `data` field of the Payment Session to be created.
* The `data` field is useful to hold any data required by the third-party provider to process the payment or retrieve its details at a later point.
*/
data: Record<string, unknown>
}
export type PaymentProviderAuthorizeResponse = {
/**
* The status of the payment, which will be stored in the payment session's `status` field.
*/
status: PaymentSessionStatus
/**
* The `data` to be stored in the payment session's `data` field.
*/
data: PaymentProviderSessionResponse["data"]
}
export type PaymentProviderDataInput = {
provider_id: string
data: Record<string, unknown>
}
/**
* An object that is returned in case of an error.
*/
export interface PaymentProviderError {
/**
* The error message
*/
error: string
/**
* The error code.
*/
code?: string
/**
* Any additional helpful details.
*/
detail?: any
}
export interface IPaymentProvider {
/**
* @ignore
*
* Return a unique identifier to retrieve the payment plugin provider
*/
getIdentifier(): string
/**
* Make calls to the third-party provider to initialize the payment. For example, in Stripe this method is used to create a Payment Intent for the customer.
*
* @param {PaymentProviderContext} context - The context of the payment.
* @returns {Promise<PaymentProviderError | PaymentProviderSessionResponse>} Either the payment's data or an error object.
*/
initiatePayment(
context: PaymentProviderContext
): Promise<PaymentProviderError | PaymentProviderSessionResponse>
/**
* This method is used to update the payment session.
*
* @param {PaymentProviderContext} context - The context of the payment.
* @returns {Promise<PaymentProviderError | PaymentProviderSessionResponse | void>} Either the payment's data or an error object.
*/
updatePayment(
context: PaymentProviderContext
): Promise<PaymentProviderError | PaymentProviderSessionResponse>
/**
* This method is used to perform any actions necessary before a Payment Session is deleted. The Payment Session is deleted in one of the following cases:
*
* @param {Record<string, unknown>} paymentSessionData - The `data` field of the Payment Session.
* @returns Either an error object or an empty object.
*/
deletePayment(
paymentSessionData: Record<string, unknown>
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
/**
* This method is used to authorize payment using the Payment Session.
* You can interact with a third-party provider and perform any actions necessary to authorize the payment.
*
* The payment authorization might require additional action from the customer before it is declared authorized. Once that additional action is performed,
* the `authorizePayment` method will be called again to validate that the payment is now fully authorized. So, make sure to implement it for this case as well, if necessary.
*
* :::note
*
* The payment authorization status is determined using the {@link getPaymentStatus} method. If the status is `requires_more`, then it means additional actions are required
* from the customer.
*
* :::
*
* @param {Record<string, unknown>} paymentSessionData - The `data` field of the payment session.
* @param {Record<string, unknown>} context - The context of the authorization.
* @returns The authorization details or an error object.
*/
authorizePayment(
paymentSessionData: Record<string, unknown>,
context: Record<string, unknown>
): Promise<PaymentProviderError | PaymentProviderAuthorizeResponse>
/**
* This method is used to capture the payment amount. This is typically triggered manually by the store operator from the admin.
*
* You can utilize this method to interact with the third-party provider and perform any actions necessary to capture the payment.
*
* @param {Record<string, unknown>} paymentSessionData - The `data` field of the Payment for its first parameter.
* @returns Either an error object or a value that's stored in the `data` field of the Payment.
*/
capturePayment(
paymentSessionData: Record<string, unknown>
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
/**
* This method is used to refund a payment. This is typically triggered manually by the store operator from the admin. The refund amount might be the total amount or part of it.
*
* You can utilize this method to interact with the third-party provider and perform any actions necessary to refund the payment.
*
* @param {Record<string, unknown>} paymentSessionData - The `data` field of a Payment.
* @param {number} refundAmount - the amount to refund.
* @returns Either an error object or a value that's stored in the `data` field of the Payment.
*/
refundPayment(
paymentSessionData: Record<string, unknown>,
refundAmount: number
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
/**
* This method is used to provide a uniform way of retrieving the payment information from the third-party provider.
* For example, in Stripe’s Payment Provider this method is used to retrieve the payment intent details from Stripe.
*
* @param {Record<string, unknown>} paymentSessionData -
* The `data` field of a Payment Session. Make sure to store in the `data` field any necessary data that would allow you to retrieve the payment data from the third-party provider.
* @returns {Promise<PaymentProviderError | PaymentProviderSessionResponse["session_data"]>} The payment's data, typically retrieved from a third-party provider.
*/
retrievePayment(
paymentSessionData: Record<string, unknown>
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
/**
* This method is used to cancel a payment. This method is typically triggered by one of the following situations:
*
* You can utilize this method to interact with the third-party provider and perform any actions necessary to cancel the payment.
*
* @param {Record<string, unknown>} paymentSessionData - The `data` field of the Payment.
* @returns Either an error object or a value that's stored in the `data` field of the Payment.
*/
cancelPayment(
paymentSessionData: Record<string, unknown>
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
/**
* This method is used to get the status of a Payment or a Payment Session.
*
* @param {Record<string, unknown>} paymentSessionData -
* The `data` field of a Payment as a parameter. You can use this data to interact with the third-party provider to check the status of the payment if necessary.
* @returns {Promise<PaymentSessionStatus>} The status of the Payment or Payment Session.
*/
getPaymentStatus(
paymentSessionData: Record<string, unknown>
): Promise<PaymentSessionStatus>
}
+40 -73
View File
@@ -6,25 +6,26 @@ import {
CreatePaymentDTO, CreatePaymentDTO,
CreatePaymentSessionDTO, CreatePaymentSessionDTO,
CreateRefundDTO, CreateRefundDTO,
SetPaymentSessionsDTO,
UpdatePaymentCollectionDTO, UpdatePaymentCollectionDTO,
UpdatePaymentDTO, UpdatePaymentDTO,
UpdatePaymentSessionDTO,
} from "./mutations" } from "./mutations"
import { import {
FilterablePaymentCollectionProps, FilterablePaymentCollectionProps,
PaymentCollectionDTO, PaymentCollectionDTO,
PaymentDTO, PaymentDTO,
PaymentSessionDTO,
} from "./common" } from "./common"
import { FindConfig } from "../common" import { FindConfig } from "../common"
export interface IPaymentModuleService extends IModuleService { export interface IPaymentModuleService extends IModuleService {
/* ********** PAYMENT COLLECTION ********** */ /* ********** PAYMENT COLLECTION ********** */
createPaymentCollection( createPaymentCollections(
data: CreatePaymentCollectionDTO[], data: CreatePaymentCollectionDTO[],
sharedContext?: Context sharedContext?: Context
): Promise<PaymentCollectionDTO[]> ): Promise<PaymentCollectionDTO[]>
createPaymentCollection( createPaymentCollections(
data: CreatePaymentCollectionDTO, data: CreatePaymentCollectionDTO,
sharedContext?: Context sharedContext?: Context
): Promise<PaymentCollectionDTO> ): Promise<PaymentCollectionDTO>
@@ -47,11 +48,11 @@ export interface IPaymentModuleService extends IModuleService {
sharedContext?: Context sharedContext?: Context
): Promise<[PaymentCollectionDTO[], number]> ): Promise<[PaymentCollectionDTO[], number]>
updatePaymentCollection( updatePaymentCollections(
data: UpdatePaymentCollectionDTO[], data: UpdatePaymentCollectionDTO[],
sharedContext?: Context sharedContext?: Context
): Promise<PaymentCollectionDTO[]> ): Promise<PaymentCollectionDTO[]>
updatePaymentCollection( updatePaymentCollections(
data: UpdatePaymentCollectionDTO, data: UpdatePaymentCollectionDTO,
sharedContext?: Context sharedContext?: Context
): Promise<PaymentCollectionDTO> ): Promise<PaymentCollectionDTO>
@@ -65,59 +66,14 @@ export interface IPaymentModuleService extends IModuleService {
sharedContext?: Context sharedContext?: Context
): Promise<void> ): Promise<void>
authorizePaymentCollection( completePaymentCollections(
paymentCollectionId: string, paymentCollectionId: string,
sharedContext?: Context sharedContext?: Context
): Promise<PaymentCollectionDTO> ): Promise<PaymentCollectionDTO>
completePaymentCollections(
completePaymentCollection( paymentCollectionId: string[],
paymentCollectionId: string,
sharedContext?: Context sharedContext?: Context
): Promise<PaymentCollectionDTO> ): Promise<PaymentCollectionDTO[]>
/* ********** PAYMENT ********** */
createPayment(
data: CreatePaymentDTO,
sharedContext?: Context
): Promise<PaymentDTO>
createPayment(
data: CreatePaymentDTO[],
sharedContext?: Context
): Promise<PaymentDTO[]>
capturePayment(
data: CreateCaptureDTO,
sharedContext?: Context
): Promise<PaymentDTO>
capturePayment(
data: CreateCaptureDTO[],
sharedContext?: Context
): Promise<PaymentDTO[]>
refundPayment(
data: CreateRefundDTO,
sharedContext?: Context
): Promise<PaymentDTO>
refundPayment(
data: CreateRefundDTO[],
sharedContext?: Context
): Promise<PaymentDTO[]>
cancelPayment(paymentId: string, sharedContext?: Context): Promise<PaymentDTO>
cancelPayment(
paymentId: string[],
sharedContext?: Context
): Promise<PaymentDTO[]>
updatePayment(
data: UpdatePaymentDTO,
sharedContext?: Context
): Promise<PaymentDTO>
updatePayment(
data: UpdatePaymentDTO[],
sharedContext?: Context
): Promise<PaymentDTO[]>
/* ********** PAYMENT SESSION ********** */ /* ********** PAYMENT SESSION ********** */
@@ -125,28 +81,39 @@ export interface IPaymentModuleService extends IModuleService {
paymentCollectionId: string, paymentCollectionId: string,
data: CreatePaymentSessionDTO, data: CreatePaymentSessionDTO,
sharedContext?: Context sharedContext?: Context
): Promise<PaymentCollectionDTO> ): Promise<PaymentSessionDTO>
createPaymentSession(
paymentCollectionId: string,
data: CreatePaymentSessionDTO[],
sharedContext?: Context
): Promise<PaymentCollectionDTO>
authorizePaymentSessions( updatePaymentSession(
paymentCollectionId: string, data: UpdatePaymentSessionDTO,
sessionIds: string[],
sharedContext?: Context sharedContext?: Context
): Promise<PaymentCollectionDTO> ): Promise<PaymentSessionDTO>
completePaymentSessions( deletePaymentSession(id: string, sharedContext?: Context): Promise<void>
paymentCollectionId: string,
sessionIds: string[],
sharedContext?: Context
): Promise<PaymentCollectionDTO>
setPaymentSessions( authorizePaymentSession(
paymentCollectionId: string, id: string,
data: SetPaymentSessionsDTO[], context: Record<string, unknown>,
sharedContext?: Context sharedContext?: Context
): Promise<PaymentCollectionDTO> ): Promise<PaymentDTO>
/* ********** PAYMENT ********** */
updatePayment(
data: UpdatePaymentDTO,
sharedContext?: Context
): Promise<PaymentDTO>
capturePayment(
data: CreateCaptureDTO,
sharedContext?: Context
): Promise<PaymentDTO>
refundPayment(
data: CreateRefundDTO,
sharedContext?: Context
): Promise<PaymentDTO>
cancelPayment(paymentId: string, sharedContext?: Context): Promise<PaymentDTO>
createProvidersOnLoad(): Promise<void>
} }
@@ -0,0 +1,127 @@
import {
IPaymentProvider,
MedusaContainer,
PaymentProviderContext,
PaymentProviderError,
PaymentProviderSessionResponse,
PaymentSessionStatus,
} from "@medusajs/types"
export abstract class AbstractPaymentProvider implements IPaymentProvider {
/**
* You can use the `constructor` of your Payment Provider to have access to different services in Medusa through [dependency injection](https://docs.medusajs.com/development/fundamentals/dependency-injection).
*
* You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party provider’s APIs,
* you can initialize it in the constructor and use it in other methods in the service.
*
* Additionally, if you’re creating your Payment Provider as an external plugin to be installed on any Medusa backend and you want to access the options added for the plugin,
* you can access it in the constructor. The options are passed as a second parameter.
*
* @param {MedusaContainer} container - An instance of `MedusaContainer` that allows you to access other resources, such as services, in your Medusa backend through [dependency injection](https://docs.medusajs.com/development/fundamentals/dependency-injection)
* @param {Record<string, unknown>} config - If this fulfillment provider is created in a plugin, the plugin's options are passed in this parameter.
*
* @example
* ```ts
* class MyPaymentService extends AbstractPaymentProvider {
* // ...
* constructor(container, options) {
* super(container)
* // you can access options here
*
* // you can also initialize a client that
* // communicates with a third-party service.
* this.client = new Client(options)
* }
* // ...
* }
* ```
*/
protected constructor(
protected readonly container: MedusaContainer,
protected readonly config?: Record<string, unknown> // eslint-disable-next-line @typescript-eslint/no-empty-function
) {}
static _isPaymentProvider = true
static isPaymentProvider(object): boolean {
return object?.constructor?._isPaymentProvider
}
/**
* The `PaymentProvider` entity has 2 properties: `id` and `is_installed`. The `identifier` property in the payment provider service is used when the payment provider is added to the database.
*
* The value of this property is also used to reference the payment provider throughout Medusa.
* For example, it is used to [add a payment provider](https://docs.medusajs.com/api/admin#regions_postregionsregionpaymentproviders) to a region.
*
* ```ts
* class MyPaymentService extends AbstractPaymentProvider {
* static identifier = "my-payment"
* // ...
* }
* ```
*/
public static identifier: string
/**
* @ignore
*
* Return a unique identifier to retrieve the payment plugin provider
*/
public getIdentifier(): string {
const ctr = this.constructor as typeof AbstractPaymentProvider
if (!ctr.identifier) {
throw new Error(`Missing static property "identifier".`)
}
return ctr.identifier
}
abstract capturePayment(
paymentSessionData: Record<string, unknown>
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
abstract authorizePayment(
paymentSessionData: Record<string, unknown>,
context: Record<string, unknown>
): Promise<
| PaymentProviderError
| {
status: PaymentSessionStatus
data: PaymentProviderSessionResponse["data"]
}
>
abstract cancelPayment(
paymentSessionData: Record<string, unknown>
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
abstract initiatePayment(
context: PaymentProviderContext
): Promise<PaymentProviderError | PaymentProviderSessionResponse>
abstract deletePayment(
paymentSessionData: Record<string, unknown>
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
abstract getPaymentStatus(
paymentSessionData: Record<string, unknown>
): Promise<PaymentSessionStatus>
abstract refundPayment(
paymentSessionData: Record<string, unknown>,
refundAmount: number
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
abstract retrievePayment(
paymentSessionData: Record<string, unknown>
): Promise<PaymentProviderError | PaymentProviderSessionResponse["data"]>
abstract updatePayment(
context: PaymentProviderContext
): Promise<PaymentProviderError | PaymentProviderSessionResponse>
}
export function isPaymentProviderError(obj: any): obj is PaymentProviderError {
return obj && typeof obj === "object" && obj.error && obj.code && obj.detail
}
+1
View File
@@ -1,2 +1,3 @@
export * from "./payment-collection" export * from "./payment-collection"
export * from "./payment-session" export * from "./payment-session"
export * from "./abstract-payment-provider"