chore(): Reorganize modules (#7210)
**What** Move all modules to the modules directory
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
/dist
|
||||
node_modules
|
||||
.DS_store
|
||||
.env*
|
||||
.env
|
||||
*.sql
|
||||
@@ -0,0 +1,23 @@
|
||||
# @medusajs/payment
|
||||
|
||||
## 0.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#6864](https://github.com/medusajs/medusa/pull/6864) [`e944a627f0`](https://github.com/medusajs/medusa/commit/e944a627f074fb39a56f4bc7b3d6d315736ebf7c) Thanks [@adrien2p](https://github.com/adrien2p)! - feat: region payment providers management workflows/api
|
||||
|
||||
- Updated dependencies [[`0c0b425de7`](https://github.com/medusajs/medusa/commit/0c0b425de7b154b80b712ab17b16215cf62d1e83), [`8d356217bd`](https://github.com/medusajs/medusa/commit/8d356217bd31c97a196e861ee243822a4d924df7), [`1eeb1e9de3`](https://github.com/medusajs/medusa/commit/1eeb1e9de3e0b571735437b00968ee96e4aabad5), [`20e8df914e`](https://github.com/medusajs/medusa/commit/20e8df914ec5fdf8d562d4fa84f72c58c7056195), [`27f4f0d724`](https://github.com/medusajs/medusa/commit/27f4f0d7243367c2dfc6012bf1f6b7400a77ec7b), [`e0b02a1012`](https://github.com/medusajs/medusa/commit/e0b02a1012981c29830d7779f59ebe805bbfd137), [`e944a627f0`](https://github.com/medusajs/medusa/commit/e944a627f074fb39a56f4bc7b3d6d315736ebf7c), [`1a48fe0282`](https://github.com/medusajs/medusa/commit/1a48fe0282a8bc1f8548a4736255e457d173da09), [`86f499de2f`](https://github.com/medusajs/medusa/commit/86f499de2f31356ab36ad5e93f27345443b3e5f6), [`09a2220569`](https://github.com/medusajs/medusa/commit/09a22205693da62fbf8fd450535d5024cb9c01d1), [`78f603e4f1`](https://github.com/medusajs/medusa/commit/78f603e4f18c9d16f4b58a2189c959026453d8b2), [`cc557c8752`](https://github.com/medusajs/medusa/commit/cc557c8752fd0554f5a1b58522d9a88dc43a8509), [`dd35a4dbff`](https://github.com/medusajs/medusa/commit/dd35a4dbff10c86ea3c5f7f817c18b6e60d599e3), [`58c68f6715`](https://github.com/medusajs/medusa/commit/58c68f67156e993255fbc25d91db15ae23bc95c0), [`1bcb13f892`](https://github.com/medusajs/medusa/commit/1bcb13f892bc61db21b3fc6bdbce85f747aeec4c), [`82a176e30e`](https://github.com/medusajs/medusa/commit/82a176e30e47a7d11caaf31c3023bd8db588b465), [`11517f0faf`](https://github.com/medusajs/medusa/commit/11517f0fafdf00af256240448b58d149d8b6f600), [`62b9dcc6c1`](https://github.com/medusajs/medusa/commit/62b9dcc6c1ce46aadb7944215006c12da3c9f619), [`5d9aea053c`](https://github.com/medusajs/medusa/commit/5d9aea053ce6e04f242f86fb9053c13dec515d5b), [`e26cda4b6a`](https://github.com/medusajs/medusa/commit/e26cda4b6afb7fb25f0b0a7a7ce20b7f914d35db), [`bc06ad2db4`](https://github.com/medusajs/medusa/commit/bc06ad2db48c999023ab823fefc1375196976e9b), [`18f3aacee6`](https://github.com/medusajs/medusa/commit/18f3aacee6752854d377faa806f4cc67bc71456b), [`232322d035`](https://github.com/medusajs/medusa/commit/232322d03515f81e56867ff8c765b8409399ee68), [`38c971f111`](https://github.com/medusajs/medusa/commit/38c971f111af69f176e7e9892eb59f5bae831fa7), [`45c49e89f2`](https://github.com/medusajs/medusa/commit/45c49e89f28123ef622fc1c07253bae94fd74875), [`528ef4ca90`](https://github.com/medusajs/medusa/commit/528ef4ca90bb2cf6173dccc9fd6a9f9932ff9b76), [`65794f4bb5`](https://github.com/medusajs/medusa/commit/65794f4bb56e4fd3f0ccb7656a948f856f05324e), [`93ef94cad3`](https://github.com/medusajs/medusa/commit/93ef94cad3ddc5b6973b4e48e422b0aa0e6ddbbe), [`4cf71af07d`](https://github.com/medusajs/medusa/commit/4cf71af07d1807c83df3889c1774f82cbd1b9a6f), [`4b57c5d286`](https://github.com/medusajs/medusa/commit/4b57c5d286f9dc6e2098c67e9fecb0d93175b5a1), [`c78915c7c5`](https://github.com/medusajs/medusa/commit/c78915c7c5e91a99c1b1bae932656c8d86b17daf), [`18f3aacee6`](https://github.com/medusajs/medusa/commit/18f3aacee6752854d377faa806f4cc67bc71456b), [`667c8609cc`](https://github.com/medusajs/medusa/commit/667c8609ccf3850f5df8cf784723a95bd0d6d2a6), [`f175cac4af`](https://github.com/medusajs/medusa/commit/f175cac4af63b71066a8398ecf9beaa6f28b20cc), [`0a9b9b073d`](https://github.com/medusajs/medusa/commit/0a9b9b073dd2d3f4aa5e5cb1c16e2221a7200e0d), [`a6562d2a41`](https://github.com/medusajs/medusa/commit/a6562d2a41453cbe7aa43be352c4924e3e4c79d5), [`00e6b21bb5`](https://github.com/medusajs/medusa/commit/00e6b21bb50dbc886bc37ad052a1c40ce865294e), [`8fd1488938`](https://github.com/medusajs/medusa/commit/8fd148893850eb66c5eae00c4ca9391a80ea2eb9), [`1c6ba4468e`](https://github.com/medusajs/medusa/commit/1c6ba4468eab1440931c88929affd5b4c593f377)]:
|
||||
- @medusajs/types@1.11.16
|
||||
- @medusajs/modules-sdk@1.12.11
|
||||
- @medusajs/utils@1.11.9
|
||||
|
||||
## 0.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#6700](https://github.com/medusajs/medusa/pull/6700) [`8f8a4f9b13`](https://github.com/medusajs/medusa/commit/8f8a4f9b1353087d98f6cc75346d43a7f49901a8) Thanks [@olivermrbl](https://github.com/olivermrbl)! - chore: Version all modules to allow for initial testing
|
||||
|
||||
- Updated dependencies [[`1fd0457c15`](https://github.com/medusajs/medusa/commit/1fd0457c153b2ef7657c052878d8e5364e1b324a), [`9288f53327`](https://github.com/medusajs/medusa/commit/9288f53327b8ce617af92ed8d14d9459cbfeb13c), [`d4b921f3db`](https://github.com/medusajs/medusa/commit/d4b921f3dbe0a38f1565a8de759996c70798d58e), [`ac86362e81`](https://github.com/medusajs/medusa/commit/ac86362e81d8523cb8e3dfad026fc94658513018), [`e4acde1aa2`](https://github.com/medusajs/medusa/commit/e4acde1aa2eb57f07e6692fe8b61f728948b9a96), [`1a661adf3e`](https://github.com/medusajs/medusa/commit/1a661adf3ef4991aa6e237dd894b6a5c47cd4aca), [`56cbf88115`](https://github.com/medusajs/medusa/commit/56cbf88115994adea7037c3f2814f0c96af3cfc0), [`36a61658f9`](https://github.com/medusajs/medusa/commit/36a61658f969a7b19c84a1e621ad1464927cafb1), [`04a532e5ef`](https://github.com/medusajs/medusa/commit/04a532e5efabbf75b1e4155520b1da175b686ffc), [`c319edb8e0`](https://github.com/medusajs/medusa/commit/c319edb8e0ecd13d086652147667916e5abab2d8), [`0b9fcb6324`](https://github.com/medusajs/medusa/commit/0b9fcb6324eee9f2556c7e6317775fae93b12a47), [`586df9da25`](https://github.com/medusajs/medusa/commit/586df9da250e492442769f5bac2f8b3de1d46f05), [`b3d826497b`](https://github.com/medusajs/medusa/commit/b3d826497b3dae5e1b26b7924706c24fd5e87ca5), [`a86c87fe14`](https://github.com/medusajs/medusa/commit/a86c87fe1442afce9285e39255914e01012b4449), [`640eccd5dd`](https://github.com/medusajs/medusa/commit/640eccd5ddbb163e0f987ce6c772f1129c2e2632), [`8ea37d03c9`](https://github.com/medusajs/medusa/commit/8ea37d03c914a5004a3e42770668b2d1f7f8f564), [`339a946f38`](https://github.com/medusajs/medusa/commit/339a946f389033c21e05338f9dbf07d88e140533), [`ac829fc67f`](https://github.com/medusajs/medusa/commit/ac829fc67f7495b08f28e55923c59f0fd6320311), [`d9d5afc3cf`](https://github.com/medusajs/medusa/commit/d9d5afc3cfc29221d0e65bff7b78474a8fb8f31f), [`c3c4f49fc2`](https://github.com/medusajs/medusa/commit/c3c4f49fc2126f950e69e291ca939ca88a15afd3), [`9288f53327`](https://github.com/medusajs/medusa/commit/9288f53327b8ce617af92ed8d14d9459cbfeb13c), [`0d46abf0ff`](https://github.com/medusajs/medusa/commit/0d46abf0ffa4c5e03bf7d2a9cdf1db828a76bea8), [`fafde4f54d`](https://github.com/medusajs/medusa/commit/fafde4f54d3ef75a7d382e6cbf94e38b3deae99b), [`8dad2b51a2`](https://github.com/medusajs/medusa/commit/8dad2b51a26c4c3c14a6c95f70424c8bef2ad63e), [`0c705d7bd4`](https://github.com/medusajs/medusa/commit/0c705d7bd41a768c48017ae95b3c8414d96c6acb), [`a6d7070dd6`](https://github.com/medusajs/medusa/commit/a6d7070dd669c21ea19d70434d42c2f8167dc309), [`1d91b7429b`](https://github.com/medusajs/medusa/commit/1d91b7429beebd6f09d5027f7f7e1fe74ce3a8ff), [`168f02f138`](https://github.com/medusajs/medusa/commit/168f02f138ad101e1013f2c8c3f8dc19de12accf), [`1ed5f918c3`](https://github.com/medusajs/medusa/commit/1ed5f918c31794a70aca4a4e4cd83cf456593baa), [`c20eb15cd9`](https://github.com/medusajs/medusa/commit/c20eb15cd9b1bd90c8d01f68eca6f0f181cd902d), [`e5945479e0`](https://github.com/medusajs/medusa/commit/e5945479e091d9560ae3e7240306a31031ef4584), [`f5c2256286`](https://github.com/medusajs/medusa/commit/f5c22562867f412040f8bc6c55ab5de3a3735e62), [`000eb61e33`](https://github.com/medusajs/medusa/commit/000eb61e33e0302db95ee6ad1656ea9b430ed471), [`d550be3685`](https://github.com/medusajs/medusa/commit/d550be3685423218d47a20c57a5e06758f4a961a), [`62a7bcc30c`](https://github.com/medusajs/medusa/commit/62a7bcc30cbc7b234b2b51d7858439951a84edeb), [`8f8a4f9b13`](https://github.com/medusajs/medusa/commit/8f8a4f9b1353087d98f6cc75346d43a7f49901a8), [`6500f18b9b`](https://github.com/medusajs/medusa/commit/6500f18b9b80c5c9c473489e7e740d55dca74303), [`ce39b9b66e`](https://github.com/medusajs/medusa/commit/ce39b9b66e8c277ec0691ea6d0a950003be09cc1), [`a6a4b3f01a`](https://github.com/medusajs/medusa/commit/a6a4b3f01a6d2bd97b1580c59134279a1b033a5d), [`4d51f095b3`](https://github.com/medusajs/medusa/commit/4d51f095b3f98f468cefb760512563f7b77bb9cf), [`4625bd1241`](https://github.com/medusajs/medusa/commit/4625bd12416275b09c22cde4a09cb0f68df5d7c1), [`56b0b45304`](https://github.com/medusajs/medusa/commit/56b0b4530401a6ec5aa155874d371e45bb388fe2), [`cc1b66842c`](https://github.com/medusajs/medusa/commit/cc1b66842cbb37c6eab84e2d8b74844c214f38d7), [`24fb102a56`](https://github.com/medusajs/medusa/commit/24fb102a564b1253d1f8b039bb1e435cc5312fbb), [`e85463b2a7`](https://github.com/medusajs/medusa/commit/e85463b2a717751de2e21c39a4c745449b31affe)]:
|
||||
- @medusajs/types@1.11.14
|
||||
- @medusajs/utils@1.11.7
|
||||
- @medusajs/modules-sdk@1.12.9
|
||||
@@ -0,0 +1,3 @@
|
||||
# Payment Module
|
||||
|
||||
A Payment is the result of a series of financial transactions performed as part of collecting a payment of a purchase. The Payment holds information about the payment provider as well as the state of it. You can associate a Payment with a Cart, an Order, an OrderEdit, or any other resource you’d like to have your customers pay for.
|
||||
@@ -0,0 +1,67 @@
|
||||
export const defaultPaymentCollectionData = [
|
||||
{
|
||||
id: "pay-col-id-1",
|
||||
amount: 100,
|
||||
region_id: "region-id-1",
|
||||
currency_code: "usd",
|
||||
},
|
||||
{
|
||||
id: "pay-col-id-2",
|
||||
amount: 200,
|
||||
region_id: "region-id-1",
|
||||
currency_code: "usd",
|
||||
},
|
||||
{
|
||||
id: "pay-col-id-3",
|
||||
amount: 300,
|
||||
region_id: "region-id-2",
|
||||
currency_code: "usd",
|
||||
},
|
||||
]
|
||||
|
||||
export const defaultPaymentSessionData = [
|
||||
{
|
||||
id: "pay-sess-id-1",
|
||||
amount: 100,
|
||||
currency_code: "usd",
|
||||
provider_id: "pp_system_default",
|
||||
payment_collection_id: "pay-col-id-1",
|
||||
},
|
||||
{
|
||||
id: "pay-sess-id-2",
|
||||
amount: 100,
|
||||
currency_code: "usd",
|
||||
provider_id: "pp_system_default",
|
||||
payment_collection_id: "pay-col-id-2",
|
||||
},
|
||||
{
|
||||
id: "pay-sess-id-3",
|
||||
amount: 100,
|
||||
currency_code: "usd",
|
||||
provider_id: "pp_system_default",
|
||||
payment_collection_id: "pay-col-id-2",
|
||||
},
|
||||
]
|
||||
|
||||
export const defaultPaymentData = [
|
||||
{
|
||||
id: "pay-id-1",
|
||||
amount: 100,
|
||||
currency_code: "usd",
|
||||
payment_collection_id: "pay-col-id-1",
|
||||
payment_session: "pay-sess-id-1",
|
||||
provider_id: "pp_system_default",
|
||||
authorized_amount: 100,
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
id: "pay-id-2",
|
||||
amount: 100,
|
||||
authorized_amount: 100,
|
||||
currency_code: "usd",
|
||||
payment_collection_id: "pay-col-id-2",
|
||||
payment_session: "pay-sess-id-2",
|
||||
provider_id: "pp_system_default",
|
||||
data: {},
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,53 @@
|
||||
import { EntityName } from "@mikro-orm/core"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
|
||||
import { Payment, PaymentSession, PaymentCollection } from "@models"
|
||||
|
||||
import {
|
||||
defaultPaymentCollectionData,
|
||||
defaultPaymentData,
|
||||
defaultPaymentSessionData,
|
||||
} from "./data"
|
||||
|
||||
export * from "./data"
|
||||
|
||||
async function createEntities<
|
||||
T extends EntityName<Payment | PaymentCollection | PaymentSession>
|
||||
>(manager: SqlEntityManager, entity: T, data: any[]) {
|
||||
const created: T[] = []
|
||||
for (let record of data) {
|
||||
created.push(manager.create(entity, record))
|
||||
}
|
||||
|
||||
await manager.persistAndFlush(created)
|
||||
return created
|
||||
}
|
||||
|
||||
export async function createPaymentCollections(
|
||||
manager: SqlEntityManager,
|
||||
paymentCollectionData = defaultPaymentCollectionData
|
||||
): Promise<PaymentCollection[]> {
|
||||
return await createEntities<PaymentCollection>(
|
||||
manager,
|
||||
PaymentCollection,
|
||||
paymentCollectionData
|
||||
)
|
||||
}
|
||||
|
||||
export async function createPaymentSessions(
|
||||
manager: SqlEntityManager,
|
||||
paymentSessionData = defaultPaymentSessionData
|
||||
): Promise<PaymentCollection[]> {
|
||||
return await createEntities<PaymentSession>(
|
||||
manager,
|
||||
PaymentSession,
|
||||
paymentSessionData
|
||||
)
|
||||
}
|
||||
|
||||
export async function createPayments(
|
||||
manager: SqlEntityManager,
|
||||
paymentData = defaultPaymentData
|
||||
): Promise<PaymentCollection[]> {
|
||||
return await createEntities<Payment>(manager, Payment, paymentData)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { IPaymentModuleService } from "@medusajs/types"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { initModules } from "medusa-test-utils"
|
||||
import { MikroOrmWrapper } from "../../utils"
|
||||
import { getInitModuleConfig } from "../../utils/get-init-module-config"
|
||||
import { createPaymentCollections } from "../../__fixtures__"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("Payment Module Service", () => {
|
||||
let service: IPaymentModuleService
|
||||
let repositoryManager: SqlEntityManager
|
||||
let shutdownFunc: () => Promise<void>
|
||||
|
||||
beforeAll(async () => {
|
||||
await MikroOrmWrapper.setupDatabase()
|
||||
|
||||
const initModulesConfig = getInitModuleConfig()
|
||||
const { medusaApp, shutdown } = await initModules(initModulesConfig)
|
||||
service = medusaApp.modules[Modules.PAYMENT]
|
||||
|
||||
shutdownFunc = shutdown
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await shutdownFunc()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await MikroOrmWrapper.setupDatabase()
|
||||
repositoryManager = await MikroOrmWrapper.forkManager()
|
||||
|
||||
await createPaymentCollections(repositoryManager)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await MikroOrmWrapper.clearDatabase()
|
||||
})
|
||||
|
||||
describe("providers", () => {
|
||||
it("should load payment plugins", async () => {
|
||||
let error = await service
|
||||
.createPaymentCollections([
|
||||
{
|
||||
amount: 200,
|
||||
region_id: "req_123",
|
||||
} as any,
|
||||
])
|
||||
.catch((e) => e)
|
||||
|
||||
expect(error.message).toContain(
|
||||
"Value for PaymentCollection.currency_code is required, 'undefined' found"
|
||||
)
|
||||
})
|
||||
|
||||
it("should create a payment collection successfully", async () => {
|
||||
const [createdPaymentCollection] = await service.createPaymentCollections(
|
||||
[{ currency_code: "USD", amount: 200, region_id: "reg_123" }]
|
||||
)
|
||||
|
||||
expect(createdPaymentCollection).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
status: "not_paid",
|
||||
payment_providers: [],
|
||||
payment_sessions: [],
|
||||
payments: [],
|
||||
currency_code: "USD",
|
||||
amount: 200,
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
+691
@@ -0,0 +1,691 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { IPaymentModuleService } from "@medusajs/types"
|
||||
|
||||
import {
|
||||
moduleIntegrationTestRunner,
|
||||
SuiteOptions,
|
||||
} from "medusa-test-utils/dist"
|
||||
import {
|
||||
createPaymentCollections,
|
||||
createPayments,
|
||||
createPaymentSessions,
|
||||
} from "../../../__fixtures__"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
moduleIntegrationTestRunner({
|
||||
moduleName: Modules.PAYMENT,
|
||||
testSuite: ({
|
||||
MikroOrmWrapper,
|
||||
service,
|
||||
}: SuiteOptions<IPaymentModuleService>) => {
|
||||
describe("Payment Module Service", () => {
|
||||
describe("Payment Flow", () => {
|
||||
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: "pp_system_default",
|
||||
amount: 200,
|
||||
currency_code: "usd",
|
||||
data: {},
|
||||
context: {
|
||||
extra: {},
|
||||
customer: {},
|
||||
billing_address: {},
|
||||
email: "test@test.test.com",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
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: "pp_system_default",
|
||||
status: "authorized",
|
||||
authorized_at: expect.any(Date),
|
||||
}),
|
||||
],
|
||||
payments: [
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
amount: 200,
|
||||
currency_code: "usd",
|
||||
provider_id: "pp_system_default",
|
||||
captures: [
|
||||
expect.objectContaining({
|
||||
amount: 200,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("PaymentCollection", () => {
|
||||
beforeEach(async () => {
|
||||
const repositoryManager = await MikroOrmWrapper.forkManager()
|
||||
|
||||
await createPaymentCollections(repositoryManager)
|
||||
await createPaymentSessions(repositoryManager)
|
||||
await createPayments(repositoryManager)
|
||||
})
|
||||
|
||||
describe("create", () => {
|
||||
it("should throw an error when required params are not passed", async () => {
|
||||
let error = await service
|
||||
.createPaymentCollections([
|
||||
{
|
||||
amount: 200,
|
||||
region_id: "req_123",
|
||||
} as any,
|
||||
])
|
||||
.catch((e) => e)
|
||||
|
||||
expect(error.message).toContain(
|
||||
"Value for PaymentCollection.currency_code is required, 'undefined' found"
|
||||
)
|
||||
|
||||
error = await service
|
||||
.createPaymentCollections([
|
||||
{
|
||||
currency_code: "USD",
|
||||
region_id: "req_123",
|
||||
} as any,
|
||||
])
|
||||
.catch((e) => e)
|
||||
|
||||
// TODO: Change error thrown by Mikro for BigNumber fields
|
||||
expect(error.message).toContain(
|
||||
"Value for PaymentCollection.amount is required, 'undefined' found"
|
||||
)
|
||||
|
||||
error = await service
|
||||
.createPaymentCollections([
|
||||
{
|
||||
currency_code: "USD",
|
||||
amount: 200,
|
||||
} as any,
|
||||
])
|
||||
.catch((e) => e)
|
||||
|
||||
expect(error.message).toContain(
|
||||
"Value for PaymentCollection.region_id is required, 'undefined' found"
|
||||
)
|
||||
})
|
||||
|
||||
it("should create a payment collection successfully", async () => {
|
||||
const [createdPaymentCollection] =
|
||||
await service.createPaymentCollections([
|
||||
{ currency_code: "USD", amount: 200, region_id: "reg_123" },
|
||||
])
|
||||
|
||||
expect(createdPaymentCollection).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
status: "not_paid",
|
||||
payment_providers: [],
|
||||
payment_sessions: [],
|
||||
payments: [],
|
||||
currency_code: "USD",
|
||||
amount: 200,
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("delete", () => {
|
||||
it("should delete a Payment Collection", async () => {
|
||||
let collection = await service.listPaymentCollections({
|
||||
id: ["pay-col-id-1"],
|
||||
})
|
||||
|
||||
expect(collection.length).toEqual(1)
|
||||
|
||||
await service.deletePaymentCollections(["pay-col-id-1"])
|
||||
|
||||
collection = await service.listPaymentCollections({
|
||||
id: ["pay-col-id-1"],
|
||||
})
|
||||
|
||||
expect(collection.length).toEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieve", () => {
|
||||
it("should retrieve a Payment Collection", async () => {
|
||||
let collection = await service.retrievePaymentCollection(
|
||||
"pay-col-id-2"
|
||||
)
|
||||
|
||||
expect(collection).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "pay-col-id-2",
|
||||
amount: 200,
|
||||
region_id: "region-id-1",
|
||||
currency_code: "usd",
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail to retrieve a non existent Payment Collection", async () => {
|
||||
let error = await service
|
||||
.retrievePaymentCollection("pay-col-id-not-exists")
|
||||
.catch((e) => e)
|
||||
|
||||
expect(error.message).toContain(
|
||||
"PaymentCollection with id: pay-col-id-not-exists was not found"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("list", () => {
|
||||
it("should list and count Payment Collection", async () => {
|
||||
let [collections, count] =
|
||||
await service.listAndCountPaymentCollections()
|
||||
|
||||
expect(count).toEqual(3)
|
||||
|
||||
expect(collections).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: "pay-col-id-1",
|
||||
amount: 100,
|
||||
region_id: "region-id-1",
|
||||
currency_code: "usd",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "pay-col-id-2",
|
||||
amount: 200,
|
||||
region_id: "region-id-1",
|
||||
currency_code: "usd",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "pay-col-id-3",
|
||||
amount: 300,
|
||||
region_id: "region-id-2",
|
||||
currency_code: "usd",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should list Payment Collections by region_id", async () => {
|
||||
let collections = await service.listPaymentCollections(
|
||||
{
|
||||
region_id: "region-id-1",
|
||||
},
|
||||
{ select: ["id", "amount", "region_id"] }
|
||||
)
|
||||
|
||||
expect(collections.length).toEqual(2)
|
||||
|
||||
expect(collections).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: "pay-col-id-1",
|
||||
amount: 100,
|
||||
region_id: "region-id-1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "pay-col-id-2",
|
||||
amount: 200,
|
||||
region_id: "region-id-1",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
it("should update a Payment Collection", async () => {
|
||||
await service.updatePaymentCollections("pay-col-id-2", {
|
||||
currency_code: "eur",
|
||||
region_id: "reg-2",
|
||||
})
|
||||
|
||||
const collection = await service.retrievePaymentCollection(
|
||||
"pay-col-id-2"
|
||||
)
|
||||
|
||||
expect(collection).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "pay-col-id-2",
|
||||
region_id: "reg-2",
|
||||
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", () => {
|
||||
beforeEach(async () => {
|
||||
const repositoryManager = await MikroOrmWrapper.forkManager()
|
||||
|
||||
await createPaymentCollections(repositoryManager)
|
||||
await createPaymentSessions(repositoryManager)
|
||||
await createPayments(repositoryManager)
|
||||
})
|
||||
|
||||
describe("create", () => {
|
||||
it("should create a payment session successfully", async () => {
|
||||
await service.createPaymentSession("pay-col-id-1", {
|
||||
provider_id: "pp_system_default",
|
||||
amount: 200,
|
||||
currency_code: "usd",
|
||||
data: {},
|
||||
context: {
|
||||
extra: {},
|
||||
customer: {},
|
||||
billing_address: {},
|
||||
email: "test@test.test.com",
|
||||
resource_id: "cart_test",
|
||||
},
|
||||
})
|
||||
|
||||
const paymentCollection = await service.retrievePaymentCollection(
|
||||
"pay-col-id-1",
|
||||
{ relations: ["payment_sessions"] }
|
||||
)
|
||||
|
||||
expect(paymentCollection).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "pay-col-id-1",
|
||||
status: "not_paid",
|
||||
payment_sessions: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
data: {},
|
||||
status: "pending",
|
||||
authorized_at: null,
|
||||
currency_code: "usd",
|
||||
amount: 200,
|
||||
provider_id: "pp_system_default",
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
it("should update a payment session successfully", async () => {
|
||||
let session = await service.createPaymentSession("pay-col-id-1", {
|
||||
provider_id: "pp_system_default",
|
||||
amount: 200,
|
||||
currency_code: "usd",
|
||||
data: {},
|
||||
context: {
|
||||
extra: {},
|
||||
customer: {},
|
||||
billing_address: {},
|
||||
email: "test@test.test.com",
|
||||
resource_id: "cart_test",
|
||||
},
|
||||
})
|
||||
|
||||
session = await service.updatePaymentSession({
|
||||
id: session.id,
|
||||
amount: 200,
|
||||
currency_code: "eur",
|
||||
data: {},
|
||||
context: {
|
||||
resource_id: "res_id",
|
||||
extra: {},
|
||||
customer: {},
|
||||
billing_address: {},
|
||||
email: "new@test.tsst",
|
||||
},
|
||||
})
|
||||
|
||||
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: "pp_system_default",
|
||||
amount: 100,
|
||||
currency_code: "usd",
|
||||
data: {},
|
||||
context: {
|
||||
extra: {},
|
||||
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,
|
||||
currency_code: "usd",
|
||||
provider_id: "pp_system_default",
|
||||
refunds: [],
|
||||
captures: [],
|
||||
data: {},
|
||||
cart_id: null,
|
||||
order_id: null,
|
||||
customer_id: null,
|
||||
deleted_at: null,
|
||||
captured_at: null,
|
||||
canceled_at: null,
|
||||
payment_collection_id: expect.any(String),
|
||||
payment_session: expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
updated_at: expect.any(Date),
|
||||
created_at: expect.any(Date),
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
raw_amount: { value: "100", precision: 20 },
|
||||
provider_id: "pp_system_default",
|
||||
data: {},
|
||||
status: "authorized",
|
||||
authorized_at: expect.any(Date),
|
||||
payment_collection: expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
}),
|
||||
payment_collection_id: expect.any(String),
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("Payment", () => {
|
||||
beforeEach(async () => {
|
||||
const repositoryManager = await MikroOrmWrapper.forkManager()
|
||||
|
||||
await createPaymentCollections(repositoryManager)
|
||||
await createPaymentSessions(repositoryManager)
|
||||
await createPayments(repositoryManager)
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
it("should update a payment successfully", async () => {
|
||||
const updatedPayment = await service.updatePayment({
|
||||
id: "pay-id-1",
|
||||
cart_id: "new-cart",
|
||||
})
|
||||
|
||||
expect(updatedPayment).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "pay-id-1",
|
||||
cart_id: "new-cart",
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("capture", () => {
|
||||
it("should capture a payment successfully and update captured_at", async () => {
|
||||
const capturedPayment = await service.capturePayment({
|
||||
amount: 100,
|
||||
payment_id: "pay-id-1",
|
||||
})
|
||||
|
||||
expect(capturedPayment).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "pay-id-1",
|
||||
amount: 100,
|
||||
|
||||
captures: [
|
||||
expect.objectContaining({
|
||||
created_by: null,
|
||||
amount: 100,
|
||||
}),
|
||||
],
|
||||
captured_at: expect.any(Date),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should split a payment in two captures a payment successfully", async () => {
|
||||
await service.capturePayment({
|
||||
amount: 50,
|
||||
payment_id: "pay-id-1",
|
||||
})
|
||||
|
||||
const capturedPayment = await service.capturePayment({
|
||||
amount: 50,
|
||||
payment_id: "pay-id-1",
|
||||
})
|
||||
|
||||
expect(capturedPayment).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "pay-id-1",
|
||||
amount: 100,
|
||||
|
||||
captures: [
|
||||
expect.objectContaining({
|
||||
created_by: null,
|
||||
amount: 50,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
created_by: null,
|
||||
amount: 50,
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail to capture amount greater than authorized", async () => {
|
||||
const error = await service
|
||||
.capturePayment({
|
||||
amount: 200,
|
||||
payment_id: "pay-id-1",
|
||||
})
|
||||
.catch((e) => e)
|
||||
|
||||
expect(error.message).toEqual(
|
||||
"You cannot capture more than the authorized amount substracted by what is already captured."
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail to capture amount greater than what is already captured", async () => {
|
||||
await service.capturePayment({
|
||||
amount: 99,
|
||||
payment_id: "pay-id-1",
|
||||
})
|
||||
|
||||
const error = await service
|
||||
.capturePayment({
|
||||
amount: 2,
|
||||
payment_id: "pay-id-1",
|
||||
})
|
||||
.catch((e) => e)
|
||||
|
||||
expect(error.message).toEqual(
|
||||
"You cannot capture more than the authorized amount substracted by what is already captured."
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail to capture already captured payment", async () => {
|
||||
await service.capturePayment({
|
||||
amount: 100,
|
||||
payment_id: "pay-id-1",
|
||||
})
|
||||
|
||||
const error = await service
|
||||
.capturePayment({
|
||||
amount: 100,
|
||||
payment_id: "pay-id-1",
|
||||
})
|
||||
.catch((e) => e)
|
||||
|
||||
expect(error.message).toEqual(
|
||||
"You cannot capture more than the authorized amount substracted by what is already 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", () => {
|
||||
it("should refund a payments successfully", async () => {
|
||||
await service.capturePayment({
|
||||
amount: 100,
|
||||
payment_id: "pay-id-2",
|
||||
})
|
||||
|
||||
const refundedPayment = await service.refundPayment({
|
||||
amount: 100,
|
||||
payment_id: "pay-id-2",
|
||||
})
|
||||
|
||||
expect(refundedPayment).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "pay-id-2",
|
||||
amount: 100,
|
||||
refunds: [
|
||||
expect.objectContaining({
|
||||
created_by: null,
|
||||
amount: 100,
|
||||
}),
|
||||
],
|
||||
// captured_amount: 100,
|
||||
// refunded_amount: 100,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw if refund is greater than captured amount", async () => {
|
||||
await service.capturePayment({
|
||||
amount: 50,
|
||||
payment_id: "pay-id-1",
|
||||
})
|
||||
|
||||
const error = await service
|
||||
.refundPayment({
|
||||
amount: 100,
|
||||
payment_id: "pay-id-1",
|
||||
})
|
||||
.catch((e) => e)
|
||||
|
||||
expect(error.message).toEqual(
|
||||
"You cannot refund more than what is captured on the payment."
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
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,6 @@
|
||||
if (typeof process.env.DB_TEMP_NAME === "undefined") {
|
||||
const tempName = parseInt(process.env.JEST_WORKER_ID || "1")
|
||||
process.env.DB_TEMP_NAME = `medusa-payment-integration-${tempName}`
|
||||
}
|
||||
|
||||
process.env.MEDUSA_PAYMENT_DB_SCHEMA = "public"
|
||||
@@ -0,0 +1,3 @@
|
||||
import { JestUtils } from "medusa-test-utils"
|
||||
|
||||
JestUtils.afterAllHookDropDatabase()
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ModuleServiceInitializeOptions } from "@medusajs/types"
|
||||
|
||||
export const databaseOptions: ModuleServiceInitializeOptions["database"] = {
|
||||
schema: "public",
|
||||
clientUrl: "medusa-payment-test",
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import {TestDatabaseUtils} from "medusa-test-utils"
|
||||
|
||||
import * as PaymentModules from "@models"
|
||||
|
||||
const pathToMigrations = "../../src/migrations"
|
||||
const mikroOrmEntities = PaymentModules as unknown as any[]
|
||||
|
||||
export const MikroOrmWrapper = TestDatabaseUtils.getMikroOrmWrapper({
|
||||
mikroOrmEntities,
|
||||
pathToMigrations,
|
||||
})
|
||||
|
||||
export const MikroOrmConfig = TestDatabaseUtils.getMikroOrmConfig({
|
||||
mikroOrmEntities,
|
||||
pathToMigrations,
|
||||
})
|
||||
|
||||
export const DB_URL = TestDatabaseUtils.getDatabaseURL()
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Modules, ModulesDefinition } from "@medusajs/modules-sdk"
|
||||
|
||||
import { DB_URL } from "./database"
|
||||
|
||||
export function getInitModuleConfig() {
|
||||
const moduleOptions = {
|
||||
defaultAdapterOptions: {
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PAYMENT_DB_SCHEMA,
|
||||
},
|
||||
},
|
||||
providers: [
|
||||
{
|
||||
resolve: "@medusajs/payment-stripe",
|
||||
options: {
|
||||
config: {
|
||||
dkk: {
|
||||
apiKey: "pk_test_123",
|
||||
},
|
||||
usd: {
|
||||
apiKey: "pk_test_456",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const injectedDependencies = {}
|
||||
|
||||
const modulesConfig_ = {
|
||||
[Modules.PAYMENT]: {
|
||||
definition: ModulesDefinition[Modules.PAYMENT],
|
||||
options: moduleOptions,
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
injectedDependencies,
|
||||
modulesConfig: modulesConfig_,
|
||||
databaseConfig: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PAYMENT_DB_SCHEMA,
|
||||
},
|
||||
joinerConfig: [],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./config"
|
||||
export * from "./database"
|
||||
@@ -0,0 +1,21 @@
|
||||
module.exports = {
|
||||
moduleNameMapper: {
|
||||
"^@models": "<rootDir>/src/models",
|
||||
"^@services": "<rootDir>/src/services",
|
||||
"^@repositories": "<rootDir>/src/repositories",
|
||||
},
|
||||
transform: {
|
||||
"^.+\\.[jt]s?$": [
|
||||
"ts-jest",
|
||||
{
|
||||
tsconfig: "tsconfig.spec.json",
|
||||
isolatedModules: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
testEnvironment: `node`,
|
||||
moduleFileExtensions: [`js`, `ts`],
|
||||
modulePathIgnorePatterns: ["dist/"],
|
||||
setupFiles: ["<rootDir>/integration-tests/setup-env.js"],
|
||||
setupFilesAfterEnv: ["<rootDir>/integration-tests/setup.js"],
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import * as entities from "./src/models"
|
||||
|
||||
module.exports = {
|
||||
entities: Object.values(entities),
|
||||
schema: "public",
|
||||
clientUrl: "postgres://postgres@localhost/medusa-payment",
|
||||
type: "postgresql",
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "@medusajs/payment",
|
||||
"version": "0.0.3",
|
||||
"description": "Medusa Payment module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"bin": {
|
||||
"medusa-payment-seed": "dist/scripts/bin/run-seed.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/medusajs/medusa",
|
||||
"directory": "packages/payment"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"author": "Medusa",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"watch": "tsc --build --watch",
|
||||
"watch:test": "tsc --build tsconfig.spec.json --watch",
|
||||
"prepublishOnly": "cross-env NODE_ENV=production tsc --build && tsc-alias -p tsconfig.json",
|
||||
"build": "rimraf dist && tsc --build && tsc-alias -p tsconfig.json",
|
||||
"test": "jest --runInBand --bail --passWithNoTests --forceExit -- src",
|
||||
"test:integration": "jest --forceExit -- integration-tests/**/__tests__/**/*.ts",
|
||||
"migration:generate": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:generate",
|
||||
"migration:initial": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:create --initial",
|
||||
"migration:create": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:create",
|
||||
"migration:up": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm migration:up",
|
||||
"orm:cache:clear": " MIKRO_ORM_CLI=./mikro-orm.config.dev.ts mikro-orm cache:clear"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mikro-orm/cli": "5.9.7",
|
||||
"cross-env": "^5.2.1",
|
||||
"jest": "^29.6.3",
|
||||
"medusa-test-utils": "^1.1.44",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsc-alias": "^1.8.6",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@medusajs/modules-sdk": "^1.12.11",
|
||||
"@medusajs/types": "^1.11.16",
|
||||
"@medusajs/utils": "^1.11.9",
|
||||
"@mikro-orm/core": "5.9.7",
|
||||
"@mikro-orm/migrations": "5.9.7",
|
||||
"@mikro-orm/postgresql": "5.9.7",
|
||||
"awilix": "^8.0.0",
|
||||
"bignumber.js": "^9.1.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"knex": "2.4.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import {
|
||||
moduleDefinition,
|
||||
revertMigration,
|
||||
runMigrations,
|
||||
} from "./module-definition"
|
||||
|
||||
export default moduleDefinition
|
||||
export { revertMigration, runMigrations }
|
||||
|
||||
export * from "./initialize"
|
||||
export * from "./types"
|
||||
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
ExternalModuleDeclaration,
|
||||
InternalModuleDeclaration,
|
||||
MedusaModule,
|
||||
MODULE_PACKAGE_NAMES,
|
||||
Modules,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import {
|
||||
IPaymentModuleService,
|
||||
ModuleProvider,
|
||||
ModulesSdkTypes,
|
||||
} from "@medusajs/types"
|
||||
|
||||
import { moduleDefinition } from "../module-definition"
|
||||
import { InitializeModuleInjectableDependencies } from "../types"
|
||||
|
||||
export const initialize = async (
|
||||
options?:
|
||||
| (
|
||||
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
||||
| ExternalModuleDeclaration
|
||||
| InternalModuleDeclaration
|
||||
) & { providers: ModuleProvider[] },
|
||||
injectedDependencies?: InitializeModuleInjectableDependencies
|
||||
): Promise<IPaymentModuleService> => {
|
||||
const loaded = await MedusaModule.bootstrap<IPaymentModuleService>({
|
||||
moduleKey: Modules.PAYMENT,
|
||||
defaultPath: MODULE_PACKAGE_NAMES[Modules.PAYMENT],
|
||||
declaration: options as
|
||||
| InternalModuleDeclaration
|
||||
| ExternalModuleDeclaration,
|
||||
injectedDependencies,
|
||||
moduleExports: moduleDefinition,
|
||||
})
|
||||
|
||||
return loaded[Modules.PAYMENT]
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { MapToConfig } from "@medusajs/utils"
|
||||
import {
|
||||
Payment,
|
||||
PaymentCollection,
|
||||
PaymentProvider,
|
||||
PaymentSession,
|
||||
} from "@models"
|
||||
|
||||
export const LinkableKeys = {
|
||||
payment_id: Payment.name,
|
||||
payment_collection_id: PaymentCollection.name,
|
||||
payment_provider_id: PaymentProvider.name,
|
||||
}
|
||||
|
||||
const entityLinkableKeysMap: MapToConfig = {}
|
||||
Object.entries(LinkableKeys).forEach(([key, value]) => {
|
||||
entityLinkableKeysMap[value] ??= []
|
||||
entityLinkableKeysMap[value].push({
|
||||
mapTo: key,
|
||||
valueFrom: key.split("_").pop()!,
|
||||
})
|
||||
})
|
||||
|
||||
export const entityNameToLinkableKeysMap: MapToConfig = entityLinkableKeysMap
|
||||
|
||||
export const joinerConfig: ModuleJoinerConfig = {
|
||||
serviceName: Modules.PAYMENT,
|
||||
primaryKeys: ["id"],
|
||||
linkableKeys: LinkableKeys,
|
||||
alias: [
|
||||
{
|
||||
name: ["payment", "payments"],
|
||||
args: {
|
||||
entity: Payment.name,
|
||||
methodSuffix: "Payments",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["payment_collection", "payment_collections"],
|
||||
args: {
|
||||
entity: PaymentCollection.name,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["payment_session", "payment_sessions"],
|
||||
args: {
|
||||
entity: PaymentSession.name,
|
||||
methodSuffix: "PaymentSessions",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["payment_provider", "payment_providers"],
|
||||
args: {
|
||||
entity: PaymentProvider.name,
|
||||
methodSuffix: "PaymentProviders",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
InternalModuleDeclaration,
|
||||
LoaderOptions,
|
||||
Modules,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import { ModulesSdkTypes } from "@medusajs/types"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { EntitySchema } from "@mikro-orm/core"
|
||||
import * as PaymentModels from "../models"
|
||||
|
||||
export default async (
|
||||
{
|
||||
options,
|
||||
container,
|
||||
logger,
|
||||
}: LoaderOptions<
|
||||
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
||||
>,
|
||||
moduleDeclaration?: InternalModuleDeclaration
|
||||
): Promise<void> => {
|
||||
const entities = Object.values(PaymentModels) as unknown as EntitySchema[]
|
||||
const pathToMigrations = __dirname + "/../migrations"
|
||||
|
||||
await ModulesSdkUtils.mikroOrmConnectionLoader({
|
||||
moduleName: Modules.PAYMENT,
|
||||
entities,
|
||||
container,
|
||||
options,
|
||||
moduleDeclaration,
|
||||
logger,
|
||||
pathToMigrations,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import * as ModuleModels from "@models"
|
||||
import * as ModuleRepositories from "@repositories"
|
||||
import * as ModuleServices from "@services"
|
||||
|
||||
export default ModulesSdkUtils.moduleContainerLoaderFactory({
|
||||
moduleModels: ModuleModels,
|
||||
moduleRepositories: ModuleRepositories,
|
||||
moduleServices: ModuleServices,
|
||||
})
|
||||
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
CreatePaymentProviderDTO,
|
||||
LoaderOptions
|
||||
} from "@medusajs/types"
|
||||
|
||||
export default async ({ container }: LoaderOptions): Promise<void> => {
|
||||
const providersToLoad = container.resolve("payment_providers")
|
||||
const paymentProviderService = container.resolve("paymentProviderService")
|
||||
|
||||
const providers = await paymentProviderService.list({
|
||||
id: providersToLoad,
|
||||
})
|
||||
|
||||
const loadedProvidersMap = new Map(providers.map((p) => [p.id, p]))
|
||||
|
||||
const providersToCreate: CreatePaymentProviderDTO[] = []
|
||||
for (const id of providersToLoad) {
|
||||
if (loadedProvidersMap.has(id)) {
|
||||
continue
|
||||
}
|
||||
|
||||
providersToCreate.push({ id })
|
||||
}
|
||||
|
||||
await paymentProviderService.create(providersToCreate)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./connection"
|
||||
export * from "./container"
|
||||
export * from "./providers"
|
||||
export * from "./defaults"
|
||||
@@ -0,0 +1,40 @@
|
||||
import { moduleProviderLoader } from "@medusajs/modules-sdk"
|
||||
import { LoaderOptions, ModuleProvider, ModulesSdkTypes } from "@medusajs/types"
|
||||
import { Lifetime, asFunction, asValue } from "awilix"
|
||||
|
||||
import * as providers from "../providers"
|
||||
|
||||
const registrationFn = async (klass, container, pluginOptions) => {
|
||||
Object.entries(pluginOptions.config || []).map(([name, config]) => {
|
||||
const key = `pp_${klass.PROVIDER}_${name}`
|
||||
|
||||
container.register({
|
||||
[key]: asFunction((cradle) => new klass(cradle, config), {
|
||||
lifetime: klass.LIFE_TIME || Lifetime.SINGLETON,
|
||||
}),
|
||||
})
|
||||
|
||||
container.registerAdd("payment_providers", asValue(key))
|
||||
})
|
||||
}
|
||||
|
||||
export default async ({
|
||||
container,
|
||||
options,
|
||||
}: LoaderOptions<
|
||||
(
|
||||
| ModulesSdkTypes.ModuleServiceInitializeOptions
|
||||
| ModulesSdkTypes.ModuleServiceInitializeCustomDataLayerOptions
|
||||
) & { providers: ModuleProvider[] }
|
||||
>): Promise<void> => {
|
||||
// Local providers
|
||||
for (const provider of Object.values(providers)) {
|
||||
await registrationFn(provider, container, { config: { default: {} } })
|
||||
}
|
||||
|
||||
await moduleProviderLoader({
|
||||
container,
|
||||
providers: options?.providers || [],
|
||||
registerServiceFn: registrationFn,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
import { generatePostgresAlterColummnIfExistStatement } from "@medusajs/utils"
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class Migration20240225134525 extends Migration {
|
||||
async up(): Promise<void> {
|
||||
const paymentCollectionExists = await this.execute(
|
||||
`SELECT * FROM information_schema.tables where table_name = 'payment_collection' and table_schema = 'public';`
|
||||
)
|
||||
|
||||
if (paymentCollectionExists.length) {
|
||||
this.addSql(`
|
||||
${generatePostgresAlterColummnIfExistStatement(
|
||||
"payment_collection",
|
||||
["type", "created_by"],
|
||||
"DROP NOT NULL"
|
||||
)}
|
||||
|
||||
ALTER TABLE IF EXISTS "payment_collection" ADD COLUMN IF NOT EXISTS "completed_at" TIMESTAMPTZ NULL;
|
||||
ALTER TABLE IF EXISTS "payment_collection" ADD COLUMN IF NOT EXISTS "raw_amount" JSONB NOT NULL;
|
||||
ALTER TABLE IF EXISTS "payment_collection" ADD COLUMN IF NOT EXISTS "deleted_at" TIMESTAMPTZ NULL;
|
||||
ALTER TABLE "payment_collection" DROP CONSTRAINT "FK_payment_collection_region_id";
|
||||
|
||||
ALTER TABLE IF EXISTS "payment_provider" ADD COLUMN IF NOT EXISTS "is_enabled" BOOLEAN NOT NULL DEFAULT TRUE;
|
||||
|
||||
ALTER TABLE IF EXISTS "payment_session" ADD COLUMN IF NOT EXISTS "payment_collection_id" TEXT NOT NULL;
|
||||
ALTER TABLE IF EXISTS "payment_session" ADD COLUMN IF NOT EXISTS "currency_code" TEXT NOT NULL;
|
||||
ALTER TABLE IF EXISTS "payment_session" ADD COLUMN IF NOT EXISTS "authorized_at" TEXT NULL;
|
||||
ALTER TABLE IF EXISTS "payment_session" ADD COLUMN IF NOT EXISTS "payment_authorized_at" TIMESTAMPTZ NULL;
|
||||
ALTER TABLE IF EXISTS "payment_session" ADD COLUMN IF NOT EXISTS "raw_amount" JSONB NOT NULL;
|
||||
ALTER TABLE IF EXISTS "payment_session" ADD COLUMN IF NOT EXISTS "deleted_at" TIMESTAMPTZ NULL;
|
||||
ALTER TABLE IF EXISTS "payment_session" ADD COLUMN IF NOT EXISTS "context" JSONB NULL;
|
||||
|
||||
ALTER TABLE IF EXISTS "payment" ADD COLUMN IF NOT EXISTS "deleted_at" TIMESTAMPTZ NULL;
|
||||
ALTER TABLE IF EXISTS "payment" ADD COLUMN IF NOT EXISTS "payment_collection_id" TEXT NOT NULL;
|
||||
ALTER TABLE IF EXISTS "payment" ADD COLUMN IF NOT EXISTS "provider_id" TEXT NOT NULL;
|
||||
ALTER TABLE IF EXISTS "payment" ADD COLUMN IF NOT EXISTS "raw_amount" JSONB NOT NULL;
|
||||
ALTER TABLE IF EXISTS "payment" ADD COLUMN IF NOT EXISTS "deleted_at" TIMESTAMPTZ NULL;
|
||||
ALTER TABLE IF EXISTS "payment" ADD COLUMN IF NOT EXISTS "payment_session_id" TEXT NOT NULL;
|
||||
ALTER TABLE IF EXISTS "payment" ADD COLUMN IF NOT EXISTS "customer_id" TEXT NULL;
|
||||
|
||||
ALTER TABLE IF EXISTS "refund" ADD COLUMN IF NOT EXISTS "raw_amount" JSONB NOT NULL;
|
||||
ALTER TABLE IF EXISTS "refund" ADD COLUMN IF NOT EXISTS "deleted_at" TIMESTAMPTZ NULL;
|
||||
ALTER TABLE IF EXISTS "refund" ADD COLUMN IF NOT EXISTS "created_by" TEXT NULL;
|
||||
${generatePostgresAlterColummnIfExistStatement(
|
||||
"refund",
|
||||
["reason"],
|
||||
"DROP NOT NULL"
|
||||
)}
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "capture" (
|
||||
"id" TEXT NOT NULL,
|
||||
"amount" NUMERIC NOT NULL,
|
||||
"raw_amount" JSONB NOT NULL,
|
||||
"payment_id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"deleted_at" TIMESTAMPTZ NULL,
|
||||
"created_by" TEXT NULL,
|
||||
"metadata" JSONB NULL,
|
||||
CONSTRAINT "capture_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payment_method_token" (
|
||||
"id" TEXT NOT NULL,
|
||||
"provider_id" TEXT NOT NULL,
|
||||
"data" JSONB NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"type_detail" TEXT NULL,
|
||||
"description_detail" TEXT NULL,
|
||||
"metadata" JSONB NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"deleted_at" TIMESTAMPTZ NULL,
|
||||
CONSTRAINT "payment_method_token_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payment_collection_payment_providers" (
|
||||
"payment_collection_id" TEXT NOT NULL,
|
||||
"payment_provider_id" TEXT NOT NULL,
|
||||
CONSTRAINT "payment_collection_payment_providers_pkey" PRIMARY KEY ("payment_collection_id", "payment_provider_id")
|
||||
);
|
||||
|
||||
ALTER TABLE IF EXISTS "payment_collection_payment_providers"
|
||||
ADD CONSTRAINT "payment_collection_payment_providers_payment_coll_aa276_foreign" FOREIGN KEY ("payment_collection_id") REFERENCES "payment_collection" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE IF EXISTS "payment_collection_payment_providers"
|
||||
ADD CONSTRAINT "payment_collection_payment_providers_payment_provider_id_foreign" FOREIGN KEY ("payment_provider_id") REFERENCES "payment_provider" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE IF EXISTS "capture"
|
||||
ADD CONSTRAINT "capture_payment_id_foreign" FOREIGN KEY ("payment_id") REFERENCES "payment" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE IF EXISTS "refund"
|
||||
ADD CONSTRAINT "refund_payment_id_foreign" FOREIGN KEY ("payment_id") REFERENCES "payment" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_deleted_at" ON "payment" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_payment_collection_id" ON "payment" ("payment_collection_id") WHERE "deleted_at" IS NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_method_token_deleted_at" ON "payment_method_token" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_provider_id" ON "payment" ("provider_id") WHERE "deleted_at" IS NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_collection_region_id" ON "payment_collection" ("region_id") WHERE "deleted_at" IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_collection_deleted_at" ON "payment_collection" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_refund_payment_id" ON "refund" ("payment_id") WHERE "deleted_at" IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS "IDX_refund_deleted_at" ON "payment" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_capture_payment_id" ON "capture" ("payment_id") WHERE "deleted_at" IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS "IDX_capture_deleted_at" ON "payment" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_session_payment_collection_id" ON "payment_session" ("payment_collection_id") WHERE "deleted_at" IS NULL;
|
||||
|
||||
`)
|
||||
} else {
|
||||
this.addSql(`
|
||||
CREATE TABLE IF NOT EXISTS "payment_collection" (
|
||||
"id" TEXT NOT NULL,
|
||||
"currency_code" TEXT NOT NULL,
|
||||
"amount" NUMERIC NOT NULL,
|
||||
"raw_amount" JSONB NOT NULL,
|
||||
"region_id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"deleted_at" TIMESTAMPTZ NULL,
|
||||
"completed_at" TIMESTAMPTZ NULL,
|
||||
"status" TEXT CHECK ("status" IN ('not_paid', 'awaiting', 'authorized', 'partially_authorized', 'canceled')) NOT NULL DEFAULT 'not_paid',
|
||||
"metadata" JSONB NULL,
|
||||
CONSTRAINT "payment_collection_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payment_method_token" (
|
||||
"id" TEXT NOT NULL,
|
||||
"provider_id" TEXT NOT NULL,
|
||||
"data" JSONB NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"type_detail" TEXT NULL,
|
||||
"description_detail" TEXT NULL,
|
||||
"metadata" JSONB NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"deleted_at" TIMESTAMPTZ NULL,
|
||||
CONSTRAINT "payment_method_token_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payment_provider" (
|
||||
"id" TEXT NOT NULL,
|
||||
"is_enabled" BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
CONSTRAINT "payment_provider_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payment_collection_payment_providers" (
|
||||
"payment_collection_id" TEXT NOT NULL,
|
||||
"payment_provider_id" TEXT NOT NULL,
|
||||
CONSTRAINT "payment_collection_payment_providers_pkey" PRIMARY KEY ("payment_collection_id", "payment_provider_id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payment_session" (
|
||||
"id" TEXT NOT NULL,
|
||||
"currency_code" TEXT NOT NULL,
|
||||
"amount" NUMERIC NOT NULL,
|
||||
"raw_amount" JSONB NOT NULL,
|
||||
"provider_id" TEXT NOT NULL,
|
||||
"data" JSONB NOT NULL,
|
||||
"context" JSONB NULL,
|
||||
"status" TEXT CHECK ("status" IN ('authorized', 'pending', 'requires_more', 'error', 'canceled')) NOT NULL DEFAULT 'pending',
|
||||
"authorized_at" TIMESTAMPTZ NULL,
|
||||
"payment_collection_id" TEXT NOT NULL,
|
||||
"metadata" JSONB NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"deleted_at" TIMESTAMPTZ NULL,
|
||||
CONSTRAINT "payment_session_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payment" (
|
||||
"id" TEXT NOT NULL,
|
||||
"amount" NUMERIC NOT NULL,
|
||||
"raw_amount" JSONB NOT NULL,
|
||||
"currency_code" TEXT NOT NULL,
|
||||
"provider_id" TEXT NOT NULL,
|
||||
"cart_id" TEXT NULL,
|
||||
"order_id" TEXT NULL,
|
||||
"customer_id" TEXT NULL,
|
||||
"data" JSONB NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"deleted_at" TIMESTAMPTZ NULL,
|
||||
"captured_at" TIMESTAMPTZ NULL,
|
||||
"canceled_at" TIMESTAMPTZ NULL,
|
||||
"payment_collection_id" TEXT NOT NULL,
|
||||
"payment_session_id" TEXT NOT NULL,
|
||||
"metadata" JSONB NULL,
|
||||
CONSTRAINT "payment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "refund" (
|
||||
"id" TEXT NOT NULL,
|
||||
"amount" NUMERIC NOT NULL,
|
||||
"raw_amount" JSONB NOT NULL,
|
||||
"payment_id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"deleted_at" TIMESTAMPTZ NULL,
|
||||
"created_by" TEXT NULL,
|
||||
"metadata" JSONB NULL,
|
||||
CONSTRAINT "refund_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "capture" (
|
||||
"id" TEXT NOT NULL,
|
||||
"amount" NUMERIC NOT NULL,
|
||||
"raw_amount" JSONB NOT NULL,
|
||||
"payment_id" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"deleted_at" TIMESTAMPTZ NULL,
|
||||
"created_by" TEXT NULL,
|
||||
"metadata" JSONB NULL,
|
||||
CONSTRAINT "capture_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_deleted_at" ON "payment" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_payment_collection_id" ON "payment" ("payment_collection_id") WHERE "deleted_at" IS NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_method_token_deleted_at" ON "payment_method_token" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_provider_id" ON "payment" ("provider_id") WHERE "deleted_at" IS NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_collection_region_id" ON "payment_collection" ("region_id") WHERE "deleted_at" IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_collection_deleted_at" ON "payment_collection" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_refund_payment_id" ON "refund" ("payment_id") WHERE "deleted_at" IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS "IDX_refund_deleted_at" ON "payment" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_capture_payment_id" ON "capture" ("payment_id") WHERE "deleted_at" IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS "IDX_capture_deleted_at" ON "payment" ("deleted_at") WHERE "deleted_at" IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "IDX_payment_session_payment_collection_id" ON "payment_session" ("payment_collection_id") WHERE "deleted_at" IS NULL;
|
||||
|
||||
ALTER TABLE IF EXISTS "payment_collection_payment_providers"
|
||||
ADD CONSTRAINT "payment_collection_payment_providers_payment_coll_aa276_foreign" FOREIGN KEY ("payment_collection_id") REFERENCES "payment_collection" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE IF EXISTS "payment_collection_payment_providers"
|
||||
ADD CONSTRAINT "payment_collection_payment_providers_payment_provider_id_foreign" FOREIGN KEY ("payment_provider_id") REFERENCES "payment_provider" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE IF EXISTS "payment_session"
|
||||
ADD CONSTRAINT "payment_session_payment_collection_id_foreign" FOREIGN KEY ("payment_collection_id") REFERENCES "payment_collection" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE IF EXISTS "payment"
|
||||
ADD CONSTRAINT "payment_payment_collection_id_foreign" FOREIGN KEY ("payment_collection_id") REFERENCES "payment_collection" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE IF EXISTS "capture"
|
||||
ADD CONSTRAINT "capture_payment_id_foreign" FOREIGN KEY ("payment_id") REFERENCES "payment" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE IF EXISTS "refund"
|
||||
ADD CONSTRAINT "refund_payment_id_foreign" FOREIGN KEY ("payment_id") REFERENCES "payment" ("id") ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { BigNumberRawValue } from "@medusajs/types"
|
||||
import {
|
||||
BigNumber,
|
||||
MikroOrmBigNumberProperty,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import Payment from "./payment"
|
||||
|
||||
type OptionalCaptureProps = "created_at"
|
||||
|
||||
@Entity({ tableName: "capture" })
|
||||
export default class Capture {
|
||||
[OptionalProps]?: OptionalCaptureProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
amount: BigNumber | number
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_amount: BigNumberRawValue
|
||||
|
||||
@ManyToOne(() => Payment, {
|
||||
onDelete: "cascade",
|
||||
index: "IDX_capture_payment_id",
|
||||
fieldName: "payment_id",
|
||||
})
|
||||
payment!: Payment
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
index: "IDX_capture_deleted_at",
|
||||
})
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
created_by: string | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "capt")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "capt")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export { default as Capture } from "./capture"
|
||||
export { default as Payment } from "./payment"
|
||||
export { default as PaymentCollection } from "./payment-collection"
|
||||
export { default as PaymentMethodToken } from "./payment-method-token"
|
||||
export { default as PaymentProvider } from "./payment-provider"
|
||||
export { default as PaymentSession } from "./payment-session"
|
||||
export { default as Refund } from "./refund"
|
||||
@@ -0,0 +1,108 @@
|
||||
import { BigNumberRawValue, DAL } from "@medusajs/types"
|
||||
import {
|
||||
BigNumber,
|
||||
DALUtils,
|
||||
MikroOrmBigNumberProperty,
|
||||
PaymentCollectionStatus,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Cascade,
|
||||
Collection,
|
||||
Entity,
|
||||
Enum,
|
||||
Filter,
|
||||
ManyToMany,
|
||||
OnInit,
|
||||
OneToMany,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import Payment from "./payment"
|
||||
import PaymentProvider from "./payment-provider"
|
||||
import PaymentSession from "./payment-session"
|
||||
|
||||
type OptionalPaymentCollectionProps = "status" | DAL.EntityDateColumns
|
||||
|
||||
@Entity({ tableName: "payment_collection" })
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class PaymentCollection {
|
||||
[OptionalProps]?: OptionalPaymentCollectionProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
currency_code: string
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
amount: BigNumber | number
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_amount: BigNumberRawValue
|
||||
|
||||
@Property({ columnType: "text", index: "IDX_payment_collection_region_id" })
|
||||
region_id: string
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
index: "IDX_payment_collection_deleted_at",
|
||||
})
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
})
|
||||
completed_at: Date | null = null
|
||||
|
||||
@Enum({
|
||||
items: () => PaymentCollectionStatus,
|
||||
default: PaymentCollectionStatus.NOT_PAID,
|
||||
})
|
||||
status: PaymentCollectionStatus = PaymentCollectionStatus.NOT_PAID
|
||||
|
||||
@ManyToMany(() => PaymentProvider)
|
||||
payment_providers = new Collection<PaymentProvider>(this)
|
||||
|
||||
@OneToMany(() => PaymentSession, (ps) => ps.payment_collection, {
|
||||
cascade: [Cascade.PERSIST, "soft-remove"] as any,
|
||||
})
|
||||
payment_sessions = new Collection<PaymentSession>(this)
|
||||
|
||||
@OneToMany(() => Payment, (payment) => payment.payment_collection, {
|
||||
cascade: [Cascade.PERSIST, "soft-remove"] as any,
|
||||
})
|
||||
payments = new Collection<Payment>(this)
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "pay_col")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "pay_col")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { generateEntityId } from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
OnInit,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
|
||||
@Entity({ tableName: "payment_method_token" })
|
||||
export default class PaymentMethodToken {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
provider_id: string
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
data: Record<string, unknown> | null = null
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
name: string
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
type_detail: string | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
description_detail: string | null = null
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
index: "IDX_payment_metod_token_deleted_at",
|
||||
})
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "paymttok")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "paymttok")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Entity, OptionalProps, PrimaryKey, Property } from "@mikro-orm/core"
|
||||
|
||||
@Entity({ tableName: "payment_provider" })
|
||||
export default class PaymentProvider {
|
||||
[OptionalProps]?: "is_enabled"
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({
|
||||
default: true,
|
||||
columnType: "boolean",
|
||||
})
|
||||
is_enabled: boolean = true
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { BigNumberRawValue } from "@medusajs/types"
|
||||
import {
|
||||
BigNumber,
|
||||
generateEntityId,
|
||||
MikroOrmBigNumberProperty,
|
||||
PaymentSessionStatus,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
Enum,
|
||||
ManyToOne,
|
||||
OneToOne,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import Payment from "./payment"
|
||||
import PaymentCollection from "./payment-collection"
|
||||
|
||||
@Entity({ tableName: "payment_session" })
|
||||
export default class PaymentSession {
|
||||
[OptionalProps]?: "status" | "data"
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
currency_code: string
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
amount: BigNumber | number
|
||||
|
||||
@Property({
|
||||
columnType: "jsonb",
|
||||
})
|
||||
raw_amount: BigNumberRawValue
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
provider_id: string
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
data: Record<string, unknown> = {}
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
context: Record<string, unknown> | null
|
||||
|
||||
@Enum({
|
||||
items: () => PaymentSessionStatus,
|
||||
})
|
||||
status: PaymentSessionStatus = PaymentSessionStatus.PENDING
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
})
|
||||
authorized_at: Date | null = null
|
||||
|
||||
@ManyToOne(() => PaymentCollection, {
|
||||
persist: false,
|
||||
})
|
||||
payment_collection: PaymentCollection
|
||||
|
||||
@ManyToOne({
|
||||
entity: () => PaymentCollection,
|
||||
columnType: "text",
|
||||
index: "IDX_payment_session_payment_collection_id",
|
||||
fieldName: "payment_collection_id",
|
||||
mapToPk: true,
|
||||
})
|
||||
payment_collection_id: string
|
||||
|
||||
@OneToOne({
|
||||
entity: () => Payment,
|
||||
nullable: true,
|
||||
mappedBy: "payment_session",
|
||||
})
|
||||
payment?: Payment | null
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
index: "IDX_payment_session_deleted_at",
|
||||
})
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "payses")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "payses")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import { BigNumberRawValue, DAL } from "@medusajs/types"
|
||||
import {
|
||||
BigNumber,
|
||||
DALUtils,
|
||||
MikroOrmBigNumberProperty,
|
||||
Searchable,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Cascade,
|
||||
Collection,
|
||||
Entity,
|
||||
Filter,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import Capture from "./capture"
|
||||
import PaymentCollection from "./payment-collection"
|
||||
import PaymentSession from "./payment-session"
|
||||
import Refund from "./refund"
|
||||
|
||||
type OptionalPaymentProps = DAL.EntityDateColumns
|
||||
|
||||
@Entity({ tableName: "payment" })
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class Payment {
|
||||
[OptionalProps]?: OptionalPaymentProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
amount: BigNumber | number
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_amount: BigNumberRawValue
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
currency_code: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
provider_id: string
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
cart_id: string | null = null
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
order_id: string | null = null
|
||||
|
||||
@Searchable()
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
customer_id: string | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
data: Record<string, unknown> | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
index: "IDX_payment_deleted_at",
|
||||
})
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
})
|
||||
captured_at: Date | null = null
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
})
|
||||
canceled_at: Date | null = null
|
||||
|
||||
@OneToMany(() => Refund, (refund) => refund.payment, {
|
||||
cascade: [Cascade.REMOVE],
|
||||
})
|
||||
refunds = new Collection<Refund>(this)
|
||||
|
||||
@OneToMany(() => Capture, (capture) => capture.payment, {
|
||||
cascade: [Cascade.REMOVE],
|
||||
})
|
||||
captures = new Collection<Capture>(this)
|
||||
|
||||
@ManyToOne({
|
||||
entity: () => PaymentCollection,
|
||||
persist: false,
|
||||
})
|
||||
payment_collection: PaymentCollection
|
||||
|
||||
@ManyToOne({
|
||||
entity: () => PaymentCollection,
|
||||
columnType: "text",
|
||||
index: "IDX_payment_payment_collection_id",
|
||||
fieldName: "payment_collection_id",
|
||||
mapToPk: true,
|
||||
})
|
||||
payment_collection_id: string
|
||||
|
||||
@OneToOne({
|
||||
owner: true,
|
||||
fieldName: "payment_session_id",
|
||||
index: "IDX_payment_payment_session_id",
|
||||
})
|
||||
payment_session: PaymentSession
|
||||
|
||||
/** COMPUTED PROPERTIES START **/
|
||||
|
||||
captured_amount: number // sum of the associated captures
|
||||
refunded_amount: number // sum of the associated refunds
|
||||
|
||||
/** COMPUTED PROPERTIES END **/
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "pay")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "pay")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { BigNumberRawValue } from "@medusajs/types"
|
||||
import {
|
||||
BigNumber,
|
||||
MikroOrmBigNumberProperty,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
BeforeCreate,
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import Payment from "./payment"
|
||||
|
||||
@Entity({ tableName: "refund" })
|
||||
export default class Refund {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@MikroOrmBigNumberProperty()
|
||||
amount: BigNumber | number
|
||||
|
||||
@Property({ columnType: "jsonb" })
|
||||
raw_amount: BigNumberRawValue
|
||||
|
||||
@ManyToOne(() => Payment, {
|
||||
onDelete: "cascade",
|
||||
index: "IDX_refund_payment_id",
|
||||
fieldName: "payment_id",
|
||||
})
|
||||
payment!: Payment
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
defaultRaw: "now()",
|
||||
})
|
||||
updated_at: Date
|
||||
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
index: "IDX_refund_deleted_at",
|
||||
})
|
||||
deleted_at: Date | null = null
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
created_by: string | null = null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "ref")
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "ref")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { ModuleExports } from "@medusajs/types"
|
||||
|
||||
import { PaymentModuleService } from "@services"
|
||||
|
||||
import loadConnection from "./loaders/connection"
|
||||
import loadContainer from "./loaders/container"
|
||||
import loadProviders from "./loaders/providers"
|
||||
import loadDefaults from "./loaders/defaults"
|
||||
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
|
||||
import * as PaymentModels from "@models"
|
||||
|
||||
const migrationScriptOptions = {
|
||||
moduleName: Modules.PAYMENT,
|
||||
models: PaymentModels,
|
||||
pathToMigrations: __dirname + "/migrations",
|
||||
}
|
||||
|
||||
export const runMigrations = ModulesSdkUtils.buildMigrationScript(
|
||||
migrationScriptOptions
|
||||
)
|
||||
export const revertMigration = ModulesSdkUtils.buildRevertMigrationScript(
|
||||
migrationScriptOptions
|
||||
)
|
||||
|
||||
const service = PaymentModuleService
|
||||
const loaders = [
|
||||
loadContainer,
|
||||
loadConnection,
|
||||
loadProviders,
|
||||
loadDefaults,
|
||||
] as any
|
||||
|
||||
export const moduleDefinition: ModuleExports = {
|
||||
service,
|
||||
loaders,
|
||||
runMigrations,
|
||||
revertMigration,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as SystemPaymentProvider } from "./system"
|
||||
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
CreatePaymentProviderSession,
|
||||
PaymentProviderError,
|
||||
PaymentProviderSessionResponse,
|
||||
PaymentSessionStatus,
|
||||
ProviderWebhookPayload,
|
||||
WebhookActionResult,
|
||||
} from "@medusajs/types"
|
||||
import { AbstractPaymentProvider, PaymentActions } from "@medusajs/utils"
|
||||
|
||||
export class SystemProviderService extends AbstractPaymentProvider {
|
||||
static identifier = "system"
|
||||
static PROVIDER = "system"
|
||||
|
||||
async getStatus(_): Promise<string> {
|
||||
return "authorized"
|
||||
}
|
||||
|
||||
async getPaymentData(_): Promise<Record<string, unknown>> {
|
||||
return {}
|
||||
}
|
||||
|
||||
async initiatePayment(
|
||||
context: CreatePaymentProviderSession
|
||||
): Promise<PaymentProviderSessionResponse> {
|
||||
return { data: {} }
|
||||
}
|
||||
|
||||
async getPaymentStatus(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<PaymentSessionStatus> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
|
||||
async retrievePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProviderError> {
|
||||
return {}
|
||||
}
|
||||
|
||||
async authorizePayment(_): Promise<
|
||||
| PaymentProviderError
|
||||
| {
|
||||
status: PaymentSessionStatus
|
||||
data: PaymentProviderSessionResponse["data"]
|
||||
}
|
||||
> {
|
||||
return { data: {}, status: PaymentSessionStatus.AUTHORIZED }
|
||||
}
|
||||
|
||||
async updatePayment(
|
||||
_
|
||||
): Promise<PaymentProviderError | PaymentProviderSessionResponse> {
|
||||
return { data: {} } as PaymentProviderSessionResponse
|
||||
}
|
||||
|
||||
async deletePayment(_): Promise<Record<string, unknown>> {
|
||||
return {}
|
||||
}
|
||||
|
||||
async capturePayment(_): Promise<Record<string, unknown>> {
|
||||
return {}
|
||||
}
|
||||
|
||||
async refundPayment(_): Promise<Record<string, unknown>> {
|
||||
return {}
|
||||
}
|
||||
|
||||
async cancelPayment(_): Promise<Record<string, unknown>> {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getWebhookActionAndData(
|
||||
data: ProviderWebhookPayload["payload"]
|
||||
): Promise<WebhookActionResult> {
|
||||
return { action: PaymentActions.NOT_SUPPORTED }
|
||||
}
|
||||
}
|
||||
|
||||
export default SystemProviderService
|
||||
@@ -0,0 +1 @@
|
||||
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { EOL } from "os"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
|
||||
import * as PaymentModels from "@models"
|
||||
|
||||
import { createPayments } from "../seed-utils"
|
||||
|
||||
const args = process.argv
|
||||
const path = args.pop() as string
|
||||
|
||||
export default (async () => {
|
||||
const { config } = await import("dotenv")
|
||||
config()
|
||||
if (!path) {
|
||||
throw new Error(
|
||||
`filePath is required.${EOL}Example: medusa-payment-seed <filePath>`
|
||||
)
|
||||
}
|
||||
|
||||
const run = ModulesSdkUtils.buildSeedScript({
|
||||
moduleName: Modules.PAYMENT,
|
||||
models: PaymentModels,
|
||||
pathToMigrations: __dirname + "/../../migrations",
|
||||
seedHandler: async ({ manager, data }) => {
|
||||
const { paymentsData } = data
|
||||
await createPayments(manager, paymentsData)
|
||||
},
|
||||
})
|
||||
await run({ path })
|
||||
})()
|
||||
@@ -0,0 +1,8 @@
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
|
||||
export async function createPayments(
|
||||
manager: SqlEntityManager,
|
||||
paymentsData: any[]
|
||||
): Promise<any[]> {
|
||||
return []
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default as PaymentModuleService } from "./payment-module"
|
||||
export { default as PaymentProviderService } from "./payment-provider"
|
||||
|
||||
@@ -0,0 +1,759 @@
|
||||
import {
|
||||
CaptureDTO,
|
||||
Context,
|
||||
CreateCaptureDTO,
|
||||
CreatePaymentCollectionDTO,
|
||||
CreatePaymentSessionDTO,
|
||||
CreateRefundDTO,
|
||||
DAL,
|
||||
FilterablePaymentCollectionProps,
|
||||
FilterablePaymentProviderProps,
|
||||
FindConfig,
|
||||
InternalModuleDeclaration,
|
||||
IPaymentModuleService,
|
||||
ModuleJoinerConfig,
|
||||
ModulesSdkTypes,
|
||||
PaymentCollectionDTO,
|
||||
PaymentCollectionUpdatableFields,
|
||||
PaymentDTO,
|
||||
PaymentProviderDTO,
|
||||
PaymentSessionDTO,
|
||||
PaymentSessionStatus,
|
||||
ProviderWebhookPayload,
|
||||
RefundDTO,
|
||||
UpdatePaymentCollectionDTO,
|
||||
UpdatePaymentDTO,
|
||||
UpdatePaymentSessionDTO,
|
||||
UpsertPaymentCollectionDTO,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
BigNumber,
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
isString,
|
||||
MathBN,
|
||||
MedusaContext,
|
||||
MedusaError,
|
||||
ModulesSdkUtils,
|
||||
PaymentActions,
|
||||
promiseAll,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
Capture,
|
||||
Payment,
|
||||
PaymentCollection,
|
||||
PaymentSession,
|
||||
Refund,
|
||||
} from "@models"
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||
import PaymentProviderService from "./payment-provider"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
paymentService: ModulesSdkTypes.InternalModuleService<any>
|
||||
captureService: ModulesSdkTypes.InternalModuleService<any>
|
||||
refundService: ModulesSdkTypes.InternalModuleService<any>
|
||||
paymentSessionService: ModulesSdkTypes.InternalModuleService<any>
|
||||
paymentCollectionService: ModulesSdkTypes.InternalModuleService<any>
|
||||
paymentProviderService: PaymentProviderService
|
||||
}
|
||||
|
||||
const generateMethodForModels = [
|
||||
PaymentCollection,
|
||||
Payment,
|
||||
PaymentSession,
|
||||
Capture,
|
||||
Refund,
|
||||
]
|
||||
|
||||
export default class PaymentModuleService<
|
||||
TPaymentCollection extends PaymentCollection = PaymentCollection,
|
||||
TPayment extends Payment = Payment,
|
||||
TCapture extends Capture = Capture,
|
||||
TRefund extends Refund = Refund,
|
||||
TPaymentSession extends PaymentSession = PaymentSession
|
||||
>
|
||||
extends ModulesSdkUtils.abstractModuleServiceFactory<
|
||||
InjectedDependencies,
|
||||
PaymentCollectionDTO,
|
||||
{
|
||||
PaymentCollection: { dto: PaymentCollectionDTO }
|
||||
PaymentSession: { dto: PaymentSessionDTO }
|
||||
Payment: { dto: PaymentDTO }
|
||||
Capture: { dto: CaptureDTO }
|
||||
Refund: { dto: RefundDTO }
|
||||
}
|
||||
>(PaymentCollection, generateMethodForModels, entityNameToLinkableKeysMap)
|
||||
implements IPaymentModuleService
|
||||
{
|
||||
protected baseRepository_: DAL.RepositoryService
|
||||
|
||||
protected paymentService_: ModulesSdkTypes.InternalModuleService<TPayment>
|
||||
protected captureService_: ModulesSdkTypes.InternalModuleService<TCapture>
|
||||
protected refundService_: ModulesSdkTypes.InternalModuleService<TRefund>
|
||||
protected paymentSessionService_: ModulesSdkTypes.InternalModuleService<TPaymentSession>
|
||||
protected paymentCollectionService_: ModulesSdkTypes.InternalModuleService<TPaymentCollection>
|
||||
protected paymentProviderService_: PaymentProviderService
|
||||
|
||||
constructor(
|
||||
{
|
||||
baseRepository,
|
||||
paymentService,
|
||||
captureService,
|
||||
refundService,
|
||||
paymentSessionService,
|
||||
paymentProviderService,
|
||||
paymentCollectionService,
|
||||
}: InjectedDependencies,
|
||||
protected readonly moduleDeclaration: InternalModuleDeclaration
|
||||
) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
|
||||
this.baseRepository_ = baseRepository
|
||||
|
||||
this.refundService_ = refundService
|
||||
this.captureService_ = captureService
|
||||
this.paymentService_ = paymentService
|
||||
this.paymentSessionService_ = paymentSessionService
|
||||
this.paymentProviderService_ = paymentProviderService
|
||||
this.paymentCollectionService_ = paymentCollectionService
|
||||
}
|
||||
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
return joinerConfig
|
||||
}
|
||||
|
||||
createPaymentCollections(
|
||||
data: CreatePaymentCollectionDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO>
|
||||
|
||||
createPaymentCollections(
|
||||
data: CreatePaymentCollectionDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO[]>
|
||||
@InjectManager("baseRepository_")
|
||||
async createPaymentCollections(
|
||||
data: CreatePaymentCollectionDTO | CreatePaymentCollectionDTO[],
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO | PaymentCollectionDTO[]> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
|
||||
const collections = await this.createPaymentCollections_(
|
||||
input,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize<PaymentCollectionDTO[]>(
|
||||
Array.isArray(data) ? collections : collections[0],
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async createPaymentCollections_(
|
||||
data: CreatePaymentCollectionDTO[],
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentCollection[]> {
|
||||
return this.paymentCollectionService_.create(data, sharedContext)
|
||||
}
|
||||
|
||||
updatePaymentCollections(
|
||||
paymentCollectionId: string,
|
||||
data: PaymentCollectionUpdatableFields,
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO>
|
||||
updatePaymentCollections(
|
||||
selector: FilterablePaymentCollectionProps,
|
||||
data: PaymentCollectionUpdatableFields,
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO[]>
|
||||
@InjectManager("baseRepository_")
|
||||
async updatePaymentCollections(
|
||||
idOrSelector: string | FilterablePaymentCollectionProps,
|
||||
data: PaymentCollectionUpdatableFields,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO | PaymentCollectionDTO[]> {
|
||||
let updateData: UpdatePaymentCollectionDTO[] = []
|
||||
|
||||
if (isString(idOrSelector)) {
|
||||
updateData = [
|
||||
{
|
||||
id: idOrSelector,
|
||||
...data,
|
||||
},
|
||||
]
|
||||
} else {
|
||||
const collections = await this.paymentCollectionService_.list(
|
||||
idOrSelector,
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
updateData = collections.map((c) => ({
|
||||
id: c.id,
|
||||
...data,
|
||||
}))
|
||||
}
|
||||
|
||||
const result = await this.updatePaymentCollections_(
|
||||
updateData,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize<PaymentCollectionDTO[]>(
|
||||
Array.isArray(data) ? result : result[0],
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async updatePaymentCollections_(
|
||||
data: UpdatePaymentCollectionDTO[],
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentCollection[]> {
|
||||
return await this.paymentCollectionService_.update(data, sharedContext)
|
||||
}
|
||||
|
||||
upsertPaymentCollections(
|
||||
data: UpsertPaymentCollectionDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO[]>
|
||||
upsertPaymentCollections(
|
||||
data: UpsertPaymentCollectionDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO>
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async upsertPaymentCollections(
|
||||
data: UpsertPaymentCollectionDTO | UpsertPaymentCollectionDTO[],
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO | PaymentCollectionDTO[]> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
const forUpdate = input.filter(
|
||||
(collection): collection is UpdatePaymentCollectionDTO => !!collection.id
|
||||
)
|
||||
const forCreate = input.filter(
|
||||
(collection): collection is CreatePaymentCollectionDTO => !collection.id
|
||||
)
|
||||
|
||||
const operations: Promise<PaymentCollection[]>[] = []
|
||||
|
||||
if (forCreate.length) {
|
||||
operations.push(this.createPaymentCollections_(forCreate, sharedContext))
|
||||
}
|
||||
if (forUpdate.length) {
|
||||
operations.push(this.updatePaymentCollections_(forUpdate, sharedContext))
|
||||
}
|
||||
|
||||
const result = (await promiseAll(operations)).flat()
|
||||
|
||||
return await this.baseRepository_.serialize<
|
||||
PaymentCollectionDTO[] | PaymentCollectionDTO
|
||||
>(Array.isArray(data) ? result : result[0])
|
||||
}
|
||||
|
||||
completePaymentCollections(
|
||||
paymentCollectionId: string,
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO>
|
||||
completePaymentCollections(
|
||||
paymentCollectionId: string[],
|
||||
sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO[]>
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async completePaymentCollections(
|
||||
paymentCollectionId: string | string[],
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentCollectionDTO | PaymentCollectionDTO[]> {
|
||||
const input = Array.isArray(paymentCollectionId)
|
||||
? paymentCollectionId.map((id) => ({
|
||||
id,
|
||||
completed_at: new Date(),
|
||||
}))
|
||||
: [{ id: paymentCollectionId, completed_at: new Date() }]
|
||||
|
||||
// TODO: what checks should be done here? e.g. captured_amount === amount?
|
||||
|
||||
const updated = await this.paymentCollectionService_.update(
|
||||
input,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize(
|
||||
Array.isArray(paymentCollectionId) ? updated : updated[0],
|
||||
{ populate: true }
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async createPaymentSession(
|
||||
paymentCollectionId: string,
|
||||
input: CreatePaymentSessionDTO,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentSessionDTO> {
|
||||
let paymentSession: PaymentSession
|
||||
|
||||
try {
|
||||
const providerSessionSession =
|
||||
await this.paymentProviderService_.createSession(input.provider_id, {
|
||||
context: input.context ?? {},
|
||||
amount: input.amount,
|
||||
currency_code: input.currency_code,
|
||||
})
|
||||
|
||||
input.data = {
|
||||
...input.data,
|
||||
...providerSessionSession,
|
||||
}
|
||||
|
||||
paymentSession = await this.createPaymentSession_(
|
||||
paymentCollectionId,
|
||||
input,
|
||||
sharedContext
|
||||
)
|
||||
} catch (error) {
|
||||
// In case the session is created at the provider, but fails to be created in Medusa,
|
||||
// we catch the error and delete the session at the provider and rethrow.
|
||||
await this.paymentProviderService_.deleteSession({
|
||||
provider_id: input.provider_id,
|
||||
data: input.data,
|
||||
})
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
return await this.baseRepository_.serialize(paymentSession, {
|
||||
populate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async createPaymentSession_(
|
||||
paymentCollectionId: string,
|
||||
data: CreatePaymentSessionDTO,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentSession> {
|
||||
const paymentSession = await this.paymentSessionService_.create(
|
||||
{
|
||||
payment_collection_id: paymentCollectionId,
|
||||
provider_id: data.provider_id,
|
||||
amount: data.amount,
|
||||
currency_code: data.currency_code,
|
||||
context: data.context,
|
||||
data: data.data,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return paymentSession
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async updatePaymentSession(
|
||||
data: UpdatePaymentSessionDTO,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentSessionDTO> {
|
||||
const session = await this.paymentSessionService_.retrieve(
|
||||
data.id,
|
||||
{ select: ["id", "data", "provider_id"] },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const updated = await this.paymentSessionService_.update(
|
||||
{
|
||||
id: session.id,
|
||||
amount: data.amount,
|
||||
currency_code: data.currency_code,
|
||||
data: data.data,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize(updated[0], { populate: true })
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async deletePaymentSession(
|
||||
id: string,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<void> {
|
||||
const session = await this.paymentSessionService_.retrieve(
|
||||
id,
|
||||
{ select: ["id", "data", "provider_id"] },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
await this.paymentProviderService_.deleteSession({
|
||||
provider_id: session.provider_id,
|
||||
data: session.data,
|
||||
})
|
||||
|
||||
await this.paymentSessionService_.delete(id, sharedContext)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async authorizePaymentSession(
|
||||
id: string,
|
||||
context: Record<string, unknown>,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentDTO> {
|
||||
const session = await this.paymentSessionService_.retrieve(
|
||||
id,
|
||||
{
|
||||
select: [
|
||||
"id",
|
||||
"data",
|
||||
"provider_id",
|
||||
"amount",
|
||||
"currency_code",
|
||||
"payment_collection_id",
|
||||
],
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
// this method needs to be idempotent
|
||||
if (session.authorized_at) {
|
||||
const payment = await this.paymentService_.retrieve(
|
||||
{ session_id: session.id },
|
||||
{ relations: ["payment_collection"] },
|
||||
sharedContext
|
||||
)
|
||||
return await this.baseRepository_.serialize(payment, { populate: true })
|
||||
}
|
||||
|
||||
const { data, status } =
|
||||
await this.paymentProviderService_.authorizePayment(
|
||||
{
|
||||
provider_id: session.provider_id,
|
||||
data: session.data,
|
||||
},
|
||||
context
|
||||
)
|
||||
|
||||
await this.paymentSessionService_.update(
|
||||
{
|
||||
id: session.id,
|
||||
data,
|
||||
status,
|
||||
authorized_at:
|
||||
status === PaymentSessionStatus.AUTHORIZED ? new Date() : null,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if (status !== PaymentSessionStatus.AUTHORIZED) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Session: ${session.id} is not authorized with the provider.`
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: update status on payment collection if authorized_amount === amount - depends on the BigNumber PR
|
||||
|
||||
const payment = await this.paymentService_.create(
|
||||
{
|
||||
amount: session.amount,
|
||||
currency_code: session.currency_code,
|
||||
payment_session: session.id,
|
||||
payment_collection_id: session.payment_collection_id,
|
||||
provider_id: session.provider_id,
|
||||
// customer_id: context.customer.id,
|
||||
data,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.retrievePayment(
|
||||
payment.id,
|
||||
{ relations: ["payment_collection"] },
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async updatePayment(
|
||||
data: UpdatePaymentDTO,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentDTO> {
|
||||
// NOTE: currently there is no update with the provider but maybe data could be updated
|
||||
const result = await this.paymentService_.update(data, sharedContext)
|
||||
|
||||
return await this.baseRepository_.serialize<PaymentDTO>(result[0], {
|
||||
populate: true,
|
||||
})
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async capturePayment(
|
||||
data: CreateCaptureDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<PaymentDTO> {
|
||||
const payment = await this.paymentService_.retrieve(
|
||||
data.payment_id,
|
||||
{
|
||||
select: [
|
||||
"id",
|
||||
"data",
|
||||
"provider_id",
|
||||
"amount",
|
||||
"raw_amount",
|
||||
"canceled_at",
|
||||
],
|
||||
relations: ["captures.raw_amount"],
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
// If no custom amount is passed, we assume the full amount needs to be captured
|
||||
if (!data.amount) {
|
||||
data.amount = payment.amount as number
|
||||
}
|
||||
|
||||
if (payment.canceled_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`The payment: ${payment.id} has been canceled.`
|
||||
)
|
||||
}
|
||||
|
||||
if (payment.captured_at) {
|
||||
return await this.retrievePayment(
|
||||
data.payment_id,
|
||||
{ relations: ["captures"] },
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
const capturedAmount = payment.captures.reduce((captureAmount, next) => {
|
||||
return MathBN.add(captureAmount, next.raw_amount)
|
||||
}, MathBN.convert(0))
|
||||
|
||||
const authorizedAmount = new BigNumber(payment.raw_amount)
|
||||
const newCaptureAmount = new BigNumber(data.amount)
|
||||
const remainingToCapture = MathBN.sub(authorizedAmount, capturedAmount)
|
||||
|
||||
if (MathBN.gt(newCaptureAmount, remainingToCapture)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`You cannot capture more than the authorized amount substracted by what is already captured.`
|
||||
)
|
||||
}
|
||||
|
||||
const paymentData = await this.paymentProviderService_.capturePayment({
|
||||
data: payment.data!,
|
||||
provider_id: payment.provider_id,
|
||||
})
|
||||
|
||||
await this.captureService_.create(
|
||||
{
|
||||
payment: data.payment_id,
|
||||
amount: data.amount,
|
||||
captured_by: data.captured_by,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
await this.paymentService_.update(
|
||||
{ id: payment.id, data: paymentData },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
// When the entire authorized amount has been captured, we mark it fully capture by setting the captured_at field
|
||||
const totalCaptured = MathBN.convert(
|
||||
MathBN.add(capturedAmount, newCaptureAmount)
|
||||
)
|
||||
if (MathBN.gte(totalCaptured, authorizedAmount)) {
|
||||
await this.paymentService_.update(
|
||||
{ id: payment.id, captured_at: new Date() },
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
return await this.retrievePayment(
|
||||
payment.id,
|
||||
{ relations: ["captures"] },
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async refundPayment(
|
||||
data: CreateRefundDTO,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentDTO> {
|
||||
const payment = await this.paymentService_.retrieve(
|
||||
data.payment_id,
|
||||
{
|
||||
select: ["id", "data", "provider_id", "amount", "raw_amount"],
|
||||
relations: ["captures.raw_amount"],
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
if (!data.amount) {
|
||||
data.amount = payment.amount as number
|
||||
}
|
||||
|
||||
const capturedAmount = payment.captures.reduce((captureAmount, next) => {
|
||||
const amountAsBigNumber = new BigNumber(next.raw_amount)
|
||||
return MathBN.add(captureAmount, amountAsBigNumber)
|
||||
}, MathBN.convert(0))
|
||||
const refundAmount = new BigNumber(data.amount)
|
||||
|
||||
if (MathBN.lt(capturedAmount, refundAmount)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`You cannot refund more than what is captured on the payment.`
|
||||
)
|
||||
}
|
||||
|
||||
const paymentData = await this.paymentProviderService_.refundPayment(
|
||||
{
|
||||
data: payment.data!,
|
||||
provider_id: payment.provider_id,
|
||||
},
|
||||
data.amount as number
|
||||
)
|
||||
|
||||
await this.refundService_.create(
|
||||
{
|
||||
payment: data.payment_id,
|
||||
amount: data.amount,
|
||||
created_by: data.created_by,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
await this.paymentService_.update(
|
||||
{ id: payment.id, data: paymentData },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.retrievePayment(
|
||||
payment.id,
|
||||
{ relations: ["refunds"] },
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async cancelPayment(
|
||||
paymentId: string,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentDTO> {
|
||||
const payment = await this.paymentService_.retrieve(
|
||||
paymentId,
|
||||
{ select: ["id", "data", "provider_id"] },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
// TODO: revisit when totals are implemented
|
||||
// if (payment.captured_amount !== 0) {
|
||||
// throw new MedusaError(
|
||||
// MedusaError.Types.INVALID_DATA,
|
||||
// `Cannot cancel a payment: ${payment.id} that has been captured.`
|
||||
// )
|
||||
// }
|
||||
|
||||
await this.paymentProviderService_.cancelPayment({
|
||||
data: payment.data!,
|
||||
provider_id: payment.provider_id,
|
||||
})
|
||||
|
||||
await this.paymentService_.update(
|
||||
{ id: paymentId, canceled_at: new Date() },
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.retrievePayment(payment.id, {}, sharedContext)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async processEvent(
|
||||
eventData: ProviderWebhookPayload,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<void> {
|
||||
const providerId = `pp_${eventData.provider}`
|
||||
|
||||
const event = await this.paymentProviderService_.getWebhookActionAndData(
|
||||
providerId,
|
||||
eventData.payload
|
||||
)
|
||||
|
||||
if (event.action === PaymentActions.NOT_SUPPORTED) {
|
||||
return
|
||||
}
|
||||
|
||||
switch (event.action) {
|
||||
case PaymentActions.SUCCESSFUL: {
|
||||
const [payment] = await this.listPayments(
|
||||
{
|
||||
session_id: event.data.resource_id,
|
||||
},
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
await this.capturePayment(
|
||||
{ payment_id: payment.id, amount: event.data.amount },
|
||||
sharedContext
|
||||
)
|
||||
break
|
||||
}
|
||||
case PaymentActions.AUTHORIZED:
|
||||
await this.authorizePaymentSession(
|
||||
event.data.resource_id as string,
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listPaymentProviders(
|
||||
filters: FilterablePaymentProviderProps = {},
|
||||
config: FindConfig<PaymentProviderDTO> = {},
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentProviderDTO[]> {
|
||||
const providers = await this.paymentProviderService_.list(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return await this.baseRepository_.serialize<PaymentProviderDTO[]>(
|
||||
providers,
|
||||
{
|
||||
populate: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async listAndCountPaymentProviders(
|
||||
filters: FilterablePaymentProviderProps = {},
|
||||
config: FindConfig<PaymentProviderDTO> = {},
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<[PaymentProviderDTO[], number]> {
|
||||
const [providers, count] = await this.paymentProviderService_.listAndCount(
|
||||
filters,
|
||||
config,
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return [
|
||||
await this.baseRepository_.serialize<PaymentProviderDTO[]>(providers, {
|
||||
populate: true,
|
||||
}),
|
||||
count,
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
import {
|
||||
Context,
|
||||
CreatePaymentProviderDTO,
|
||||
CreatePaymentProviderSession,
|
||||
DAL,
|
||||
FilterablePaymentProviderProps,
|
||||
FindConfig,
|
||||
InternalModuleDeclaration,
|
||||
IPaymentProvider,
|
||||
PaymentProviderAuthorizeResponse,
|
||||
PaymentProviderDataInput,
|
||||
PaymentProviderDTO,
|
||||
PaymentProviderError,
|
||||
PaymentProviderSessionResponse,
|
||||
PaymentSessionStatus,
|
||||
ProviderWebhookPayload,
|
||||
UpdatePaymentProviderSession,
|
||||
WebhookActionResult,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
isPaymentProviderError,
|
||||
MedusaContext,
|
||||
ModulesSdkUtils,
|
||||
} from "@medusajs/utils"
|
||||
import { PaymentProvider } from "@models"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { EOL } from "os"
|
||||
|
||||
type InjectedDependencies = {
|
||||
paymentProviderRepository: DAL.RepositoryService
|
||||
[key: `pp_${string}`]: IPaymentProvider
|
||||
}
|
||||
|
||||
export default class PaymentProviderService {
|
||||
protected readonly container_: InjectedDependencies
|
||||
protected readonly paymentProviderRepository_: DAL.RepositoryService
|
||||
|
||||
constructor(
|
||||
container: InjectedDependencies,
|
||||
|
||||
protected readonly moduleDeclaration: InternalModuleDeclaration
|
||||
) {
|
||||
this.container_ = container
|
||||
this.paymentProviderRepository_ = container.paymentProviderRepository
|
||||
}
|
||||
|
||||
@InjectTransactionManager("paymentProviderRepository_")
|
||||
async create(
|
||||
data: CreatePaymentProviderDTO[],
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentProvider[]> {
|
||||
return await this.paymentProviderRepository_.create(data, sharedContext)
|
||||
}
|
||||
|
||||
@InjectManager("paymentProviderRepository_")
|
||||
async list(
|
||||
filters: FilterablePaymentProviderProps,
|
||||
config: FindConfig<PaymentProviderDTO>,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<PaymentProvider[]> {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<PaymentProvider>(
|
||||
filters,
|
||||
config
|
||||
)
|
||||
|
||||
return await this.paymentProviderRepository_.find(
|
||||
queryOptions,
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
@InjectManager("paymentProviderRepository_")
|
||||
async listAndCount(
|
||||
filters: FilterablePaymentProviderProps,
|
||||
config: FindConfig<PaymentProviderDTO>,
|
||||
@MedusaContext() sharedContext?: Context
|
||||
): Promise<[PaymentProvider[], number]> {
|
||||
const queryOptions = ModulesSdkUtils.buildQuery<PaymentProvider>(
|
||||
filters,
|
||||
config
|
||||
)
|
||||
|
||||
return await this.paymentProviderRepository_.findAndCount(
|
||||
queryOptions,
|
||||
sharedContext
|
||||
)
|
||||
}
|
||||
|
||||
retrieveProvider(providerId: string): IPaymentProvider {
|
||||
try {
|
||||
return this.container_[providerId] as IPaymentProvider
|
||||
} catch (e) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Could not find a payment provider with id: ${providerId}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async createSession(
|
||||
providerId: string,
|
||||
sessionInput: CreatePaymentProviderSession
|
||||
): Promise<PaymentProviderSessionResponse["data"]> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
|
||||
const paymentResponse = await provider.initiatePayment(sessionInput)
|
||||
|
||||
if (isPaymentProviderError(paymentResponse)) {
|
||||
this.throwPaymentProviderError(paymentResponse)
|
||||
}
|
||||
|
||||
return (paymentResponse as PaymentProviderSessionResponse).data
|
||||
}
|
||||
|
||||
async updateSession(
|
||||
providerId: string,
|
||||
sessionInput: UpdatePaymentProviderSession
|
||||
): Promise<Record<string, unknown> | undefined> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
|
||||
const paymentResponse = await provider.updatePayment(sessionInput)
|
||||
|
||||
if (isPaymentProviderError(paymentResponse)) {
|
||||
this.throwPaymentProviderError(paymentResponse)
|
||||
}
|
||||
|
||||
return (paymentResponse as PaymentProviderSessionResponse)?.data
|
||||
}
|
||||
|
||||
async deleteSession(input: PaymentProviderDataInput): Promise<void> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
|
||||
const error = await provider.deletePayment(input.data)
|
||||
if (isPaymentProviderError(error)) {
|
||||
this.throwPaymentProviderError(error)
|
||||
}
|
||||
}
|
||||
|
||||
async authorizePayment(
|
||||
input: PaymentProviderDataInput,
|
||||
context: Record<string, unknown>
|
||||
): Promise<{ data: Record<string, unknown>; status: PaymentSessionStatus }> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
|
||||
const res = await provider.authorizePayment(input.data, context)
|
||||
if (isPaymentProviderError(res)) {
|
||||
this.throwPaymentProviderError(res)
|
||||
}
|
||||
|
||||
const { data, status } = res as PaymentProviderAuthorizeResponse
|
||||
return { data, status }
|
||||
}
|
||||
|
||||
async getStatus(
|
||||
input: PaymentProviderDataInput
|
||||
): Promise<PaymentSessionStatus> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
return await provider.getPaymentStatus(input.data)
|
||||
}
|
||||
|
||||
async capturePayment(
|
||||
input: PaymentProviderDataInput
|
||||
): Promise<Record<string, unknown>> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
|
||||
const res = await provider.capturePayment(input.data)
|
||||
if (isPaymentProviderError(res)) {
|
||||
this.throwPaymentProviderError(res)
|
||||
}
|
||||
|
||||
return res as Record<string, unknown>
|
||||
}
|
||||
|
||||
async cancelPayment(input: PaymentProviderDataInput): Promise<void> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
|
||||
const error = await provider.cancelPayment(input.data)
|
||||
if (isPaymentProviderError(error)) {
|
||||
this.throwPaymentProviderError(error)
|
||||
}
|
||||
}
|
||||
|
||||
async refundPayment(
|
||||
input: PaymentProviderDataInput,
|
||||
amount: number
|
||||
): Promise<Record<string, unknown>> {
|
||||
const provider = this.retrieveProvider(input.provider_id)
|
||||
|
||||
const res = await provider.refundPayment(input.data, amount)
|
||||
if (isPaymentProviderError(res)) {
|
||||
this.throwPaymentProviderError(res)
|
||||
}
|
||||
|
||||
return res as Record<string, unknown>
|
||||
}
|
||||
|
||||
async getWebhookActionAndData(
|
||||
providerId: string,
|
||||
data: ProviderWebhookPayload["payload"]
|
||||
): Promise<WebhookActionResult> {
|
||||
const provider = this.retrieveProvider(providerId)
|
||||
|
||||
return await provider.getWebhookActionAndData(data)
|
||||
}
|
||||
|
||||
private throwPaymentProviderError(errObj: PaymentProviderError) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`${errObj.error}${errObj.detail ? `:${EOL}${errObj.detail}` : ""}`,
|
||||
errObj.code
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
Logger,
|
||||
ModuleProviderExports,
|
||||
ModuleServiceInitializeOptions,
|
||||
} from "@medusajs/types"
|
||||
|
||||
export type InitializeModuleInjectableDependencies = {
|
||||
logger?: Logger
|
||||
}
|
||||
|
||||
export type PaymentModuleOptions = Partial<ModuleServiceInitializeOptions> & {
|
||||
/**
|
||||
* Providers to be registered
|
||||
*/
|
||||
providers?: {
|
||||
/**
|
||||
* The module provider to be registered
|
||||
*/
|
||||
resolve: string | ModuleProviderExports
|
||||
options: {
|
||||
/**
|
||||
* key value pair of the provider name and the configuration to be passed to the provider constructor
|
||||
*/
|
||||
config: Record<string, unknown>
|
||||
}
|
||||
}[]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es2020"],
|
||||
"target": "es2020",
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"sourceMap": false,
|
||||
"noImplicitReturns": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitThis": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"downlevelIteration": true, // to use ES5 specific tooling
|
||||
"baseUrl": ".",
|
||||
"resolveJsonModule": true,
|
||||
"paths": {
|
||||
"@models": ["./src/models"],
|
||||
"@services": ["./src/services"],
|
||||
"@repositories": ["./src/repositories"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"./src/**/__tests__",
|
||||
"./src/**/__mocks__",
|
||||
"./src/**/__fixtures__",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["src", "integration-tests"],
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"compilerOptions": {
|
||||
"sourceMap": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user