feat(payment): provider service (#6308)
This commit is contained in:
1 parent
869dc751a0
commit
a6a4b3f01a
21 files changed
+1439
-565
No files matched your search
@@ -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: {},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
+368
-219
@@ -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."
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -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,4 +1,4 @@
|
|||||||
export * from "./connection"
|
export * from "./connection"
|
||||||
export * from "./container"
|
export * from "./container"
|
||||||
export * from "./providers"
|
export * from "./providers"
|
||||||
|
export * from "./defaults"
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as SystemPaymentProvider } from "./system"
|
||||||
@@ -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 +1,2 @@
|
|||||||
export { default as PaymentModuleService } from "./payment-module"
|
export { default as PaymentModuleService } from "./payment-module"
|
||||||
|
export { default as PaymentProviderService } from "./payment-provider"
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,4 +1,4 @@
|
|||||||
export * from "./common"
|
export * from "./common"
|
||||||
export * from "./mutations"
|
export * from "./mutations"
|
||||||
|
export * from "./provider"
|
||||||
export * from "./service"
|
export * from "./service"
|
||||||
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
}
|
||||||
@@ -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,2 +1,3 @@
|
|||||||
export * from "./payment-collection"
|
export * from "./payment-collection"
|
||||||
export * from "./payment-session"
|
export * from "./payment-session"
|
||||||
|
export * from "./abstract-payment-provider"
|
||||||
Reference in new issue
Block a user