feat(medusa): Payment Collection endpoints (#2525)
This commit is contained in:
1 parent
27184f3878
commit
755ba90c05
57 files changed
+1148
-442
No files matched your search
@@ -29,7 +29,7 @@ describe("/admin/collections", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd })
|
medusaProcess = await setupServer({ cwd, verbose: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const {
|
|||||||
} = require("../../factories")
|
} = require("../../factories")
|
||||||
const { OrderEditItemChangeType, OrderEdit } = require("@medusajs/medusa")
|
const { OrderEditItemChangeType, OrderEdit } = require("@medusajs/medusa")
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
const adminHeaders = {
|
const adminHeaders = {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -869,23 +869,82 @@ describe("[MEDUSA_FF_ORDER_EDITING] /admin/order-edits", () => {
|
|||||||
await adminSeeder(dbConnection)
|
await adminSeeder(dbConnection)
|
||||||
|
|
||||||
const product1 = await simpleProductFactory(dbConnection)
|
const product1 = await simpleProductFactory(dbConnection)
|
||||||
|
const product2 = await simpleProductFactory(dbConnection)
|
||||||
|
|
||||||
const { id, order_id } = await simpleOrderEditFactory(dbConnection, {
|
const order = await simpleOrderFactory(dbConnection, {
|
||||||
|
id: IdMap.getId("order-test-2"),
|
||||||
|
email: "test@testson.com",
|
||||||
|
tax_rate: null,
|
||||||
|
fulfillment_status: "fulfilled",
|
||||||
|
payment_status: "captured",
|
||||||
|
region: {
|
||||||
|
id: "test-region",
|
||||||
|
name: "Test region",
|
||||||
|
tax_rate: 0,
|
||||||
|
},
|
||||||
|
line_items: [
|
||||||
|
{
|
||||||
|
id: "lineItemId1",
|
||||||
|
variant_id: product2.variants[0].id,
|
||||||
|
quantity: 1,
|
||||||
|
fulfilled_quantity: 1,
|
||||||
|
shipped_quantity: 1,
|
||||||
|
unit_price: 1000,
|
||||||
|
tax_lines: [
|
||||||
|
{
|
||||||
|
rate: 10,
|
||||||
|
code: "code1",
|
||||||
|
name: "code1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
shipping_methods: [
|
||||||
|
{
|
||||||
|
shipping_option: {
|
||||||
|
name: "random",
|
||||||
|
region_id: "test-region",
|
||||||
|
},
|
||||||
|
price: 10,
|
||||||
|
tax_lines: [
|
||||||
|
{
|
||||||
|
rate: 0,
|
||||||
|
code: "code1",
|
||||||
|
name: "code1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const { id } = await simpleOrderEditFactory(dbConnection, {
|
||||||
created_by: "admin_user",
|
created_by: "admin_user",
|
||||||
|
order_id: order.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
const noChangesEdit = await simpleOrderEditFactory(dbConnection, {
|
const noChangesEdit = await simpleOrderEditFactory(dbConnection, {
|
||||||
created_by: "admin_user",
|
created_by: "admin_user",
|
||||||
})
|
})
|
||||||
|
|
||||||
await simpleLineItemFactory(dbConnection, {
|
const lineItemAdded = await simpleLineItemFactory(dbConnection, {
|
||||||
order_id: order_id,
|
order_id: null,
|
||||||
|
order_edit_id: id,
|
||||||
variant_id: product1.variants[0].id,
|
variant_id: product1.variants[0].id,
|
||||||
|
unit_price: 2000,
|
||||||
|
quantity: 1,
|
||||||
|
tax_lines: [
|
||||||
|
{
|
||||||
|
rate: 0,
|
||||||
|
code: "code1",
|
||||||
|
name: "code1",
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
await simpleOrderItemChangeFactory(dbConnection, {
|
await simpleOrderItemChangeFactory(dbConnection, {
|
||||||
order_edit_id: id,
|
order_edit_id: id,
|
||||||
type: "item_add",
|
type: OrderEditItemChangeType.ITEM_ADD,
|
||||||
|
line_item_id: lineItemAdded.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
orderEditId = id
|
orderEditId = id
|
||||||
@@ -900,6 +959,28 @@ describe("[MEDUSA_FF_ORDER_EDITING] /admin/order-edits", () => {
|
|||||||
it("requests order edit", async () => {
|
it("requests order edit", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
|
const result = await api.post(
|
||||||
|
`/admin/order-edits/${orderEditId}/request`,
|
||||||
|
{
|
||||||
|
payment_collection_description: "Payment collection description",
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(result.status).toEqual(200)
|
||||||
|
expect(result.data.order_edit).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: orderEditId,
|
||||||
|
requested_at: expect.any(String),
|
||||||
|
requested_by: "admin_user",
|
||||||
|
status: "requested",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("creates payment collection if difference_due > 0", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
const result = await api.post(
|
const result = await api.post(
|
||||||
`/admin/order-edits/${orderEditId}/request`,
|
`/admin/order-edits/${orderEditId}/request`,
|
||||||
{},
|
{},
|
||||||
@@ -913,6 +994,7 @@ describe("[MEDUSA_FF_ORDER_EDITING] /admin/order-edits", () => {
|
|||||||
requested_at: expect.any(String),
|
requested_at: expect.any(String),
|
||||||
requested_by: "admin_user",
|
requested_by: "admin_user",
|
||||||
status: "requested",
|
status: "requested",
|
||||||
|
payment_collection_id: expect.any(String),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -934,6 +1016,7 @@ describe("[MEDUSA_FF_ORDER_EDITING] /admin/order-edits", () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it("requests order edit", async () => {
|
it("requests order edit", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
@@ -2635,7 +2718,6 @@ describe("[MEDUSA_FF_ORDER_EDITING] /admin/order-edits", () => {
|
|||||||
const lineItemId2Discount = IdMap.getId("line-item-2-discount")
|
const lineItemId2Discount = IdMap.getId("line-item-2-discount")
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const api = useApi()
|
|
||||||
await adminSeeder(dbConnection)
|
await adminSeeder(dbConnection)
|
||||||
|
|
||||||
product = await simpleProductFactory(dbConnection, {
|
product = await simpleProductFactory(dbConnection, {
|
||||||
@@ -2646,7 +2728,7 @@ describe("[MEDUSA_FF_ORDER_EDITING] /admin/order-edits", () => {
|
|||||||
id: prodId2,
|
id: prodId2,
|
||||||
})
|
})
|
||||||
|
|
||||||
const reagion = await simpleRegionFactory(dbConnection, {
|
const region = await simpleRegionFactory(dbConnection, {
|
||||||
id: "test-region",
|
id: "test-region",
|
||||||
name: "Test region",
|
name: "Test region",
|
||||||
tax_rate: 12.5,
|
tax_rate: 12.5,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ describe("/store/collections", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd })
|
medusaProcess = await setupServer({ cwd, verbose: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -18,3 +18,4 @@ export * from "./simple-price-list-factory"
|
|||||||
export * from "./simple-batch-job-factory"
|
export * from "./simple-batch-job-factory"
|
||||||
export * from "./simple-sales-channel-factory"
|
export * from "./simple-sales-channel-factory"
|
||||||
export * from "./simple-custom-shipping-option-factory"
|
export * from "./simple-custom-shipping-option-factory"
|
||||||
|
export * from "./simple-payment-collection-factory"
|
||||||
@@ -31,6 +31,7 @@ export type LineItemFactoryData = {
|
|||||||
tax_lines?: TaxLineFactoryData[]
|
tax_lines?: TaxLineFactoryData[]
|
||||||
adjustments: LineItemAdjustmentFactoryData[]
|
adjustments: LineItemAdjustmentFactoryData[]
|
||||||
includes_tax?: boolean
|
includes_tax?: boolean
|
||||||
|
order_edit_id?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const simpleLineItemFactory = async (
|
export const simpleLineItemFactory = async (
|
||||||
@@ -72,6 +73,7 @@ export const simpleLineItemFactory = async (
|
|||||||
returned_quantity: data.returned_quantity || null,
|
returned_quantity: data.returned_quantity || null,
|
||||||
adjustments: data.adjustments,
|
adjustments: data.adjustments,
|
||||||
includes_tax: data.includes_tax,
|
includes_tax: data.includes_tax,
|
||||||
|
order_edit_id: data.order_edit_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
const line = await manager.save(toSave)
|
const line = await manager.save(toSave)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type OrderEditFactoryData = {
|
|||||||
order_id?: string
|
order_id?: string
|
||||||
internal_note?: string
|
internal_note?: string
|
||||||
declined_reason?: string
|
declined_reason?: string
|
||||||
|
payment_collection_id?: string
|
||||||
confirmed_at?: Date | string
|
confirmed_at?: Date | string
|
||||||
confirmed_by?: string
|
confirmed_by?: string
|
||||||
created_at?: Date | string
|
created_at?: Date | string
|
||||||
@@ -46,6 +47,7 @@ export const simpleOrderEditFactory = async (
|
|||||||
created_by: data.created_by,
|
created_by: data.created_by,
|
||||||
confirmed_at: data.confirmed_at,
|
confirmed_at: data.confirmed_at,
|
||||||
confirmed_by: data.confirmed_by,
|
confirmed_by: data.confirmed_by,
|
||||||
|
payment_collection_id: data.payment_collection_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
return await manager.save<OrderEdit>(orderEdit)
|
return await manager.save<OrderEdit>(orderEdit)
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Connection } from "typeorm"
|
||||||
|
|
||||||
|
import { simpleRegionFactory } from "./simple-region-factory"
|
||||||
|
import { simplePaymentFactory } from "./simple-payment-factory"
|
||||||
|
import { Payment, PaymentCollection } from "@medusajs/medusa"
|
||||||
|
|
||||||
|
export const simplePaymentCollectionFactory = async (
|
||||||
|
connection: Connection,
|
||||||
|
data: Partial<PaymentCollection> = {}
|
||||||
|
): Promise<PaymentCollection> => {
|
||||||
|
const manager = connection.manager
|
||||||
|
|
||||||
|
const defaultData = {
|
||||||
|
currency_code: data.currency_code ?? "usd",
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.region && !data.region_id) {
|
||||||
|
data.region = await simpleRegionFactory(connection, {
|
||||||
|
id: data.region_id || "test-region",
|
||||||
|
currency_code: defaultData.currency_code,
|
||||||
|
})
|
||||||
|
data.region_id = data.region.id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.payments?.length) {
|
||||||
|
const payments: Payment[] = []
|
||||||
|
for (const payment of data.payments) {
|
||||||
|
payment.currency_code = payment.currency_code ?? defaultData.currency_code
|
||||||
|
|
||||||
|
const savedPayment = await simplePaymentFactory(
|
||||||
|
connection,
|
||||||
|
payment as any
|
||||||
|
)
|
||||||
|
payments.push(savedPayment)
|
||||||
|
}
|
||||||
|
|
||||||
|
data.payments = payments
|
||||||
|
}
|
||||||
|
|
||||||
|
const obj = {
|
||||||
|
...defaultData,
|
||||||
|
...data,
|
||||||
|
}
|
||||||
|
|
||||||
|
const payCol = manager.create<PaymentCollection>(PaymentCollection, obj)
|
||||||
|
|
||||||
|
return await manager.save<PaymentCollection>(payCol)
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest --silent=false --runInBand --bail",
|
"test": "jest --silent=false --runInBand --bail --detectOpenHandles --forceExit",
|
||||||
"build": "babel src -d dist --extensions \".ts,.js\""
|
"build": "babel src -d dist --extensions \".ts,.js\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class TestPayService extends AbstractPaymentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async retrieveSavedMethods(customer) {
|
async retrieveSavedMethods(customer) {
|
||||||
return Promise.resolve([])
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
async createPayment(cart) {
|
async createPayment(cart) {
|
||||||
|
|||||||
@@ -12,17 +12,16 @@ function replacePath(requirePath, package, concatPackage = true) {
|
|||||||
const idx = requirePath.indexOf(package)
|
const idx = requirePath.indexOf(package)
|
||||||
const packPath = requirePath.substring(idx + package.length)
|
const packPath = requirePath.substring(idx + package.length)
|
||||||
|
|
||||||
let newPath = path.resolve(
|
let newPath =
|
||||||
medusaCore +
|
medusaCore +
|
||||||
"/" +
|
"/" +
|
||||||
(concatPackage ? package + "/" : "") +
|
(concatPackage ? package + "/" : "") +
|
||||||
packPath.replace("/dist", "/src").replace(".js", "")
|
packPath.replace("/dist", "/src").replace(".js", "")
|
||||||
)
|
|
||||||
|
|
||||||
if (!newPath.includes("/src")) {
|
if (!newPath.includes("/src")) {
|
||||||
newPath += "/src"
|
newPath += "/src"
|
||||||
}
|
}
|
||||||
return newPath
|
return path.resolve(newPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
Module.prototype.require = function (...args) {
|
Module.prototype.require = function (...args) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class TestPayService extends AbstractPaymentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async retrieveSavedMethods(customer) {
|
async retrieveSavedMethods(customer) {
|
||||||
return Promise.resolve([])
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
async createPayment(cart) {
|
async createPayment(cart) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class TestPayService extends AbstractPaymentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async retrieveSavedMethods(customer) {
|
async retrieveSavedMethods(customer) {
|
||||||
return Promise.resolve([])
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
async createPayment(cart) {
|
async createPayment(cart) {
|
||||||
|
|||||||
+1
-1
@@ -63,7 +63,7 @@
|
|||||||
"jest": "jest",
|
"jest": "jest",
|
||||||
"test": "turbo run test",
|
"test": "turbo run test",
|
||||||
"test:integration": "NODE_ENV=test jest --runInBand --bail --config=integration-tests/jest.config.js",
|
"test:integration": "NODE_ENV=test jest --runInBand --bail --config=integration-tests/jest.config.js",
|
||||||
"test:integration:api": "NODE_ENV=test jest --runInBand --bail --config=integration-tests/jest.config.js --projects=integration-tests/api",
|
"test:integration:api": "NODE_ENV=test jest --detectOpenHandles --forceExit --runInBand --bail --config=integration-tests/jest.config.js --projects=integration-tests/api",
|
||||||
"test:integration:plugins": "NODE_ENV=test jest --runInBand --bail --config=integration-tests/jest.config.js --projects=integration-tests/plugins",
|
"test:integration:plugins": "NODE_ENV=test jest --runInBand --bail --config=integration-tests/jest.config.js --projects=integration-tests/plugins",
|
||||||
"test:fixtures": "NODE_ENV=test jest --config=docs-util/jest.config.js --runInBand --bail",
|
"test:fixtures": "NODE_ENV=test jest --config=docs-util/jest.config.js --runInBand --bail",
|
||||||
"openapi:generate": "node ./scripts/build-openapi.js",
|
"openapi:generate": "node ./scripts/build-openapi.js",
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ class MobilePayAdyenService extends PaymentService {
|
|||||||
return raw
|
return raw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createPaymentNew(paymentInput) {
|
||||||
|
const raw = await this.adyenService_.createPaymentNew(paymentInput)
|
||||||
|
raw.type = "mobilepay"
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
async authorizePayment(sessionData, context) {
|
async authorizePayment(sessionData, context) {
|
||||||
return this.adyenService_.authorizePayment(sessionData, context)
|
return this.adyenService_.authorizePayment(sessionData, context)
|
||||||
}
|
}
|
||||||
@@ -32,6 +38,10 @@ class MobilePayAdyenService extends PaymentService {
|
|||||||
return this.adyenService_.updatePayment(data)
|
return this.adyenService_.updatePayment(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updatePaymentNew(data, _) {
|
||||||
|
return this.adyenService_.updatePaymentNew(data)
|
||||||
|
}
|
||||||
|
|
||||||
async updatePaymentData(sessionData, update) {
|
async updatePaymentData(sessionData, update) {
|
||||||
return this.adyenService_.updatePaymentData(sessionData, update)
|
return this.adyenService_.updatePaymentData(sessionData, update)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceStringWithPropertyValue(string, obj) {
|
static replaceStringWithPropertyValue(string, obj) {
|
||||||
const keys = Object.keys(obj)
|
const keys = Object.keys(obj)
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
if (string.includes(`{${key}}`)) {
|
if (string.includes(`{${key}}`)) {
|
||||||
@@ -263,15 +263,13 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
|
|
||||||
this.validateKlarnaOrderUrls("payment_collection_urls")
|
this.validateKlarnaOrderUrls("payment_collection_urls")
|
||||||
|
|
||||||
let order = {
|
const { currency_code, amount, resource_id } = paymentInput
|
||||||
|
|
||||||
|
const order = {
|
||||||
// Custom id is stored, such that we can use it for hooks
|
// Custom id is stored, such that we can use it for hooks
|
||||||
merchant_data: paymentInput.resource_id,
|
merchant_data: resource_id,
|
||||||
locale: "en-US",
|
locale: "en-US",
|
||||||
}
|
order_lines: [
|
||||||
|
|
||||||
const { currency_code, amount } = paymentInput
|
|
||||||
|
|
||||||
order.order_lines = [
|
|
||||||
{
|
{
|
||||||
name: "Payment Collection",
|
name: "Payment Collection",
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
@@ -280,29 +278,29 @@ class KlarnaProviderService extends PaymentService {
|
|||||||
total_amount: amount,
|
total_amount: amount,
|
||||||
total_tax_amount: 0,
|
total_tax_amount: 0,
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
|
||||||
// Defaults to Sweden
|
// Defaults to Sweden
|
||||||
order.purchase_country = "SE"
|
purchase_country: "SE",
|
||||||
|
|
||||||
order.order_amount = amount
|
order_amount: amount,
|
||||||
order.order_tax_amount = 0
|
order_tax_amount: 0,
|
||||||
order.purchase_currency = currency_code.toUpperCase()
|
purchase_currency: currency_code.toUpperCase(),
|
||||||
|
|
||||||
order.merchant_urls = {
|
merchant_urls: {
|
||||||
terms: this.replaceStringWithPropertyValue(
|
terms: KlarnaProviderService.replaceStringWithPropertyValue(
|
||||||
this.options_.payment_collection_urls.terms,
|
this.options_.payment_collection_urls.terms,
|
||||||
paymentInput
|
paymentInput
|
||||||
),
|
),
|
||||||
checkout: this.replaceStringWithPropertyValue(
|
checkout: KlarnaProviderService.replaceStringWithPropertyValue(
|
||||||
this.options_.payment_collection_urls.checkout,
|
this.options_.payment_collection_urls.checkout,
|
||||||
paymentInput
|
paymentInput
|
||||||
),
|
),
|
||||||
confirmation: this.replaceStringWithPropertyValue(
|
confirmation: KlarnaProviderService.replaceStringWithPropertyValue(
|
||||||
this.options_.payment_collection_urls.confirmation,
|
this.options_.payment_collection_urls.confirmation,
|
||||||
paymentInput
|
paymentInput
|
||||||
),
|
),
|
||||||
push: `${this.backendUrl_}/klarna/push?klarna_order_id={checkout.order.id}`,
|
push: `${this.backendUrl_}/klarna/push?klarna_order_id={checkout.order.id}`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return order
|
return order
|
||||||
|
|||||||
@@ -70,18 +70,15 @@ export default async (req, res) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function autorizePaymentCollection(req, payColId) {
|
async function autorizePaymentCollection(req, id, orderId) {
|
||||||
const manager = req.scope.resolve("manager")
|
const manager = req.scope.resolve("manager")
|
||||||
const paymentCollectionService = req.scope.resolve(
|
const paymentCollectionService = req.scope.resolve(
|
||||||
"paymentCollectonService"
|
"paymentCollectonService"
|
||||||
)
|
)
|
||||||
|
|
||||||
await manager.transaction(async (m) => {
|
await manager.transaction(async (manager) => {
|
||||||
const payCol = await paymentCollectionService
|
await paymentCollectionService.withTransaction(manager).authorize(id)
|
||||||
.withTransaction(m)
|
|
||||||
.retrieve(payColId)
|
|
||||||
})
|
})
|
||||||
// TODO: complete authorization
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -100,7 +97,8 @@ export default async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isPaymentCollection(customId)) {
|
if (isPaymentCollection(customId)) {
|
||||||
await autorizePaymentCollection(req, customId)
|
const orderId = order.id
|
||||||
|
await autorizePaymentCollection(req, customId, orderId)
|
||||||
} else {
|
} else {
|
||||||
await autorizeCart(req, customId)
|
await autorizeCart(req, customId)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class PayPalProviderService extends PaymentService {
|
|||||||
* Not supported
|
* Not supported
|
||||||
*/
|
*/
|
||||||
async retrieveSavedMethods(customer) {
|
async retrieveSavedMethods(customer) {
|
||||||
return Promise.resolve([])
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -50,13 +50,43 @@ export default async (req, res) => {
|
|||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handlePaymentCollection(event, req, res, id, paymentIntentId) {
|
||||||
|
const manager = req.scope.resolve("manager")
|
||||||
|
const paymentCollectionService = req.scope.resolve(
|
||||||
|
"paymentCollectionService"
|
||||||
|
)
|
||||||
|
|
||||||
|
const paycol = await paymentCollectionService
|
||||||
|
.retrieve(id, { relations: ["payments"] })
|
||||||
|
.catch(() => undefined)
|
||||||
|
|
||||||
|
if (paycol?.payments?.length) {
|
||||||
|
if (event.type === "payment_intent.succeeded") {
|
||||||
|
const payment = paycol.payments.find(
|
||||||
|
(pay) => pay.data.id === paymentIntentId
|
||||||
|
)
|
||||||
|
if (payment && !payment.captured_at) {
|
||||||
|
await manager.transaction(async (manager) => {
|
||||||
|
await paymentCollectionService
|
||||||
|
.withTransaction(manager)
|
||||||
|
.capture(payment.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
res.sendStatus(200)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.sendStatus(204)
|
||||||
|
}
|
||||||
|
|
||||||
const paymentIntent = event.data.object
|
const paymentIntent = event.data.object
|
||||||
const cartId = paymentIntent.metadata.cart_id
|
const cartId = paymentIntent.metadata.cart_id // Backward compatibility
|
||||||
const resourceId = paymentIntent.metadata.resource_id
|
const resourceId = paymentIntent.metadata.resource_id
|
||||||
|
|
||||||
if (isPaymentCollection(resourceId)) {
|
if (isPaymentCollection(resourceId)) {
|
||||||
// TODO: handle payment collection
|
await handlePaymentCollection(event, req, res, resourceId, paymentIntentId)
|
||||||
} else {
|
} else {
|
||||||
await handleCartPayments(event, req, res, resourceId ?? cartId)
|
await handleCartPayments(event, req, res, cartId ?? resourceId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ class StripeBase extends AbstractPaymentService {
|
|||||||
options
|
options
|
||||||
)
|
)
|
||||||
/** @private @const {string[]} */
|
/** @private @const {string[]} */
|
||||||
this.paymentMethodTypes = paymentMethodTypes
|
this.paymentMethodTypes_ = paymentMethodTypes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required Stripe options:
|
* Required Stripe options:
|
||||||
@@ -93,7 +93,7 @@ class StripeBase extends AbstractPaymentService {
|
|||||||
* @return {Promise<Data[]>} saved payments methods
|
* @return {Promise<Data[]>} saved payments methods
|
||||||
*/
|
*/
|
||||||
async retrieveSavedMethods(customer) {
|
async retrieveSavedMethods(customer) {
|
||||||
return Promise.resolve([])
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class StripeProviderService extends AbstractPaymentService {
|
|||||||
return methods.data
|
return methods.data
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve([])
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -222,7 +222,7 @@ class StripeProviderService extends AbstractPaymentService {
|
|||||||
*/
|
*/
|
||||||
async retrievePayment(data) {
|
async retrievePayment(data) {
|
||||||
try {
|
try {
|
||||||
return this.stripe_.paymentIntents.retrieve(data.id)
|
return await this.stripe_.paymentIntents.retrieve(data.id)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@@ -235,7 +235,7 @@ class StripeProviderService extends AbstractPaymentService {
|
|||||||
*/
|
*/
|
||||||
async getPaymentData(paymentSession) {
|
async getPaymentData(paymentSession) {
|
||||||
try {
|
try {
|
||||||
return this.stripe_.paymentIntents.retrieve(paymentSession.data.id)
|
return await this.stripe_.paymentIntents.retrieve(paymentSession.data.id)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@@ -259,7 +259,7 @@ class StripeProviderService extends AbstractPaymentService {
|
|||||||
|
|
||||||
async updatePaymentData(sessionData, update) {
|
async updatePaymentData(sessionData, update) {
|
||||||
try {
|
try {
|
||||||
return this.stripe_.paymentIntents.update(sessionData.id, {
|
return await this.stripe_.paymentIntents.update(sessionData.id, {
|
||||||
...update.data,
|
...update.data,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -284,7 +284,7 @@ class StripeProviderService extends AbstractPaymentService {
|
|||||||
return sessionData
|
return sessionData
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.stripe_.paymentIntents.update(sessionData.id, {
|
return await this.stripe_.paymentIntents.update(sessionData.id, {
|
||||||
amount: Math.round(cart.total),
|
amount: Math.round(cart.total),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -304,7 +304,7 @@ class StripeProviderService extends AbstractPaymentService {
|
|||||||
return sessionData
|
return sessionData
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.stripe_.paymentIntents.update(paymentSessionData.id, {
|
return await this.stripe_.paymentIntents.update(paymentSessionData.id, {
|
||||||
amount: Math.round(paymentInput.amount),
|
amount: Math.round(paymentInput.amount),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -335,7 +335,7 @@ class StripeProviderService extends AbstractPaymentService {
|
|||||||
*/
|
*/
|
||||||
async updatePaymentIntentCustomer(paymentIntentId, customerId) {
|
async updatePaymentIntentCustomer(paymentIntentId, customerId) {
|
||||||
try {
|
try {
|
||||||
return this.stripe_.paymentIntents.update(paymentIntentId, {
|
return await this.stripe_.paymentIntents.update(paymentIntentId, {
|
||||||
customer: customerId,
|
customer: customerId,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export * from "./routes/admin/gift-cards"
|
|||||||
export * from "./routes/admin/invites"
|
export * from "./routes/admin/invites"
|
||||||
export * from "./routes/admin/notes"
|
export * from "./routes/admin/notes"
|
||||||
export * from "./routes/admin/notifications"
|
export * from "./routes/admin/notifications"
|
||||||
|
export * from "./routes/admin/payment-collections"
|
||||||
|
export * from "./routes/admin/payments"
|
||||||
export * from "./routes/admin/order-edits"
|
export * from "./routes/admin/order-edits"
|
||||||
export * from "./routes/admin/orders"
|
export * from "./routes/admin/orders"
|
||||||
export * from "./routes/admin/price-lists"
|
export * from "./routes/admin/price-lists"
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ import taxRateRoutes from "./tax-rates"
|
|||||||
import uploadRoutes from "./uploads"
|
import uploadRoutes from "./uploads"
|
||||||
import userRoutes, { unauthenticatedUserRoutes } from "./users"
|
import userRoutes, { unauthenticatedUserRoutes } from "./users"
|
||||||
import variantRoutes from "./variants"
|
import variantRoutes from "./variants"
|
||||||
|
import paymentCollectionRoutes from "./payment-collections"
|
||||||
|
import paymentRoutes from "./payments"
|
||||||
|
|
||||||
const route = Router()
|
const route = Router()
|
||||||
|
|
||||||
@@ -99,6 +101,8 @@ export default (app, container, config) => {
|
|||||||
uploadRoutes(route)
|
uploadRoutes(route)
|
||||||
userRoutes(route)
|
userRoutes(route)
|
||||||
variantRoutes(route)
|
variantRoutes(route)
|
||||||
|
paymentCollectionRoutes(route)
|
||||||
|
paymentRoutes(route)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { IdMap } from "medusa-test-utils"
|
import { IdMap } from "medusa-test-utils"
|
||||||
import { request } from "../../../../../helpers/test-request"
|
import { request } from "../../../../../helpers/test-request"
|
||||||
import { orderEditServiceMock } from "../../../../../services/__mocks__/order-edit"
|
import { orderEditServiceMock } from "../../../../../services/__mocks__/order-edit"
|
||||||
|
|
||||||
import OrderEditingFeatureFlag from "../../../../../loaders/feature-flags/order-editing"
|
import OrderEditingFeatureFlag from "../../../../../loaders/feature-flags/order-editing"
|
||||||
|
|
||||||
describe("GET /admin/order-edits/:id", () => {
|
describe("GET /admin/order-edits/:id", () => {
|
||||||
@@ -19,6 +20,9 @@ describe("GET /admin/order-edits/:id", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
flags: [OrderEditingFeatureFlag],
|
flags: [OrderEditingFeatureFlag],
|
||||||
|
payload: {
|
||||||
|
payment_collection_description: "PayCol description",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -33,6 +37,8 @@ describe("GET /admin/order-edits/:id", () => {
|
|||||||
orderEditId,
|
orderEditId,
|
||||||
{ loggedInUserId: IdMap.getId("admin_user") }
|
{ loggedInUserId: IdMap.getId("admin_user") }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
expect(orderEditServiceMock.update).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns updated orderEdit", () => {
|
it("returns updated orderEdit", () => {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { AdminPostOrderEditsEditLineItemsReq } from "./add-line-item"
|
|||||||
import { AdminPostOrderEditsEditLineItemsLineItemReq } from "./update-order-edit-line-item"
|
import { AdminPostOrderEditsEditLineItemsLineItemReq } from "./update-order-edit-line-item"
|
||||||
import { GetOrderEditsParams } from "./list-order-edit"
|
import { GetOrderEditsParams } from "./list-order-edit"
|
||||||
import { GetOrderEditsOrderEditParams } from "./get-order-edit"
|
import { GetOrderEditsOrderEditParams } from "./get-order-edit"
|
||||||
|
import { AdminPostOrderEditsRequestConfirmationReq } from "./request-confirmation"
|
||||||
|
|
||||||
const route = Router()
|
const route = Router()
|
||||||
|
|
||||||
@@ -85,6 +86,7 @@ export default (app) => {
|
|||||||
|
|
||||||
route.post(
|
route.post(
|
||||||
"/:id/request",
|
"/:id/request",
|
||||||
|
transformBody(AdminPostOrderEditsRequestConfirmationReq),
|
||||||
middlewares.wrap(require("./request-confirmation").default)
|
middlewares.wrap(require("./request-confirmation").default)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -121,3 +123,4 @@ export * from "./create-order-edit"
|
|||||||
export * from "./get-order-edit"
|
export * from "./get-order-edit"
|
||||||
export * from "./list-order-edit"
|
export * from "./list-order-edit"
|
||||||
export * from "./add-line-item"
|
export * from "./add-line-item"
|
||||||
|
export * from "./request-confirmation"
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { OrderEditService } from "../../../../services"
|
import { IsOptional, IsString, IsObject } from "class-validator"
|
||||||
|
import {
|
||||||
|
OrderEditService,
|
||||||
|
OrderService,
|
||||||
|
PaymentCollectionService,
|
||||||
|
} from "../../../../services"
|
||||||
import {
|
import {
|
||||||
defaultOrderEditFields,
|
defaultOrderEditFields,
|
||||||
defaultOrderEditRelations,
|
defaultOrderEditRelations,
|
||||||
} from "../../../../types/order-edit"
|
} from "../../../../types/order-edit"
|
||||||
|
import { PaymentCollectionType } from "../../../../models"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /order-edits/{id}/request
|
* @oas [post] /order-edits/{id}/request
|
||||||
@@ -54,26 +60,71 @@ import {
|
|||||||
*/
|
*/
|
||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
const { id } = req.params
|
const { id } = req.params
|
||||||
|
const validatedBody =
|
||||||
|
req.validatedBody as AdminPostOrderEditsRequestConfirmationReq
|
||||||
|
|
||||||
const orderEditService: OrderEditService =
|
const orderEditService: OrderEditService =
|
||||||
req.scope.resolve("orderEditService")
|
req.scope.resolve("orderEditService")
|
||||||
|
|
||||||
|
const orderService: OrderService = req.scope.resolve("orderService")
|
||||||
|
|
||||||
|
const paymentCollectionService: PaymentCollectionService = req.scope.resolve(
|
||||||
|
"paymentCollectionService"
|
||||||
|
)
|
||||||
|
|
||||||
const manager: EntityManager = req.scope.resolve("manager")
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
|
|
||||||
const loggedInUser = (req.user?.id ?? req.user?.userId) as string
|
const loggedInUser = (req.user?.id ?? req.user?.userId) as string
|
||||||
|
|
||||||
await manager.transaction(async (transactionManager) => {
|
await manager.transaction(async (transactionManager) => {
|
||||||
await orderEditService
|
const orderEditServiceTx =
|
||||||
.withTransaction(transactionManager)
|
orderEditService.withTransaction(transactionManager)
|
||||||
.requestConfirmation(id, { loggedInUserId: loggedInUser })
|
|
||||||
|
const orderEdit = await orderEditServiceTx.requestConfirmation(id, {
|
||||||
|
loggedInUserId: loggedInUser,
|
||||||
})
|
})
|
||||||
|
|
||||||
const orderEdit = await orderEditService.retrieve(id, {
|
const total = await orderEditServiceTx.getTotals(orderEdit.id)
|
||||||
|
|
||||||
|
if (total.difference_due > 0) {
|
||||||
|
const order = await orderService
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.retrieve(orderEdit.order_id, {
|
||||||
|
select: ["currency_code", "region_id"],
|
||||||
|
})
|
||||||
|
|
||||||
|
const paymentCollection = await paymentCollectionService
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.create({
|
||||||
|
type: PaymentCollectionType.ORDER_EDIT,
|
||||||
|
amount: total.difference_due,
|
||||||
|
currency_code: order.currency_code,
|
||||||
|
region_id: order.region_id,
|
||||||
|
description: validatedBody.payment_collection_description,
|
||||||
|
created_by: loggedInUser,
|
||||||
|
})
|
||||||
|
|
||||||
|
orderEdit.payment_collection_id = paymentCollection.id
|
||||||
|
|
||||||
|
await orderEditServiceTx.update(orderEdit.id, {
|
||||||
|
payment_collection_id: paymentCollection.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let orderEdit = await orderEditService.retrieve(id, {
|
||||||
relations: defaultOrderEditRelations,
|
relations: defaultOrderEditRelations,
|
||||||
select: defaultOrderEditFields,
|
select: defaultOrderEditFields,
|
||||||
})
|
})
|
||||||
|
orderEdit = await orderEditService.decorateTotals(orderEdit)
|
||||||
|
|
||||||
res.status(200).send({
|
res.status(200).send({
|
||||||
order_edit: orderEdit,
|
order_edit: orderEdit,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AdminPostOrderEditsRequestConfirmationReq {
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
payment_collection_description?: string | undefined
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { PaymentCollectionService } from "../../../../services"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const paymentCollectionService: PaymentCollectionService = req.scope.resolve(
|
||||||
|
"paymentCollectionService"
|
||||||
|
)
|
||||||
|
|
||||||
|
await paymentCollectionService.delete(id)
|
||||||
|
|
||||||
|
res.status(200).json({ id, deleted: true, object: "payment_collection" })
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { PaymentCollectionService } from "../../../../services"
|
||||||
|
import { FindParams } from "../../../../types/common"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
const retrieveConfig = req.retrieveConfig
|
||||||
|
|
||||||
|
const paymentCollectionService: PaymentCollectionService = req.scope.resolve(
|
||||||
|
"paymentCollectionService"
|
||||||
|
)
|
||||||
|
|
||||||
|
const paymentCollection = await paymentCollectionService.retrieve(
|
||||||
|
id,
|
||||||
|
retrieveConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({ payment_collection: paymentCollection })
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetPaymentCollectionsParams extends FindParams {}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { Router } from "express"
|
||||||
|
import "reflect-metadata"
|
||||||
|
import middlewares, {
|
||||||
|
transformBody,
|
||||||
|
transformQuery,
|
||||||
|
} from "../../../middlewares"
|
||||||
|
import OrderEditingFeatureFlag from "../../../../loaders/feature-flags/order-editing"
|
||||||
|
import { isFeatureFlagEnabled } from "../../../middlewares/feature-flag-enabled"
|
||||||
|
|
||||||
|
import { GetPaymentCollectionsParams } from "./get-payment-collection"
|
||||||
|
import { AdminUpdatePaymentCollectionRequest } from "./update-payment-collection"
|
||||||
|
|
||||||
|
const route = Router()
|
||||||
|
|
||||||
|
export default (app, container) => {
|
||||||
|
app.use(
|
||||||
|
"/payment-collections",
|
||||||
|
isFeatureFlagEnabled(OrderEditingFeatureFlag.key),
|
||||||
|
route
|
||||||
|
)
|
||||||
|
|
||||||
|
route.get(
|
||||||
|
"/:id",
|
||||||
|
transformQuery(GetPaymentCollectionsParams, {
|
||||||
|
defaultFields: defaultPaymentCollectionFields,
|
||||||
|
defaultRelations: defaulPaymentCollectionRelations,
|
||||||
|
isList: false,
|
||||||
|
}),
|
||||||
|
middlewares.wrap(require("./get-payment-collection").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
"/:id",
|
||||||
|
transformBody(AdminUpdatePaymentCollectionRequest),
|
||||||
|
middlewares.wrap(require("./update-payment-collection").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.delete(
|
||||||
|
"/:id",
|
||||||
|
middlewares.wrap(require("./delete-payment-collection").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultPaymentCollectionFields = [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"status",
|
||||||
|
"description",
|
||||||
|
"amount",
|
||||||
|
"authorized_amount",
|
||||||
|
"region",
|
||||||
|
"currency_code",
|
||||||
|
"currency",
|
||||||
|
"metadata",
|
||||||
|
]
|
||||||
|
|
||||||
|
export const defaulPaymentCollectionRelations = [
|
||||||
|
"region",
|
||||||
|
"payment_sessions",
|
||||||
|
"payments",
|
||||||
|
]
|
||||||
|
|
||||||
|
export * from "./get-payment-collection"
|
||||||
|
export * from "./update-payment-collection"
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { IsObject, IsOptional, IsString } from "class-validator"
|
||||||
|
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { PaymentCollectionService } from "../../../../services"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
const data = req.validatedBody as AdminUpdatePaymentCollectionRequest
|
||||||
|
|
||||||
|
const paymentCollectionService: PaymentCollectionService = req.scope.resolve(
|
||||||
|
"paymentCollectionService"
|
||||||
|
)
|
||||||
|
|
||||||
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
|
const paymentCollection = await manager.transaction(
|
||||||
|
async (transactionManager) => {
|
||||||
|
return await paymentCollectionService
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.update(id, data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({ payment_collection: paymentCollection })
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminUpdatePaymentCollectionRequest {
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
description?: string
|
||||||
|
|
||||||
|
@IsObject()
|
||||||
|
@IsOptional()
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { PaymentService } from "../../../../services"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const paymentService: PaymentService = req.scope.resolve("paymentService")
|
||||||
|
|
||||||
|
const payment = await paymentService.capture(id)
|
||||||
|
|
||||||
|
res.status(200).json({ payment })
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { PaymentService } from "../../../../services"
|
||||||
|
import { FindParams } from "../../../../types/common"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
const retrieveConfig = req.retrieveConfig
|
||||||
|
|
||||||
|
const paymentService: PaymentService = req.scope.resolve("paymentService")
|
||||||
|
|
||||||
|
const payment = await paymentService.retrieve(id, retrieveConfig)
|
||||||
|
|
||||||
|
res.status(200).json({ payment: payment })
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetPaymentsParams extends FindParams {}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { Router } from "express"
|
||||||
|
import "reflect-metadata"
|
||||||
|
import middlewares, {
|
||||||
|
transformBody,
|
||||||
|
transformQuery,
|
||||||
|
} from "../../../middlewares"
|
||||||
|
import OrderEditingFeatureFlag from "../../../../loaders/feature-flags/order-editing"
|
||||||
|
import { isFeatureFlagEnabled } from "../../../middlewares/feature-flag-enabled"
|
||||||
|
|
||||||
|
import { GetPaymentsParams } from "./get-payment"
|
||||||
|
import { AdminPostPaymentRefundsReq } from "./refund-payment"
|
||||||
|
|
||||||
|
const route = Router()
|
||||||
|
|
||||||
|
export default (app, container) => {
|
||||||
|
app.use("/payments", isFeatureFlagEnabled(OrderEditingFeatureFlag.key), route)
|
||||||
|
|
||||||
|
route.get(
|
||||||
|
"/:id",
|
||||||
|
transformQuery(GetPaymentsParams, {
|
||||||
|
defaultFields: defaultPaymentFields,
|
||||||
|
isList: false,
|
||||||
|
}),
|
||||||
|
middlewares.wrap(require("./get-payment").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
"/:id/capture",
|
||||||
|
middlewares.wrap(require("./capture-payment").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
"/:id/refund",
|
||||||
|
transformBody(AdminPostPaymentRefundsReq),
|
||||||
|
middlewares.wrap(require("./refund-payment").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultPaymentFields = [
|
||||||
|
"id",
|
||||||
|
"swap_id",
|
||||||
|
"cart_id",
|
||||||
|
"order_id",
|
||||||
|
"amount",
|
||||||
|
"currency_code",
|
||||||
|
"amount_refunded",
|
||||||
|
"provider_id",
|
||||||
|
"data",
|
||||||
|
"captured_at",
|
||||||
|
"canceled_at",
|
||||||
|
"metadata",
|
||||||
|
]
|
||||||
|
|
||||||
|
export * from "./get-payment"
|
||||||
|
export * from "./refund-payment"
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
IsEnum,
|
||||||
|
IsInt,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
} from "class-validator"
|
||||||
|
import { RefundReason } from "../../../../models"
|
||||||
|
|
||||||
|
import { PaymentService } from "../../../../services"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const data = req.validatedBody as AdminPostPaymentRefundsReq
|
||||||
|
|
||||||
|
const paymentService: PaymentService = req.scope.resolve("paymentService")
|
||||||
|
|
||||||
|
const refund = await paymentService.refund(
|
||||||
|
id,
|
||||||
|
data.amount,
|
||||||
|
data.reason,
|
||||||
|
data.note
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({ refund })
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminPostPaymentRefundsReq {
|
||||||
|
@IsInt()
|
||||||
|
@IsNotEmpty()
|
||||||
|
amount: number
|
||||||
|
|
||||||
|
@IsEnum(RefundReason)
|
||||||
|
@IsNotEmpty()
|
||||||
|
reason: RefundReason
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
note?: string
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import returnRoutes from "./returns"
|
|||||||
import shippingOptionRoutes from "./shipping-options"
|
import shippingOptionRoutes from "./shipping-options"
|
||||||
import swapRoutes from "./swaps"
|
import swapRoutes from "./swaps"
|
||||||
import variantRoutes from "./variants"
|
import variantRoutes from "./variants"
|
||||||
|
import paymentCollectionRoutes from "./payment-collections"
|
||||||
|
|
||||||
const route = Router()
|
const route = Router()
|
||||||
|
|
||||||
@@ -47,6 +48,7 @@ export default (app, container, config) => {
|
|||||||
returnRoutes(route)
|
returnRoutes(route)
|
||||||
giftCardRoutes(route)
|
giftCardRoutes(route)
|
||||||
returnReasonRoutes(route)
|
returnReasonRoutes(route)
|
||||||
|
paymentCollectionRoutes(route)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
import { Request, Response } from "express"
|
import { Request, Response } from "express"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { OrderEditService } from "../../../../services"
|
import {
|
||||||
|
OrderEditService,
|
||||||
|
OrderService,
|
||||||
|
PaymentProviderService,
|
||||||
|
} from "../../../../services"
|
||||||
import {
|
import {
|
||||||
defaultStoreOrderEditFields,
|
defaultStoreOrderEditFields,
|
||||||
defaultStoreOrderEditRelations,
|
defaultStoreOrderEditRelations,
|
||||||
} from "../../../../types/order-edit"
|
} from "../../../../types/order-edit"
|
||||||
import { OrderEditStatus } from "../../../../models"
|
import { OrderEditStatus, PaymentCollectionStatus } from "../../../../models"
|
||||||
import { MedusaError } from "medusa-core-utils"
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,13 +59,25 @@ export default async (req: Request, res: Response) => {
|
|||||||
const orderEditService: OrderEditService =
|
const orderEditService: OrderEditService =
|
||||||
req.scope.resolve("orderEditService")
|
req.scope.resolve("orderEditService")
|
||||||
|
|
||||||
|
const orderService: OrderService = req.scope.resolve("orderService")
|
||||||
|
|
||||||
|
const paymentProviderService: PaymentProviderService = req.scope.resolve(
|
||||||
|
"paymentProviderService"
|
||||||
|
)
|
||||||
|
|
||||||
const manager: EntityManager = req.scope.resolve("manager")
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
|
|
||||||
const userId = req.user?.customer_id ?? req.user?.id ?? req.user?.userId
|
const userId = req.user?.customer_id ?? req.user?.id ?? req.user?.userId
|
||||||
|
|
||||||
await manager.transaction(async (manager) => {
|
await manager.transaction(async (manager) => {
|
||||||
const orderEditServiceTx = orderEditService.withTransaction(manager)
|
const orderEditServiceTx = orderEditService.withTransaction(manager)
|
||||||
const orderEdit = await orderEditServiceTx.retrieve(id)
|
const orderServiceTx = orderService.withTransaction(manager)
|
||||||
|
const paymentProviderServiceTx =
|
||||||
|
paymentProviderService.withTransaction(manager)
|
||||||
|
|
||||||
|
const orderEdit = await orderEditServiceTx.retrieve(id, {
|
||||||
|
relations: ["payment_collection"],
|
||||||
|
})
|
||||||
|
|
||||||
if (orderEdit.status === OrderEditStatus.CONFIRMED) {
|
if (orderEdit.status === OrderEditStatus.CONFIRMED) {
|
||||||
return orderEdit
|
return orderEdit
|
||||||
@@ -74,18 +90,31 @@ export default async (req: Request, res: Response) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO once payment collection is done
|
if (orderEdit.payment_collection) {
|
||||||
/*const paymentCollection = await this.paymentCollectionService_.withTransaction(manager).retrieve(orderEdit.payment_collection_id)
|
if (
|
||||||
if (!paymentCollection.authorized_at) {
|
orderEdit.payment_collection.status !==
|
||||||
|
PaymentCollectionStatus.AUTHORIZED
|
||||||
|
) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
"Unable to complete an order edit if the payment is not authorized"
|
"Unable to complete an order edit if the payment is not authorized"
|
||||||
)
|
)
|
||||||
}*/
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return await orderEditServiceTx.confirm(id, {
|
const returned = await orderEditServiceTx.confirm(id, {
|
||||||
loggedInUserId: userId,
|
loggedInUserId: userId,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (orderEdit.payment_collection) {
|
||||||
|
for (const payment of orderEdit.payment_collection.payments) {
|
||||||
|
await paymentProviderServiceTx.updatePayment(payment.id, {
|
||||||
|
order_id: orderEdit.order_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return returned
|
||||||
})
|
})
|
||||||
|
|
||||||
let orderEdit = await orderEditService.retrieve(id, {
|
let orderEdit = await orderEditService.retrieve(id, {
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
import { PaymentCollectionService } from "../../../../services"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { payment_id } = req.params
|
||||||
|
|
||||||
|
const paymentCollectionService: PaymentCollectionService = req.scope.resolve(
|
||||||
|
"paymentCollectionService"
|
||||||
|
)
|
||||||
|
|
||||||
|
const payment_collection = await paymentCollectionService.authorize(
|
||||||
|
payment_id
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({ payment_collection })
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { PaymentCollectionService } from "../../../../services"
|
||||||
|
import { FindParams } from "../../../../types/common"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
const retrieveConfig = req.retrieveConfig
|
||||||
|
|
||||||
|
const paymentCollectionService: PaymentCollectionService = req.scope.resolve(
|
||||||
|
"paymentCollectionService"
|
||||||
|
)
|
||||||
|
|
||||||
|
const paymentCollection = await paymentCollectionService.retrieve(
|
||||||
|
id,
|
||||||
|
retrieveConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({ payment_collection: paymentCollection })
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetPaymentCollectionsParams extends FindParams {}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { Router } from "express"
|
||||||
|
import "reflect-metadata"
|
||||||
|
import middlewares, {
|
||||||
|
transformBody,
|
||||||
|
transformQuery,
|
||||||
|
} from "../../../middlewares"
|
||||||
|
|
||||||
|
import OrderEditingFeatureFlag from "../../../../loaders/feature-flags/order-editing"
|
||||||
|
import { isFeatureFlagEnabled } from "../../../middlewares/feature-flag-enabled"
|
||||||
|
|
||||||
|
import { AdminManagePaymentCollectionSessionRequest } from "./manage-payment-sessions"
|
||||||
|
import { AdminRefreshPaymentCollectionSessionRequest } from "./refresh-payment-session"
|
||||||
|
import { GetPaymentCollectionsParams } from "./get-payment-collection"
|
||||||
|
|
||||||
|
const route = Router()
|
||||||
|
|
||||||
|
export default (app, container) => {
|
||||||
|
app.use(
|
||||||
|
"/payment-collections",
|
||||||
|
isFeatureFlagEnabled(OrderEditingFeatureFlag.key),
|
||||||
|
route
|
||||||
|
)
|
||||||
|
|
||||||
|
route.get(
|
||||||
|
"/:id",
|
||||||
|
transformQuery(GetPaymentCollectionsParams, {
|
||||||
|
defaultFields: defaultPaymentCollectionFields,
|
||||||
|
defaultRelations: defaulPaymentCollectionRelations,
|
||||||
|
isList: false,
|
||||||
|
}),
|
||||||
|
middlewares.wrap(require("./get-payment-collection").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
"/:id/authorize",
|
||||||
|
middlewares.wrap(require("./authorize-payment-collection").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
"/:id/sessions",
|
||||||
|
transformBody(AdminManagePaymentCollectionSessionRequest),
|
||||||
|
middlewares.wrap(require("./manage-payment-sessions").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
"/:id/sessions/:session_id/refresh",
|
||||||
|
transformBody(AdminRefreshPaymentCollectionSessionRequest),
|
||||||
|
middlewares.wrap(require("./refresh-payment-session").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultPaymentCollectionFields = [
|
||||||
|
"id",
|
||||||
|
"type",
|
||||||
|
"status",
|
||||||
|
"description",
|
||||||
|
"amount",
|
||||||
|
"region",
|
||||||
|
"currency_code",
|
||||||
|
"currency",
|
||||||
|
"metadata",
|
||||||
|
]
|
||||||
|
|
||||||
|
export const defaulPaymentCollectionRelations = ["region", "payment_sessions"]
|
||||||
|
|
||||||
|
export * from "./get-payment-collection"
|
||||||
|
export * from "./manage-payment-sessions"
|
||||||
|
export * from "./refresh-payment-session"
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { IsInt, IsNotEmpty, IsOptional, IsString } from "class-validator"
|
||||||
|
import { IsType } from "../../../../utils/validators/is-type"
|
||||||
|
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { PaymentCollectionService } from "../../../../services"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const data = req.validatedBody as AdminManagePaymentCollectionSessionRequest
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const paymentCollectionService: PaymentCollectionService = req.scope.resolve(
|
||||||
|
"paymentCollectionService"
|
||||||
|
)
|
||||||
|
|
||||||
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
|
const paymentCollection = await manager.transaction(
|
||||||
|
async (transactionManager) => {
|
||||||
|
return await paymentCollectionService
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.setPaymentSessions(id, data.sessions)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({ payment_collection: paymentCollection })
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PaymentCollectionSessionInputRequest {
|
||||||
|
@IsString()
|
||||||
|
provider_id: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
customer_id: string
|
||||||
|
|
||||||
|
@IsInt()
|
||||||
|
@IsNotEmpty()
|
||||||
|
amount: number
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
session_id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminManagePaymentCollectionSessionRequest {
|
||||||
|
@IsType([
|
||||||
|
PaymentCollectionSessionInputRequest,
|
||||||
|
[PaymentCollectionSessionInputRequest],
|
||||||
|
])
|
||||||
|
sessions:
|
||||||
|
| PaymentCollectionSessionInputRequest
|
||||||
|
| PaymentCollectionSessionInputRequest[]
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { IsInt, IsNotEmpty, IsString } from "class-validator"
|
||||||
|
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { PaymentCollectionService } from "../../../../services"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const data = req.validatedBody as AdminRefreshPaymentCollectionSessionRequest
|
||||||
|
const { id, session_id } = req.params
|
||||||
|
|
||||||
|
const paymentCollectionService: PaymentCollectionService = req.scope.resolve(
|
||||||
|
"paymentCollectionService"
|
||||||
|
)
|
||||||
|
|
||||||
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
|
const paymentCollection = await manager.transaction(
|
||||||
|
async (transactionManager) => {
|
||||||
|
return await paymentCollectionService
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.refreshPaymentSession(id, session_id, data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({ payment_collection: paymentCollection })
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdminRefreshPaymentCollectionSessionRequest {
|
||||||
|
@IsString()
|
||||||
|
provider_id: string
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
customer_id: string
|
||||||
|
}
|
||||||
@@ -111,7 +111,7 @@ export abstract class AbstractPaymentService
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
public async retrieveSavedMethods(customer: Customer): Promise<Data[]> {
|
public async retrieveSavedMethods(customer: Customer): Promise<Data[]> {
|
||||||
return Promise.resolve([])
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract getStatus(data: Data): Promise<PaymentSessionStatus>
|
public abstract getStatus(data: Data): Promise<PaymentSessionStatus>
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ export class paymentCollection1664880666982 implements MigrationInterface {
|
|||||||
CREATE TYPE "PAYMENT_COLLECTION_TYPE_ENUM" AS ENUM ('order_edit');
|
CREATE TYPE "PAYMENT_COLLECTION_TYPE_ENUM" AS ENUM ('order_edit');
|
||||||
|
|
||||||
CREATE TYPE "PAYMENT_COLLECTION_STATUS_ENUM" AS ENUM (
|
CREATE TYPE "PAYMENT_COLLECTION_STATUS_ENUM" AS ENUM (
|
||||||
'not_paid', 'awaiting', 'authorized', 'partially_authorized', 'captured',
|
'not_paid', 'awaiting', 'authorized',
|
||||||
'partially_captured', 'refunded', 'partially_refunded', 'canceled', 'requires_action'
|
'partially_authorized', 'canceled'
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS payment_collection
|
CREATE TABLE IF NOT EXISTS payment_collection
|
||||||
@@ -27,8 +27,6 @@ export class paymentCollection1664880666982 implements MigrationInterface {
|
|||||||
description text NULL,
|
description text NULL,
|
||||||
amount integer NOT NULL,
|
amount integer NOT NULL,
|
||||||
authorized_amount integer NULL,
|
authorized_amount integer NULL,
|
||||||
captured_amount integer NULL,
|
|
||||||
refunded_amount integer NULL,
|
|
||||||
region_id character varying NOT NULL,
|
region_id character varying NOT NULL,
|
||||||
currency_code character varying NOT NULL,
|
currency_code character varying NOT NULL,
|
||||||
metadata jsonb NULL,
|
metadata jsonb NULL,
|
||||||
@@ -78,6 +76,7 @@ export class paymentCollection1664880666982 implements MigrationInterface {
|
|||||||
ALTER TABLE refund ADD COLUMN payment_id character varying NULL;
|
ALTER TABLE refund ADD COLUMN payment_id character varying NULL;
|
||||||
CREATE INDEX "IDX_refund_payment_id" ON "refund" ("payment_id");
|
CREATE INDEX "IDX_refund_payment_id" ON "refund" ("payment_id");
|
||||||
ALTER TABLE "refund" ADD CONSTRAINT "FK_refund_payment_id" FOREIGN KEY ("payment_id") REFERENCES "payment"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
ALTER TABLE "refund" ADD CONSTRAINT "FK_refund_payment_id" FOREIGN KEY ("payment_id") REFERENCES "payment"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||||
|
ALTER TABLE refund ALTER COLUMN order_id DROP NOT NULL;
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// Add missing indexes
|
// Add missing indexes
|
||||||
@@ -97,6 +96,7 @@ export class paymentCollection1664880666982 implements MigrationInterface {
|
|||||||
|
|
||||||
DROP INDEX "IDX_refund_payment_id";
|
DROP INDEX "IDX_refund_payment_id";
|
||||||
ALTER TABLE refund DROP CONSTRAINT "FK_refund_payment_id";
|
ALTER TABLE refund DROP CONSTRAINT "FK_refund_payment_id";
|
||||||
|
ALTER TABLE refund ALTER COLUMN order_id SET NOT NULL;
|
||||||
|
|
||||||
ALTER TABLE payment_collection DROP CONSTRAINT "FK_payment_collection_region_id";
|
ALTER TABLE payment_collection DROP CONSTRAINT "FK_payment_collection_region_id";
|
||||||
ALTER TABLE payment_collection_sessions DROP CONSTRAINT "FK_payment_collection_sessions_payment_collection_id";
|
ALTER TABLE payment_collection_sessions DROP CONSTRAINT "FK_payment_collection_sessions_payment_collection_id";
|
||||||
|
|||||||
@@ -21,12 +21,7 @@ export enum PaymentCollectionStatus {
|
|||||||
AWAITING = "awaiting",
|
AWAITING = "awaiting",
|
||||||
AUTHORIZED = "authorized",
|
AUTHORIZED = "authorized",
|
||||||
PARTIALLY_AUTHORIZED = "partially_authorized",
|
PARTIALLY_AUTHORIZED = "partially_authorized",
|
||||||
CAPTURED = "captured",
|
|
||||||
PARTIALLY_CAPTURED = "partially_captured",
|
|
||||||
REFUNDED = "refunded",
|
|
||||||
PARTIALLY_REFUNDED = "partially_refunded",
|
|
||||||
CANCELED = "canceled",
|
CANCELED = "canceled",
|
||||||
REQUIRES_ACTION = "requires_action",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PaymentCollectionType {
|
export enum PaymentCollectionType {
|
||||||
@@ -41,20 +36,14 @@ export class PaymentCollection extends SoftDeletableEntity {
|
|||||||
@DbAwareColumn({ type: "enum", enum: PaymentCollectionStatus })
|
@DbAwareColumn({ type: "enum", enum: PaymentCollectionStatus })
|
||||||
status: PaymentCollectionStatus
|
status: PaymentCollectionStatus
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ type: "varchar", nullable: true })
|
||||||
description: string
|
description: string | null
|
||||||
|
|
||||||
@Column({ type: "int" })
|
@Column({ type: "int" })
|
||||||
amount: number
|
amount: number
|
||||||
|
|
||||||
@Column({ type: "int", nullable: true })
|
@Column({ type: "int", nullable: true })
|
||||||
authorized_amount: number
|
authorized_amount: number | null
|
||||||
|
|
||||||
@Column({ type: "int", nullable: true })
|
|
||||||
captured_amount: number
|
|
||||||
|
|
||||||
@Column({ type: "int", nullable: true })
|
|
||||||
refunded_amount: number
|
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column()
|
@Column()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { IdMap } from "medusa-test-utils"
|
|||||||
|
|
||||||
export const orderEdit = {
|
export const orderEdit = {
|
||||||
id: IdMap.getId("testCreatedOrder"),
|
id: IdMap.getId("testCreatedOrder"),
|
||||||
order_id: "empty-id",
|
order_id: IdMap.getId("test-order"),
|
||||||
internal_note: "internal note",
|
internal_note: "internal note",
|
||||||
declined_reason: null,
|
declined_reason: null,
|
||||||
declined_at: null,
|
declined_at: null,
|
||||||
@@ -106,6 +106,9 @@ export const orderEditServiceMock = {
|
|||||||
created_by: context.loggedInUserId,
|
created_by: context.loggedInUserId,
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
update: jest.fn().mockImplementation((id, data) => {
|
||||||
|
return Promise.resolve(data)
|
||||||
|
}),
|
||||||
decline: jest.fn().mockImplementation((id, reason, userId) => {
|
decline: jest.fn().mockImplementation((id, reason, userId) => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
id,
|
id,
|
||||||
@@ -114,8 +117,17 @@ export const orderEditServiceMock = {
|
|||||||
declined_at: new Date(),
|
declined_at: new Date(),
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
getTotals: jest.fn().mockImplementation(() => {
|
getTotals: jest.fn().mockImplementation((id) => {
|
||||||
return Promise.resolve({})
|
return Promise.resolve({
|
||||||
|
shipping_total: 10,
|
||||||
|
gift_card_total: 0,
|
||||||
|
gift_card_tax_total: 0,
|
||||||
|
discount_total: 0,
|
||||||
|
tax_total: 1,
|
||||||
|
subtotal: 2000,
|
||||||
|
difference_due: 1000,
|
||||||
|
total: 1000,
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
delete: jest.fn().mockImplementation((_) => {
|
delete: jest.fn().mockImplementation((_) => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export const orders = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
regionid: IdMap.getId("testRegion"),
|
regionid: IdMap.getId("testRegion"),
|
||||||
|
currency_code: "USD",
|
||||||
customerid: IdMap.getId("testCustomer"),
|
customerid: IdMap.getId("testCustomer"),
|
||||||
payment_method: {
|
payment_method: {
|
||||||
providerid: "default_provider",
|
providerid: "default_provider",
|
||||||
@@ -96,6 +97,7 @@ export const orders = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
regionid: IdMap.getId("region-france"),
|
regionid: IdMap.getId("region-france"),
|
||||||
|
currency_code: "EUR",
|
||||||
customerid: IdMap.getId("test-customer"),
|
customerid: IdMap.getId("test-customer"),
|
||||||
payment_method: {
|
payment_method: {
|
||||||
providerid: "default_provider",
|
providerid: "default_provider",
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
|
||||||
|
export const PaymentCollectionServiceMock = {
|
||||||
|
withTransaction: function () {
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
create: jest.fn().mockImplementation((data) => {
|
||||||
|
const id = data.id ?? IdMap.getId("paycol_1")
|
||||||
|
return Promise.resolve({ ...data, id })
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const mock = jest.fn().mockImplementation(() => {
|
||||||
|
return PaymentCollectionServiceMock
|
||||||
|
})
|
||||||
|
|
||||||
|
export default mock
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export const PaymentServiceMock = {
|
||||||
|
withTransaction: function () {
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
create: jest.fn().mockImplementation((data) => {
|
||||||
|
const id = data.id ?? IdMap.getId("pay_1")
|
||||||
|
return Promise.resolve({ ...data, id })
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const mock = jest.fn().mockImplementation(() => {
|
||||||
|
return PaymentServiceMock
|
||||||
|
})
|
||||||
|
|
||||||
|
export default mock
|
||||||
@@ -182,7 +182,6 @@ describe("OrderEditService", () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const orderEditService = new OrderEditService({
|
const orderEditService = new OrderEditService({
|
||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
orderEditRepository,
|
orderEditRepository,
|
||||||
@@ -331,6 +330,10 @@ describe("OrderEditService", () => {
|
|||||||
let result
|
let result
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
jest.spyOn(orderEditService, "getTotals").mockResolvedValue({
|
||||||
|
difference_due: 1500,
|
||||||
|
} as any)
|
||||||
|
|
||||||
result = await orderEditService.requestConfirmation(orderEditId, {
|
result = await orderEditService.requestConfirmation(orderEditId, {
|
||||||
loggedInUserId: userId,
|
loggedInUserId: userId,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
EventBusService,
|
EventBusService,
|
||||||
PaymentCollectionService,
|
PaymentCollectionService,
|
||||||
PaymentProviderService,
|
PaymentProviderService,
|
||||||
|
PaymentService,
|
||||||
} from "../index"
|
} from "../index"
|
||||||
import {
|
import {
|
||||||
PaymentCollectionStatus,
|
PaymentCollectionStatus,
|
||||||
@@ -118,7 +119,6 @@ describe("PaymentCollectionService", () => {
|
|||||||
{
|
{
|
||||||
id: IdMap.getId("payment-123"),
|
id: IdMap.getId("payment-123"),
|
||||||
amount: 35000,
|
amount: 35000,
|
||||||
captured_amount: 0,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
status: PaymentCollectionStatus.AUTHORIZED,
|
status: PaymentCollectionStatus.AUTHORIZED,
|
||||||
@@ -288,7 +288,6 @@ describe("PaymentCollectionService", () => {
|
|||||||
it("should update a payment collection with the right arguments", async () => {
|
it("should update a payment collection with the right arguments", async () => {
|
||||||
const submittedChanges = {
|
const submittedChanges = {
|
||||||
description: "updated description",
|
description: "updated description",
|
||||||
status: PaymentCollectionStatus.CAPTURED,
|
|
||||||
metadata: {
|
metadata: {
|
||||||
extra: 123,
|
extra: 123,
|
||||||
arr: ["a", "b", "c"],
|
arr: ["a", "b", "c"],
|
||||||
@@ -323,7 +322,6 @@ describe("PaymentCollectionService", () => {
|
|||||||
it("should throw error to update a non-existing payment collection", async () => {
|
it("should throw error to update a non-existing payment collection", async () => {
|
||||||
const submittedChanges = {
|
const submittedChanges = {
|
||||||
description: "updated description",
|
description: "updated description",
|
||||||
status: PaymentCollectionStatus.CAPTURED,
|
|
||||||
metadata: {
|
metadata: {
|
||||||
extra: 123,
|
extra: 123,
|
||||||
arr: ["a", "b", "c"],
|
arr: ["a", "b", "c"],
|
||||||
@@ -530,7 +528,6 @@ describe("PaymentCollectionService", () => {
|
|||||||
IdMap.getId("payCol_session1"),
|
IdMap.getId("payCol_session1"),
|
||||||
{
|
{
|
||||||
customer_id: "customer1",
|
customer_id: "customer1",
|
||||||
amount: 100,
|
|
||||||
provider_id: IdMap.getId("region1_provider1"),
|
provider_id: IdMap.getId("region1_provider1"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -544,19 +541,20 @@ describe("PaymentCollectionService", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should fail to refresh a payment session if the amount is different", async () => {
|
it("should throw to refresh a payment session that doesn't exist", async () => {
|
||||||
const sess = paymentCollectionService.refreshPaymentSession(
|
const sess = paymentCollectionService.refreshPaymentSession(
|
||||||
IdMap.getId("payment-collection-session"),
|
IdMap.getId("payment-collection-session"),
|
||||||
IdMap.getId("payCol_session1"),
|
IdMap.getId("payCol_session-not-found"),
|
||||||
{
|
{
|
||||||
customer_id: "customer1",
|
customer_id: "customer1",
|
||||||
amount: 80,
|
|
||||||
provider_id: IdMap.getId("region1_provider1"),
|
provider_id: IdMap.getId("region1_provider1"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(sess).rejects.toThrow(
|
expect(sess).rejects.toThrow(
|
||||||
"The amount has to be the same as the existing payment session"
|
`Session with id ${IdMap.getId(
|
||||||
|
"payCol_session-not-found"
|
||||||
|
)} was not found`
|
||||||
)
|
)
|
||||||
expect(PaymentProviderServiceMock.refreshSessionNew).toBeCalledTimes(0)
|
expect(PaymentProviderServiceMock.refreshSessionNew).toBeCalledTimes(0)
|
||||||
expect(DefaultProviderMock.deletePayment).toBeCalledTimes(0)
|
expect(DefaultProviderMock.deletePayment).toBeCalledTimes(0)
|
||||||
@@ -633,56 +631,4 @@ describe("PaymentCollectionService", () => {
|
|||||||
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(0)
|
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Capture Payments", () => {
|
|
||||||
afterEach(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should throw error if the status is not authorized", async () => {
|
|
||||||
paymentCollectionRepository.getPaymentCollectionIdByPaymentId = jest
|
|
||||||
.fn()
|
|
||||||
.mockReturnValue(Promise.resolve(notAuthorizedSample))
|
|
||||||
|
|
||||||
PaymentProviderServiceMock.capturePayment = jest
|
|
||||||
.fn()
|
|
||||||
.mockReturnValue(Promise.resolve())
|
|
||||||
|
|
||||||
const ret = paymentCollectionService.capture(
|
|
||||||
IdMap.getId("payment-collection-not-authorized")
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(ret).rejects.toThrowError(
|
|
||||||
new Error(
|
|
||||||
`A Payment Collection with status ${PaymentCollectionStatus.PARTIALLY_AUTHORIZED} cannot capture payment`
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(PaymentProviderServiceMock.capturePayment).toBeCalledTimes(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should emit PAYMENT_CAPTURE_FAILED if payment capture has failed", async () => {
|
|
||||||
paymentCollectionRepository.getPaymentCollectionIdByPaymentId = jest
|
|
||||||
.fn()
|
|
||||||
.mockReturnValue(Promise.resolve(fullyAuthorizedSample))
|
|
||||||
|
|
||||||
PaymentProviderServiceMock.retrievePayment = jest.fn().mockReturnValue(
|
|
||||||
Promise.resolve({
|
|
||||||
id: IdMap.getId("payment-123"),
|
|
||||||
amount: 35000,
|
|
||||||
captured_amount: 0,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
PaymentProviderServiceMock.capturePayment = jest
|
|
||||||
.fn()
|
|
||||||
.mockRejectedValue("capture failed")
|
|
||||||
|
|
||||||
const ret = paymentCollectionService.capture(IdMap.getId("payment-123"))
|
|
||||||
|
|
||||||
expect(ret).rejects.toThrowError(
|
|
||||||
new Error(`Failed to capture Payment ${IdMap.getId("payment-123")}`)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { MockManager, MockRepository } from "medusa-test-utils"
|
import { MockManager, MockRepository } from "medusa-test-utils"
|
||||||
import PaymentProviderService from "../payment-provider"
|
import PaymentProviderService from "../payment-provider"
|
||||||
import { testPayServiceMock } from "../__mocks__/test-pay"
|
import { testPayServiceMock } from "../__mocks__/test-pay"
|
||||||
|
import { FlagRouter } from "../../utils/flag-router"
|
||||||
|
|
||||||
describe("PaymentProviderService", () => {
|
describe("PaymentProviderService", () => {
|
||||||
describe("retrieveProvider", () => {
|
describe("retrieveProvider", () => {
|
||||||
@@ -106,6 +107,10 @@ describe("PaymentProviderService", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe(`PaymentProviderService`, () => {
|
describe(`PaymentProviderService`, () => {
|
||||||
|
const featureFlagRouter = new FlagRouter({
|
||||||
|
order_editing: false,
|
||||||
|
})
|
||||||
|
|
||||||
const container = {
|
const container = {
|
||||||
manager: MockManager,
|
manager: MockManager,
|
||||||
paymentSessionRepository: MockRepository({
|
paymentSessionRepository: MockRepository({
|
||||||
@@ -128,7 +133,8 @@ describe(`PaymentProviderService`, () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
find: () =>
|
find: () =>
|
||||||
Promise.resolve([{
|
Promise.resolve([
|
||||||
|
{
|
||||||
id: "pay_jadazdjk",
|
id: "pay_jadazdjk",
|
||||||
provider_id: "default_provider",
|
provider_id: "default_provider",
|
||||||
data: {
|
data: {
|
||||||
@@ -136,11 +142,13 @@ describe(`PaymentProviderService`, () => {
|
|||||||
},
|
},
|
||||||
captured_at: new Date(),
|
captured_at: new Date(),
|
||||||
amount: 100,
|
amount: 100,
|
||||||
amount_refunded: 0
|
amount_refunded: 0,
|
||||||
}]),
|
},
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
refundRepository: MockRepository(),
|
refundRepository: MockRepository(),
|
||||||
pp_default_provider: testPayServiceMock,
|
pp_default_provider: testPayServiceMock,
|
||||||
|
featureFlagRouter,
|
||||||
}
|
}
|
||||||
const providerService = new PaymentProviderService(container)
|
const providerService = new PaymentProviderService(container)
|
||||||
|
|
||||||
@@ -206,29 +214,25 @@ describe(`PaymentProviderService`, () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("successfully delete session", async () => {
|
it("successfully delete session", async () => {
|
||||||
await providerService.deleteSession(
|
await providerService.deleteSession({
|
||||||
{
|
|
||||||
id: "session",
|
id: "session",
|
||||||
provider_id: "default_provider",
|
provider_id: "default_provider",
|
||||||
data: {
|
data: {
|
||||||
id: "1234",
|
id: "1234",
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
expect(testPayServiceMock.deletePayment).toBeCalledTimes(1)
|
expect(testPayServiceMock.deletePayment).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully delete session", async () => {
|
it("successfully delete session", async () => {
|
||||||
await providerService.deleteSession(
|
await providerService.deleteSession({
|
||||||
{
|
|
||||||
id: "session",
|
id: "session",
|
||||||
provider_id: "default_provider",
|
provider_id: "default_provider",
|
||||||
data: {
|
data: {
|
||||||
id: "1234",
|
id: "1234",
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
expect(testPayServiceMock.deletePayment).toBeCalledTimes(1)
|
expect(testPayServiceMock.deletePayment).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
@@ -265,22 +269,27 @@ describe(`PaymentProviderService`, () => {
|
|||||||
|
|
||||||
it("successfully cancel payment", async () => {
|
it("successfully cancel payment", async () => {
|
||||||
await providerService.cancelPayment({
|
await providerService.cancelPayment({
|
||||||
id: "pay_jadazdjk"
|
id: "pay_jadazdjk",
|
||||||
})
|
})
|
||||||
expect(testPayServiceMock.cancelPayment).toBeCalledTimes(1)
|
expect(testPayServiceMock.cancelPayment).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully capture payment", async () => {
|
it("successfully capture payment", async () => {
|
||||||
await providerService.capturePayment({
|
await providerService.capturePayment({
|
||||||
id: "pay_jadazdjk"
|
id: "pay_jadazdjk",
|
||||||
})
|
})
|
||||||
expect(testPayServiceMock.capturePayment).toBeCalledTimes(1)
|
expect(testPayServiceMock.capturePayment).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully refund payment", async () => {
|
it("successfully refund payment", async () => {
|
||||||
await providerService.refundPayment([{
|
await providerService.refundPayment(
|
||||||
id: "pay_jadazdjk"
|
[
|
||||||
}], 50)
|
{
|
||||||
|
id: "pay_jadazdjk",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
50
|
||||||
|
)
|
||||||
expect(testPayServiceMock.refundPayment).toBeCalledTimes(1)
|
expect(testPayServiceMock.refundPayment).toBeCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -28,6 +28,7 @@ export { default as OrderEditService } from "./order-edit"
|
|||||||
export { default as OrderEditItemChangeService } from "./order-edit-item-change"
|
export { default as OrderEditItemChangeService } from "./order-edit-item-change"
|
||||||
export { default as PaymentCollectionService } from "./payment-collection"
|
export { default as PaymentCollectionService } from "./payment-collection"
|
||||||
export { default as PaymentProviderService } from "./payment-provider"
|
export { default as PaymentProviderService } from "./payment-provider"
|
||||||
|
export { default as PaymentService } from "./payment"
|
||||||
export { default as PriceListService } from "./price-list"
|
export { default as PriceListService } from "./price-list"
|
||||||
export { default as PricingService } from "./pricing"
|
export { default as PricingService } from "./pricing"
|
||||||
export { default as ProductService } from "./product"
|
export { default as ProductService } from "./product"
|
||||||
|
|||||||
@@ -661,7 +661,7 @@ export default class OrderEditService extends TransactionBaseService {
|
|||||||
|
|
||||||
let orderEdit = await this.retrieve(orderEditId, {
|
let orderEdit = await this.retrieve(orderEditId, {
|
||||||
relations: ["changes"],
|
relations: ["changes"],
|
||||||
select: ["id", "requested_at"],
|
select: ["id", "order_id", "requested_at"],
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!orderEdit.changes?.length) {
|
if (!orderEdit.changes?.length) {
|
||||||
|
|||||||
@@ -6,18 +6,17 @@ import { buildQuery, isDefined, setMetadata } from "../utils"
|
|||||||
import { PaymentCollectionRepository } from "../repositories/payment-collection"
|
import { PaymentCollectionRepository } from "../repositories/payment-collection"
|
||||||
import {
|
import {
|
||||||
Customer,
|
Customer,
|
||||||
Payment,
|
|
||||||
PaymentCollection,
|
PaymentCollection,
|
||||||
PaymentCollectionStatus,
|
PaymentCollectionStatus,
|
||||||
PaymentSession,
|
PaymentSession,
|
||||||
PaymentSessionStatus,
|
PaymentSessionStatus,
|
||||||
Refund,
|
|
||||||
} from "../models"
|
} from "../models"
|
||||||
import { TransactionBaseService } from "../interfaces"
|
import { TransactionBaseService } from "../interfaces"
|
||||||
import {
|
import {
|
||||||
CustomerService,
|
CustomerService,
|
||||||
EventBusService,
|
EventBusService,
|
||||||
PaymentProviderService,
|
PaymentProviderService,
|
||||||
|
PaymentService,
|
||||||
} from "./index"
|
} from "./index"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -40,10 +39,6 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
|||||||
UPDATED: "payment-collection.updated",
|
UPDATED: "payment-collection.updated",
|
||||||
DELETED: "payment-collection.deleted",
|
DELETED: "payment-collection.deleted",
|
||||||
PAYMENT_AUTHORIZED: "payment-collection.payment_authorized",
|
PAYMENT_AUTHORIZED: "payment-collection.payment_authorized",
|
||||||
PAYMENT_CAPTURED: "payment-collection.payment_captured",
|
|
||||||
PAYMENT_CAPTURE_FAILED: "payment-collection.payment_capture_failed",
|
|
||||||
REFUND_CREATED: "payment-collection.payment_refund_created",
|
|
||||||
REFUND_FAILED: "payment-collection.payment_refund_failed",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly manager_: EntityManager
|
protected readonly manager_: EntityManager
|
||||||
@@ -298,7 +293,7 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
|||||||
async refreshPaymentSession(
|
async refreshPaymentSession(
|
||||||
paymentCollectionId: string,
|
paymentCollectionId: string,
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
sessionInput: PaymentCollectionSessionInput
|
sessionInput: Omit<PaymentCollectionSessionInput, "amount">
|
||||||
): Promise<PaymentSession> {
|
): Promise<PaymentSession> {
|
||||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
const paymentCollectionRepository = manager.getCustomRepository(
|
const paymentCollectionRepository = manager.getCustomRepository(
|
||||||
@@ -328,10 +323,10 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
|||||||
(sess) => sessionId === sess?.id
|
(sess) => sessionId === sess?.id
|
||||||
)
|
)
|
||||||
|
|
||||||
if (session?.amount !== sessionInput.amount) {
|
if (!session) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
"The amount has to be the same as the existing payment session"
|
`Session with id ${sessionId} was not found`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,7 +355,7 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
|||||||
return sess
|
return sess
|
||||||
})
|
})
|
||||||
|
|
||||||
if (session.payment_authorized_at) {
|
if (session.payment_authorized_at && payCol.authorized_amount) {
|
||||||
payCol.authorized_amount -= session.amount
|
payCol.authorized_amount -= session.amount
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,7 +396,9 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let authorizedAmount = 0
|
let authorizedAmount = 0
|
||||||
for (const session of payCol.payment_sessions) {
|
for (let i = 0; i < payCol.payment_sessions.length; i++) {
|
||||||
|
const session = payCol.payment_sessions[i]
|
||||||
|
|
||||||
if (session.payment_authorized_at) {
|
if (session.payment_authorized_at) {
|
||||||
authorizedAmount += session.amount
|
authorizedAmount += session.amount
|
||||||
continue
|
continue
|
||||||
@@ -411,14 +408,21 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
|||||||
.withTransaction(manager)
|
.withTransaction(manager)
|
||||||
.authorizePayment(session, context)
|
.authorizePayment(session, context)
|
||||||
|
|
||||||
|
if (auth) {
|
||||||
|
payCol.payment_sessions[i] = auth
|
||||||
|
}
|
||||||
|
|
||||||
if (auth?.status === PaymentSessionStatus.AUTHORIZED) {
|
if (auth?.status === PaymentSessionStatus.AUTHORIZED) {
|
||||||
authorizedAmount += session.amount
|
authorizedAmount += session.amount
|
||||||
|
|
||||||
const inputData: Omit<PaymentProviderDataInput, "customer"> = {
|
const inputData: Omit<PaymentProviderDataInput, "customer"> & {
|
||||||
|
payment_session: PaymentSession
|
||||||
|
} = {
|
||||||
amount: session.amount,
|
amount: session.amount,
|
||||||
currency_code: payCol.currency_code,
|
currency_code: payCol.currency_code,
|
||||||
provider_id: session.provider_id,
|
provider_id: session.provider_id,
|
||||||
resource_id: payCol.id,
|
resource_id: payCol.id,
|
||||||
|
payment_session: auth,
|
||||||
}
|
}
|
||||||
|
|
||||||
payCol.payments.push(
|
payCol.payments.push(
|
||||||
@@ -447,235 +451,4 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
|||||||
return payCol
|
return payCol
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private async capturePayment(
|
|
||||||
payCol: PaymentCollection,
|
|
||||||
payment: Payment
|
|
||||||
): Promise<Payment> {
|
|
||||||
if (payment?.captured_at) {
|
|
||||||
return payment
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
|
||||||
const allowedStatuses = [
|
|
||||||
PaymentCollectionStatus.AUTHORIZED,
|
|
||||||
PaymentCollectionStatus.PARTIALLY_CAPTURED,
|
|
||||||
PaymentCollectionStatus.REQUIRES_ACTION,
|
|
||||||
]
|
|
||||||
|
|
||||||
if (!allowedStatuses.includes(payCol.status)) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
`A Payment Collection with status ${payCol.status} cannot capture payment`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let captureError: Error | null = null
|
|
||||||
const capturedPayment = await this.paymentProviderService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.capturePayment(payment)
|
|
||||||
.catch((err) => {
|
|
||||||
captureError = err
|
|
||||||
})
|
|
||||||
|
|
||||||
payCol.captured_amount = payCol.captured_amount ?? 0
|
|
||||||
if (capturedPayment) {
|
|
||||||
payCol.captured_amount += payment.amount
|
|
||||||
}
|
|
||||||
|
|
||||||
if (payCol.captured_amount === 0) {
|
|
||||||
payCol.status = PaymentCollectionStatus.REQUIRES_ACTION
|
|
||||||
} else if (payCol.captured_amount === payCol.amount) {
|
|
||||||
payCol.status = PaymentCollectionStatus.CAPTURED
|
|
||||||
} else {
|
|
||||||
payCol.status = PaymentCollectionStatus.PARTIALLY_CAPTURED
|
|
||||||
}
|
|
||||||
|
|
||||||
const paymentCollectionRepository = manager.getCustomRepository(
|
|
||||||
this.paymentCollectionRepository_
|
|
||||||
)
|
|
||||||
|
|
||||||
await paymentCollectionRepository.save(payCol)
|
|
||||||
|
|
||||||
if (!capturedPayment) {
|
|
||||||
await this.eventBusService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.emit(PaymentCollectionService.Events.PAYMENT_CAPTURE_FAILED, {
|
|
||||||
...payment,
|
|
||||||
error: captureError,
|
|
||||||
})
|
|
||||||
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.UNEXPECTED_STATE,
|
|
||||||
`Failed to capture Payment ${payment.id}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.eventBusService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.emit(PaymentCollectionService.Events.PAYMENT_CAPTURED, capturedPayment)
|
|
||||||
|
|
||||||
return capturedPayment
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async capture(paymentId: string): Promise<Payment> {
|
|
||||||
const manager = this.transactionManager_ ?? this.manager_
|
|
||||||
const paymentCollectionRepository = manager.getCustomRepository(
|
|
||||||
this.paymentCollectionRepository_
|
|
||||||
)
|
|
||||||
|
|
||||||
const payCol =
|
|
||||||
await paymentCollectionRepository.getPaymentCollectionIdByPaymentId(
|
|
||||||
paymentId,
|
|
||||||
{
|
|
||||||
relations: ["payments"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const payment = payCol.payments.find((payment) => paymentId === payment?.id)
|
|
||||||
|
|
||||||
return await this.capturePayment(payCol, payment!)
|
|
||||||
}
|
|
||||||
|
|
||||||
async captureAll(paymentCollectionId: string): Promise<Payment[]> {
|
|
||||||
const payCol = await this.retrieve(paymentCollectionId, {
|
|
||||||
relations: ["payments"],
|
|
||||||
})
|
|
||||||
|
|
||||||
const allPayments: Payment[] = []
|
|
||||||
for (const payment of payCol.payments) {
|
|
||||||
const captured = await this.capturePayment(payCol, payment).catch(
|
|
||||||
() => void 0
|
|
||||||
)
|
|
||||||
|
|
||||||
if (captured) {
|
|
||||||
allPayments.push(captured)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return allPayments
|
|
||||||
}
|
|
||||||
|
|
||||||
private async refundPayment(
|
|
||||||
payCol: PaymentCollection,
|
|
||||||
payment: Payment,
|
|
||||||
amount: number,
|
|
||||||
reason: string,
|
|
||||||
note?: string
|
|
||||||
): Promise<Refund> {
|
|
||||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
|
||||||
if (!payment.captured_at) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
`Payment ${payment.id} is not captured`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const refundable = payment.amount - payment.amount_refunded
|
|
||||||
if (amount > refundable) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
|
||||||
`Only ${refundable} can be refunded from Payment ${payment.id}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let refundError: Error | null = null
|
|
||||||
const refund = await this.paymentProviderService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.refundFromPayment(payment, amount, reason, note)
|
|
||||||
.catch((err) => {
|
|
||||||
refundError = err
|
|
||||||
})
|
|
||||||
|
|
||||||
payCol.refunded_amount = payCol.refunded_amount ?? 0
|
|
||||||
if (refund) {
|
|
||||||
payCol.refunded_amount += refund.amount
|
|
||||||
}
|
|
||||||
|
|
||||||
if (payCol.refunded_amount === 0) {
|
|
||||||
payCol.status = PaymentCollectionStatus.REQUIRES_ACTION
|
|
||||||
} else if (payCol.refunded_amount === payCol.amount) {
|
|
||||||
payCol.status = PaymentCollectionStatus.REFUNDED
|
|
||||||
} else {
|
|
||||||
payCol.status = PaymentCollectionStatus.PARTIALLY_REFUNDED
|
|
||||||
}
|
|
||||||
|
|
||||||
const paymentCollectionRepository = manager.getCustomRepository(
|
|
||||||
this.paymentCollectionRepository_
|
|
||||||
)
|
|
||||||
|
|
||||||
await paymentCollectionRepository.save(payCol)
|
|
||||||
|
|
||||||
if (!refund) {
|
|
||||||
await this.eventBusService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.emit(PaymentCollectionService.Events.REFUND_FAILED, {
|
|
||||||
...payment,
|
|
||||||
error: refundError,
|
|
||||||
})
|
|
||||||
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.UNEXPECTED_STATE,
|
|
||||||
`Failed to refund Payment ${payment.id}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.eventBusService_
|
|
||||||
.withTransaction(manager)
|
|
||||||
.emit(PaymentCollectionService.Events.REFUND_CREATED, refund)
|
|
||||||
|
|
||||||
return refund
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async refund(
|
|
||||||
paymentId: string,
|
|
||||||
amount: number,
|
|
||||||
reason: string,
|
|
||||||
note?: string
|
|
||||||
): Promise<Refund> {
|
|
||||||
const manager = this.transactionManager_ ?? this.manager_
|
|
||||||
const paymentCollectionRepository = manager.getCustomRepository(
|
|
||||||
this.paymentCollectionRepository_
|
|
||||||
)
|
|
||||||
|
|
||||||
const payCol =
|
|
||||||
await paymentCollectionRepository.getPaymentCollectionIdByPaymentId(
|
|
||||||
paymentId
|
|
||||||
)
|
|
||||||
|
|
||||||
const payment = await this.paymentProviderService_.retrievePayment(
|
|
||||||
paymentId
|
|
||||||
)
|
|
||||||
|
|
||||||
return await this.refundPayment(payCol, payment, amount, reason, note)
|
|
||||||
}
|
|
||||||
|
|
||||||
async refundAll(
|
|
||||||
paymentCollectionId: string,
|
|
||||||
reason: string,
|
|
||||||
note?: string
|
|
||||||
): Promise<Refund[]> {
|
|
||||||
const payCol = await this.retrieve(paymentCollectionId, {
|
|
||||||
relations: ["payments"],
|
|
||||||
})
|
|
||||||
|
|
||||||
const allRefunds: Refund[] = []
|
|
||||||
for (const payment of payCol.payments) {
|
|
||||||
const refunded = await this.refundPayment(
|
|
||||||
payCol,
|
|
||||||
payment,
|
|
||||||
payment.amount,
|
|
||||||
reason,
|
|
||||||
note
|
|
||||||
).catch(() => void 0)
|
|
||||||
|
|
||||||
if (refunded) {
|
|
||||||
allRefunds.push(refunded)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return allRefunds
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,9 @@ import {
|
|||||||
Refund,
|
Refund,
|
||||||
} from "../models"
|
} from "../models"
|
||||||
import { PaymentProviderDataInput } from "../types/payment-collection"
|
import { PaymentProviderDataInput } from "../types/payment-collection"
|
||||||
|
import { FlagRouter } from "../utils/flag-router"
|
||||||
|
import OrderEditingFeatureFlag from "../loaders/feature-flags/order-editing"
|
||||||
|
import PaymentService from "./payment"
|
||||||
|
|
||||||
type PaymentProviderKey = `pp_${string}` | "systemPaymentProviderService"
|
type PaymentProviderKey = `pp_${string}` | "systemPaymentProviderService"
|
||||||
type InjectedDependencies = {
|
type InjectedDependencies = {
|
||||||
@@ -25,6 +28,8 @@ type InjectedDependencies = {
|
|||||||
paymentProviderRepository: typeof PaymentProviderRepository
|
paymentProviderRepository: typeof PaymentProviderRepository
|
||||||
paymentRepository: typeof PaymentRepository
|
paymentRepository: typeof PaymentRepository
|
||||||
refundRepository: typeof RefundRepository
|
refundRepository: typeof RefundRepository
|
||||||
|
paymentService: PaymentService
|
||||||
|
featureFlagRouter: FlagRouter
|
||||||
} & {
|
} & {
|
||||||
[key in `${PaymentProviderKey}`]:
|
[key in `${PaymentProviderKey}`]:
|
||||||
| AbstractPaymentService
|
| AbstractPaymentService
|
||||||
@@ -44,6 +49,8 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
protected readonly paymentRepository_: typeof PaymentRepository
|
protected readonly paymentRepository_: typeof PaymentRepository
|
||||||
protected readonly refundRepository_: typeof RefundRepository
|
protected readonly refundRepository_: typeof RefundRepository
|
||||||
|
|
||||||
|
protected readonly featureFlagRouter_: FlagRouter
|
||||||
|
|
||||||
constructor(container: InjectedDependencies) {
|
constructor(container: InjectedDependencies) {
|
||||||
super(container)
|
super(container)
|
||||||
|
|
||||||
@@ -53,6 +60,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
this.paymentProviderRepository_ = container.paymentProviderRepository
|
this.paymentProviderRepository_ = container.paymentProviderRepository
|
||||||
this.paymentRepository_ = container.paymentRepository
|
this.paymentRepository_ = container.paymentRepository
|
||||||
this.refundRepository_ = container.refundRepository
|
this.refundRepository_ = container.refundRepository
|
||||||
|
this.featureFlagRouter_ = container.featureFlagRouter
|
||||||
}
|
}
|
||||||
|
|
||||||
async registerInstalledProviders(providerIds: string[]): Promise<void> {
|
async registerInstalledProviders(providerIds: string[]): Promise<void> {
|
||||||
@@ -184,9 +192,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
sessionInput: PaymentProviderDataInput
|
sessionInput: PaymentProviderDataInput
|
||||||
): Promise<PaymentSession> {
|
): Promise<PaymentSession> {
|
||||||
return await this.atomicPhase_(async (transactionManager) => {
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
const provider: AbstractPaymentService = this.retrieveProvider(
|
const provider = this.retrieveProvider(sessionInput.provider_id)
|
||||||
sessionInput.provider_id
|
|
||||||
)
|
|
||||||
const sessionData = await provider
|
const sessionData = await provider
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.createPaymentNew(sessionInput)
|
.createPaymentNew(sessionInput)
|
||||||
@@ -396,7 +402,9 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createPaymentNew(
|
async createPaymentNew(
|
||||||
paymentInput: Omit<PaymentProviderDataInput, "customer">
|
paymentInput: Omit<PaymentProviderDataInput, "customer"> & {
|
||||||
|
payment_session: PaymentSession
|
||||||
|
}
|
||||||
): Promise<Payment> {
|
): Promise<Payment> {
|
||||||
return await this.atomicPhase_(async (transactionManager) => {
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
const { payment_session, currency_code, amount, provider_id } =
|
const { payment_session, currency_code, amount, provider_id } =
|
||||||
@@ -407,18 +415,13 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.getPaymentData(payment_session)
|
.getPaymentData(payment_session)
|
||||||
|
|
||||||
const paymentRepo = transactionManager.getCustomRepository(
|
const paymentService = this.container_.paymentService
|
||||||
this.paymentRepository_
|
return await paymentService.withTransaction(transactionManager).create({
|
||||||
)
|
|
||||||
|
|
||||||
const created = paymentRepo.create({
|
|
||||||
provider_id,
|
provider_id,
|
||||||
amount,
|
amount,
|
||||||
currency_code,
|
currency_code,
|
||||||
data: paymentData,
|
data: paymentData,
|
||||||
})
|
})
|
||||||
|
|
||||||
return await paymentRepo.save(created)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,20 +430,10 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
data: { order_id?: string; swap_id?: string }
|
data: { order_id?: string; swap_id?: string }
|
||||||
): Promise<Payment> {
|
): Promise<Payment> {
|
||||||
return await this.atomicPhase_(async (transactionManager) => {
|
return await this.atomicPhase_(async (transactionManager) => {
|
||||||
const payment = await this.retrievePayment(paymentId)
|
const paymentService = this.container_.paymentService
|
||||||
|
return await paymentService
|
||||||
if (data?.order_id) {
|
.withTransaction(transactionManager)
|
||||||
payment.order_id = data.order_id
|
.update(paymentId, data)
|
||||||
}
|
|
||||||
|
|
||||||
if (data?.swap_id) {
|
|
||||||
payment.swap_id = data.swap_id
|
|
||||||
}
|
|
||||||
|
|
||||||
const payRepo = transactionManager.getCustomRepository(
|
|
||||||
this.paymentRepository_
|
|
||||||
)
|
|
||||||
return await payRepo.save(payment)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,6 +458,13 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
session.data = data
|
session.data = data
|
||||||
session.status = status
|
session.status = status
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.featureFlagRouter_.isFeatureEnabled(OrderEditingFeatureFlag.key) &&
|
||||||
|
status === PaymentSessionStatus.AUTHORIZED
|
||||||
|
) {
|
||||||
|
session.payment_authorized_at = new Date()
|
||||||
|
}
|
||||||
|
|
||||||
const sessionRepo = transactionManager.getCustomRepository(
|
const sessionRepo = transactionManager.getCustomRepository(
|
||||||
this.paymentSessionRepository_
|
this.paymentSessionRepository_
|
||||||
)
|
)
|
||||||
@@ -562,7 +562,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
if (refundable < amount) {
|
if (refundable < amount) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
"Refund amount is higher that the refundable amount"
|
"Refund amount is greater that the refundable amount"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,7 +634,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
|||||||
if (refundable < amount) {
|
if (refundable < amount) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
"Refund amount is higher that the refundable amount"
|
"Refund amount is greater that the refundable amount"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,229 @@
|
|||||||
|
import { PaymentRepository } from "./../repositories/payment"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
|
import { Payment, Refund } from "../models"
|
||||||
|
import { TransactionBaseService } from "../interfaces"
|
||||||
|
import { EventBusService, PaymentProviderService } from "./index"
|
||||||
|
import { buildQuery } from "../utils"
|
||||||
|
import { FindConfig } from "../types/common"
|
||||||
|
|
||||||
|
type InjectedDependencies = {
|
||||||
|
manager: EntityManager
|
||||||
|
paymentProviderService: PaymentProviderService
|
||||||
|
eventBusService: EventBusService
|
||||||
|
paymentRepository: typeof PaymentRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PaymentDataInput = {
|
||||||
|
currency_code: string
|
||||||
|
provider_id: string
|
||||||
|
amount: number
|
||||||
|
data: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class PaymentService extends TransactionBaseService {
|
||||||
|
protected readonly manager_: EntityManager
|
||||||
|
protected transactionManager_: EntityManager | undefined
|
||||||
|
protected readonly eventBusService_: EventBusService
|
||||||
|
protected readonly paymentProviderService_: PaymentProviderService
|
||||||
|
protected readonly paymentRepository_: typeof PaymentRepository
|
||||||
|
static readonly Events = {
|
||||||
|
CREATED: "payment.created",
|
||||||
|
UPDATED: "payment.updated",
|
||||||
|
PAYMENT_CAPTURED: "payment.payment_captured",
|
||||||
|
PAYMENT_CAPTURE_FAILED: "payment.payment_capture_failed",
|
||||||
|
REFUND_CREATED: "payment.payment_refund_created",
|
||||||
|
REFUND_FAILED: "payment.payment_refund_failed",
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
manager,
|
||||||
|
paymentRepository,
|
||||||
|
paymentProviderService,
|
||||||
|
eventBusService,
|
||||||
|
}: InjectedDependencies) {
|
||||||
|
// eslint-disable-next-line prefer-rest-params
|
||||||
|
super(arguments[0])
|
||||||
|
|
||||||
|
this.manager_ = manager
|
||||||
|
this.paymentRepository_ = paymentRepository
|
||||||
|
this.paymentProviderService_ = paymentProviderService
|
||||||
|
this.eventBusService_ = eventBusService
|
||||||
|
}
|
||||||
|
|
||||||
|
async retrieve(
|
||||||
|
paymentId: string,
|
||||||
|
config: FindConfig<Payment> = {}
|
||||||
|
): Promise<Payment> {
|
||||||
|
const manager = this.transactionManager_ ?? this.manager_
|
||||||
|
const paymentRepository = manager.getCustomRepository(
|
||||||
|
this.paymentRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
const query = buildQuery({ id: paymentId }, config)
|
||||||
|
|
||||||
|
const payment = await paymentRepository.find(query)
|
||||||
|
|
||||||
|
if (!payment.length) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Payment with id ${paymentId} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return payment[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(paymentInput: PaymentDataInput): Promise<Payment> {
|
||||||
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
|
const { data, currency_code, amount, provider_id } = paymentInput
|
||||||
|
|
||||||
|
const paymentRepository = manager.getCustomRepository(
|
||||||
|
this.paymentRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
const created = paymentRepository.create({
|
||||||
|
provider_id,
|
||||||
|
amount,
|
||||||
|
currency_code,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
|
||||||
|
const saved = await paymentRepository.save(created)
|
||||||
|
|
||||||
|
await this.eventBusService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.emit(PaymentService.Events.CREATED, saved)
|
||||||
|
|
||||||
|
return saved
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
paymentId: string,
|
||||||
|
data: { order_id?: string; swap_id?: string }
|
||||||
|
): Promise<Payment> {
|
||||||
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
|
const payment = await this.retrieve(paymentId)
|
||||||
|
|
||||||
|
const paymentRepository = manager.getCustomRepository(
|
||||||
|
this.paymentRepository_
|
||||||
|
)
|
||||||
|
|
||||||
|
if (data?.order_id) {
|
||||||
|
payment.order_id = data.order_id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data?.swap_id) {
|
||||||
|
payment.swap_id = data.swap_id
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = await paymentRepository.save(payment)
|
||||||
|
|
||||||
|
await this.eventBusService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.emit(PaymentService.Events.UPDATED, updated)
|
||||||
|
|
||||||
|
return updated
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async capture(paymentOrId: string | Payment): Promise<Payment> {
|
||||||
|
const payment =
|
||||||
|
typeof paymentOrId === "string"
|
||||||
|
? await this.retrieve(paymentOrId)
|
||||||
|
: paymentOrId
|
||||||
|
|
||||||
|
if (payment?.captured_at) {
|
||||||
|
return payment
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
|
let captureError: Error | null = null
|
||||||
|
const capturedPayment = await this.paymentProviderService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.capturePayment(payment)
|
||||||
|
.catch((err) => {
|
||||||
|
captureError = err
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!capturedPayment) {
|
||||||
|
await this.eventBusService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.emit(PaymentService.Events.PAYMENT_CAPTURE_FAILED, {
|
||||||
|
...payment,
|
||||||
|
error: captureError,
|
||||||
|
})
|
||||||
|
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.UNEXPECTED_STATE,
|
||||||
|
`Failed to capture Payment ${payment.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.eventBusService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.emit(PaymentService.Events.PAYMENT_CAPTURED, capturedPayment)
|
||||||
|
|
||||||
|
return capturedPayment
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async refund(
|
||||||
|
paymentOrId: string | Payment,
|
||||||
|
amount: number,
|
||||||
|
reason: string,
|
||||||
|
note?: string
|
||||||
|
): Promise<Refund> {
|
||||||
|
const payment =
|
||||||
|
typeof paymentOrId === "string"
|
||||||
|
? await this.retrieve(paymentOrId)
|
||||||
|
: paymentOrId
|
||||||
|
|
||||||
|
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||||
|
if (!payment.captured_at) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
`Payment ${payment.id} is not captured`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const refundable = payment.amount - payment.amount_refunded
|
||||||
|
if (amount > refundable) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
`Only ${refundable} can be refunded from Payment ${payment.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let refundError: Error | null = null
|
||||||
|
const refund = await this.paymentProviderService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.refundFromPayment(payment, amount, reason, note)
|
||||||
|
.catch((err) => {
|
||||||
|
refundError = err
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!refund) {
|
||||||
|
await this.eventBusService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.emit(PaymentService.Events.REFUND_FAILED, {
|
||||||
|
...payment,
|
||||||
|
error: refundError,
|
||||||
|
})
|
||||||
|
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.UNEXPECTED_STATE,
|
||||||
|
`Failed to refund Payment ${payment.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.eventBusService_
|
||||||
|
.withTransaction(manager)
|
||||||
|
.emit(PaymentService.Events.REFUND_CREATED, refund)
|
||||||
|
|
||||||
|
return refund
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ export const defaultOrderEditRelations: string[] = [
|
|||||||
"items",
|
"items",
|
||||||
"items.adjustments",
|
"items.adjustments",
|
||||||
"items.tax_lines",
|
"items.tax_lines",
|
||||||
|
"payment_collection",
|
||||||
]
|
]
|
||||||
|
|
||||||
export const defaultOrderEditFields: (keyof OrderEdit)[] = [
|
export const defaultOrderEditFields: (keyof OrderEdit)[] = [
|
||||||
@@ -45,6 +46,7 @@ export const defaultOrderEditFields: (keyof OrderEdit)[] = [
|
|||||||
"canceled_by",
|
"canceled_by",
|
||||||
"canceled_at",
|
"canceled_at",
|
||||||
"internal_note",
|
"internal_note",
|
||||||
|
"payment_collection_id",
|
||||||
]
|
]
|
||||||
|
|
||||||
export const storeOrderEditNotAllowedFieldsAndRelations = [
|
export const storeOrderEditNotAllowedFieldsAndRelations = [
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
Customer,
|
Customer,
|
||||||
PaymentCollection,
|
PaymentCollection,
|
||||||
PaymentCollectionType,
|
PaymentCollectionType,
|
||||||
PaymentSession,
|
|
||||||
} from "../models"
|
} from "../models"
|
||||||
|
|
||||||
export type CreatePaymentCollectionInput = {
|
export type CreatePaymentCollectionInput = {
|
||||||
@@ -29,8 +28,6 @@ export type PaymentProviderDataInput = {
|
|||||||
currency_code: string
|
currency_code: string
|
||||||
provider_id: string
|
provider_id: string
|
||||||
amount: number
|
amount: number
|
||||||
payment_session?: PaymentSession
|
|
||||||
payment_description?: string
|
|
||||||
cart_id?: string
|
cart_id?: string
|
||||||
cart?: Cart
|
cart?: Cart
|
||||||
metadata?: any
|
metadata?: any
|
||||||
@@ -48,7 +45,6 @@ export const defaultPaymentCollectionFields: (keyof PaymentCollection)[] = [
|
|||||||
"description",
|
"description",
|
||||||
"amount",
|
"amount",
|
||||||
"authorized_amount",
|
"authorized_amount",
|
||||||
"refunded_amount",
|
|
||||||
"currency_code",
|
"currency_code",
|
||||||
"metadata",
|
"metadata",
|
||||||
"region",
|
"region",
|
||||||
|
|||||||
Reference in new issue
Block a user