feat(medusa): Payment Collection endpoints (#2525)
This commit is contained in:
1 parent
27184f3878
commit
755ba90c05
58 files changed
+1190
-484
No files matched your search
@@ -29,6 +29,8 @@ export * from "./routes/admin/gift-cards"
|
||||
export * from "./routes/admin/invites"
|
||||
export * from "./routes/admin/notes"
|
||||
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/orders"
|
||||
export * from "./routes/admin/price-lists"
|
||||
|
||||
@@ -33,6 +33,8 @@ import taxRateRoutes from "./tax-rates"
|
||||
import uploadRoutes from "./uploads"
|
||||
import userRoutes, { unauthenticatedUserRoutes } from "./users"
|
||||
import variantRoutes from "./variants"
|
||||
import paymentCollectionRoutes from "./payment-collections"
|
||||
import paymentRoutes from "./payments"
|
||||
|
||||
const route = Router()
|
||||
|
||||
@@ -99,6 +101,8 @@ export default (app, container, config) => {
|
||||
uploadRoutes(route)
|
||||
userRoutes(route)
|
||||
variantRoutes(route)
|
||||
paymentCollectionRoutes(route)
|
||||
paymentRoutes(route)
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { orderEditServiceMock } from "../../../../../services/__mocks__/order-edit"
|
||||
|
||||
import OrderEditingFeatureFlag from "../../../../../loaders/feature-flags/order-editing"
|
||||
|
||||
describe("GET /admin/order-edits/:id", () => {
|
||||
@@ -19,6 +20,9 @@ describe("GET /admin/order-edits/:id", () => {
|
||||
},
|
||||
},
|
||||
flags: [OrderEditingFeatureFlag],
|
||||
payload: {
|
||||
payment_collection_description: "PayCol description",
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -33,6 +37,8 @@ describe("GET /admin/order-edits/:id", () => {
|
||||
orderEditId,
|
||||
{ loggedInUserId: IdMap.getId("admin_user") }
|
||||
)
|
||||
|
||||
expect(orderEditServiceMock.update).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("returns updated orderEdit", () => {
|
||||
|
||||
@@ -18,6 +18,7 @@ import { AdminPostOrderEditsEditLineItemsReq } from "./add-line-item"
|
||||
import { AdminPostOrderEditsEditLineItemsLineItemReq } from "./update-order-edit-line-item"
|
||||
import { GetOrderEditsParams } from "./list-order-edit"
|
||||
import { GetOrderEditsOrderEditParams } from "./get-order-edit"
|
||||
import { AdminPostOrderEditsRequestConfirmationReq } from "./request-confirmation"
|
||||
|
||||
const route = Router()
|
||||
|
||||
@@ -85,6 +86,7 @@ export default (app) => {
|
||||
|
||||
route.post(
|
||||
"/:id/request",
|
||||
transformBody(AdminPostOrderEditsRequestConfirmationReq),
|
||||
middlewares.wrap(require("./request-confirmation").default)
|
||||
)
|
||||
|
||||
@@ -121,3 +123,4 @@ export * from "./create-order-edit"
|
||||
export * from "./get-order-edit"
|
||||
export * from "./list-order-edit"
|
||||
export * from "./add-line-item"
|
||||
export * from "./request-confirmation"
|
||||
@@ -1,9 +1,15 @@
|
||||
import { EntityManager } from "typeorm"
|
||||
import { OrderEditService } from "../../../../services"
|
||||
import { IsOptional, IsString, IsObject } from "class-validator"
|
||||
import {
|
||||
OrderEditService,
|
||||
OrderService,
|
||||
PaymentCollectionService,
|
||||
} from "../../../../services"
|
||||
import {
|
||||
defaultOrderEditFields,
|
||||
defaultOrderEditRelations,
|
||||
} from "../../../../types/order-edit"
|
||||
import { PaymentCollectionType } from "../../../../models"
|
||||
|
||||
/**
|
||||
* @oas [post] /order-edits/{id}/request
|
||||
@@ -54,26 +60,71 @@ import {
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
const validatedBody =
|
||||
req.validatedBody as AdminPostOrderEditsRequestConfirmationReq
|
||||
|
||||
const orderEditService: 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 loggedInUser = (req.user?.id ?? req.user?.userId) as string
|
||||
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
await orderEditService
|
||||
.withTransaction(transactionManager)
|
||||
.requestConfirmation(id, { loggedInUserId: loggedInUser })
|
||||
const orderEditServiceTx =
|
||||
orderEditService.withTransaction(transactionManager)
|
||||
|
||||
const orderEdit = await orderEditServiceTx.requestConfirmation(id, {
|
||||
loggedInUserId: loggedInUser,
|
||||
})
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const orderEdit = await orderEditService.retrieve(id, {
|
||||
let orderEdit = await orderEditService.retrieve(id, {
|
||||
relations: defaultOrderEditRelations,
|
||||
select: defaultOrderEditFields,
|
||||
})
|
||||
orderEdit = await orderEditService.decorateTotals(orderEdit)
|
||||
|
||||
res.status(200).send({
|
||||
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 swapRoutes from "./swaps"
|
||||
import variantRoutes from "./variants"
|
||||
import paymentCollectionRoutes from "./payment-collections"
|
||||
|
||||
const route = Router()
|
||||
|
||||
@@ -47,6 +48,7 @@ export default (app, container, config) => {
|
||||
returnRoutes(route)
|
||||
giftCardRoutes(route)
|
||||
returnReasonRoutes(route)
|
||||
paymentCollectionRoutes(route)
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
import { Request, Response } from "express"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { OrderEditService } from "../../../../services"
|
||||
import {
|
||||
OrderEditService,
|
||||
OrderService,
|
||||
PaymentProviderService,
|
||||
} from "../../../../services"
|
||||
import {
|
||||
defaultStoreOrderEditFields,
|
||||
defaultStoreOrderEditRelations,
|
||||
} from "../../../../types/order-edit"
|
||||
import { OrderEditStatus } from "../../../../models"
|
||||
import { OrderEditStatus, PaymentCollectionStatus } from "../../../../models"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
|
||||
/**
|
||||
@@ -55,13 +59,25 @@ export default async (req: Request, res: Response) => {
|
||||
const orderEditService: 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 userId = req.user?.customer_id ?? req.user?.id ?? req.user?.userId
|
||||
|
||||
await manager.transaction(async (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) {
|
||||
return orderEdit
|
||||
@@ -74,18 +90,31 @@ export default async (req: Request, res: Response) => {
|
||||
)
|
||||
}
|
||||
|
||||
// TODO once payment collection is done
|
||||
/*const paymentCollection = await this.paymentCollectionService_.withTransaction(manager).retrieve(orderEdit.payment_collection_id)
|
||||
if (!paymentCollection.authorized_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Unable to complete an order edit if the payment is not authorized"
|
||||
)
|
||||
}*/
|
||||
if (orderEdit.payment_collection) {
|
||||
if (
|
||||
orderEdit.payment_collection.status !==
|
||||
PaymentCollectionStatus.AUTHORIZED
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"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,
|
||||
})
|
||||
|
||||
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, {
|
||||
|
||||
+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
|
||||
public async retrieveSavedMethods(customer: Customer): Promise<Data[]> {
|
||||
return Promise.resolve([])
|
||||
return []
|
||||
}
|
||||
|
||||
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_STATUS_ENUM" AS ENUM (
|
||||
'not_paid', 'awaiting', 'authorized', 'partially_authorized', 'captured',
|
||||
'partially_captured', 'refunded', 'partially_refunded', 'canceled', 'requires_action'
|
||||
'not_paid', 'awaiting', 'authorized',
|
||||
'partially_authorized', 'canceled'
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS payment_collection
|
||||
@@ -27,8 +27,6 @@ export class paymentCollection1664880666982 implements MigrationInterface {
|
||||
description text NULL,
|
||||
amount integer NOT NULL,
|
||||
authorized_amount integer NULL,
|
||||
captured_amount integer NULL,
|
||||
refunded_amount integer NULL,
|
||||
region_id character varying NOT NULL,
|
||||
currency_code character varying NOT NULL,
|
||||
metadata jsonb NULL,
|
||||
@@ -78,6 +76,7 @@ export class paymentCollection1664880666982 implements MigrationInterface {
|
||||
ALTER TABLE refund ADD COLUMN payment_id character varying NULL;
|
||||
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 ALTER COLUMN order_id DROP NOT NULL;
|
||||
`)
|
||||
|
||||
// Add missing indexes
|
||||
@@ -97,6 +96,7 @@ export class paymentCollection1664880666982 implements MigrationInterface {
|
||||
|
||||
DROP INDEX "IDX_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_sessions DROP CONSTRAINT "FK_payment_collection_sessions_payment_collection_id";
|
||||
|
||||
@@ -21,12 +21,7 @@ export enum PaymentCollectionStatus {
|
||||
AWAITING = "awaiting",
|
||||
AUTHORIZED = "authorized",
|
||||
PARTIALLY_AUTHORIZED = "partially_authorized",
|
||||
CAPTURED = "captured",
|
||||
PARTIALLY_CAPTURED = "partially_captured",
|
||||
REFUNDED = "refunded",
|
||||
PARTIALLY_REFUNDED = "partially_refunded",
|
||||
CANCELED = "canceled",
|
||||
REQUIRES_ACTION = "requires_action",
|
||||
}
|
||||
|
||||
export enum PaymentCollectionType {
|
||||
@@ -41,20 +36,14 @@ export class PaymentCollection extends SoftDeletableEntity {
|
||||
@DbAwareColumn({ type: "enum", enum: PaymentCollectionStatus })
|
||||
status: PaymentCollectionStatus
|
||||
|
||||
@Column({ nullable: true })
|
||||
description: string
|
||||
@Column({ type: "varchar", nullable: true })
|
||||
description: string | null
|
||||
|
||||
@Column({ type: "int" })
|
||||
amount: number
|
||||
|
||||
@Column({ type: "int", nullable: true })
|
||||
authorized_amount: number
|
||||
|
||||
@Column({ type: "int", nullable: true })
|
||||
captured_amount: number
|
||||
|
||||
@Column({ type: "int", nullable: true })
|
||||
refunded_amount: number
|
||||
authorized_amount: number | null
|
||||
|
||||
@Index()
|
||||
@Column()
|
||||
|
||||
@@ -33,7 +33,7 @@ export class PublishableApiKey extends BaseEntity {
|
||||
* type: string
|
||||
* description: The key's ID
|
||||
* example: pak_01G1G5V27GYX4QXNARRQCW1N8T
|
||||
* created_by:
|
||||
* created_by:
|
||||
* type: string
|
||||
* description: "The unique identifier of the user that created the key."
|
||||
* example: usr_01G1G5V26F5TB3GPAPNJ8X1S3V
|
||||
|
||||
@@ -2,7 +2,7 @@ import { IdMap } from "medusa-test-utils"
|
||||
|
||||
export const orderEdit = {
|
||||
id: IdMap.getId("testCreatedOrder"),
|
||||
order_id: "empty-id",
|
||||
order_id: IdMap.getId("test-order"),
|
||||
internal_note: "internal note",
|
||||
declined_reason: null,
|
||||
declined_at: null,
|
||||
@@ -106,6 +106,9 @@ export const orderEditServiceMock = {
|
||||
created_by: context.loggedInUserId,
|
||||
})
|
||||
}),
|
||||
update: jest.fn().mockImplementation((id, data) => {
|
||||
return Promise.resolve(data)
|
||||
}),
|
||||
decline: jest.fn().mockImplementation((id, reason, userId) => {
|
||||
return Promise.resolve({
|
||||
id,
|
||||
@@ -114,8 +117,17 @@ export const orderEditServiceMock = {
|
||||
declined_at: new Date(),
|
||||
})
|
||||
}),
|
||||
getTotals: jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve({})
|
||||
getTotals: jest.fn().mockImplementation((id) => {
|
||||
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((_) => {
|
||||
return Promise.resolve()
|
||||
|
||||
@@ -42,6 +42,7 @@ export const orders = {
|
||||
},
|
||||
],
|
||||
regionid: IdMap.getId("testRegion"),
|
||||
currency_code: "USD",
|
||||
customerid: IdMap.getId("testCustomer"),
|
||||
payment_method: {
|
||||
providerid: "default_provider",
|
||||
@@ -96,6 +97,7 @@ export const orders = {
|
||||
},
|
||||
],
|
||||
regionid: IdMap.getId("region-france"),
|
||||
currency_code: "EUR",
|
||||
customerid: IdMap.getId("test-customer"),
|
||||
payment_method: {
|
||||
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({
|
||||
manager: MockManager,
|
||||
orderEditRepository,
|
||||
@@ -331,6 +330,10 @@ describe("OrderEditService", () => {
|
||||
let result
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(orderEditService, "getTotals").mockResolvedValue({
|
||||
difference_due: 1500,
|
||||
} as any)
|
||||
|
||||
result = await orderEditService.requestConfirmation(orderEditId, {
|
||||
loggedInUserId: userId,
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
EventBusService,
|
||||
PaymentCollectionService,
|
||||
PaymentProviderService,
|
||||
PaymentService,
|
||||
} from "../index"
|
||||
import {
|
||||
PaymentCollectionStatus,
|
||||
@@ -118,7 +119,6 @@ describe("PaymentCollectionService", () => {
|
||||
{
|
||||
id: IdMap.getId("payment-123"),
|
||||
amount: 35000,
|
||||
captured_amount: 0,
|
||||
},
|
||||
],
|
||||
status: PaymentCollectionStatus.AUTHORIZED,
|
||||
@@ -288,7 +288,6 @@ describe("PaymentCollectionService", () => {
|
||||
it("should update a payment collection with the right arguments", async () => {
|
||||
const submittedChanges = {
|
||||
description: "updated description",
|
||||
status: PaymentCollectionStatus.CAPTURED,
|
||||
metadata: {
|
||||
extra: 123,
|
||||
arr: ["a", "b", "c"],
|
||||
@@ -323,7 +322,6 @@ describe("PaymentCollectionService", () => {
|
||||
it("should throw error to update a non-existing payment collection", async () => {
|
||||
const submittedChanges = {
|
||||
description: "updated description",
|
||||
status: PaymentCollectionStatus.CAPTURED,
|
||||
metadata: {
|
||||
extra: 123,
|
||||
arr: ["a", "b", "c"],
|
||||
@@ -530,7 +528,6 @@ describe("PaymentCollectionService", () => {
|
||||
IdMap.getId("payCol_session1"),
|
||||
{
|
||||
customer_id: "customer1",
|
||||
amount: 100,
|
||||
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(
|
||||
IdMap.getId("payment-collection-session"),
|
||||
IdMap.getId("payCol_session1"),
|
||||
IdMap.getId("payCol_session-not-found"),
|
||||
{
|
||||
customer_id: "customer1",
|
||||
amount: 80,
|
||||
provider_id: IdMap.getId("region1_provider1"),
|
||||
}
|
||||
)
|
||||
|
||||
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(DefaultProviderMock.deletePayment).toBeCalledTimes(0)
|
||||
@@ -633,56 +631,4 @@ describe("PaymentCollectionService", () => {
|
||||
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 PaymentProviderService from "../payment-provider"
|
||||
import { testPayServiceMock } from "../__mocks__/test-pay"
|
||||
import { FlagRouter } from "../../utils/flag-router"
|
||||
|
||||
describe("PaymentProviderService", () => {
|
||||
describe("retrieveProvider", () => {
|
||||
@@ -106,6 +107,10 @@ describe("PaymentProviderService", () => {
|
||||
})
|
||||
|
||||
describe(`PaymentProviderService`, () => {
|
||||
const featureFlagRouter = new FlagRouter({
|
||||
order_editing: false,
|
||||
})
|
||||
|
||||
const container = {
|
||||
manager: MockManager,
|
||||
paymentSessionRepository: MockRepository({
|
||||
@@ -128,19 +133,22 @@ describe(`PaymentProviderService`, () => {
|
||||
},
|
||||
}),
|
||||
find: () =>
|
||||
Promise.resolve([{
|
||||
id: "pay_jadazdjk",
|
||||
provider_id: "default_provider",
|
||||
data: {
|
||||
id: "1234",
|
||||
Promise.resolve([
|
||||
{
|
||||
id: "pay_jadazdjk",
|
||||
provider_id: "default_provider",
|
||||
data: {
|
||||
id: "1234",
|
||||
},
|
||||
captured_at: new Date(),
|
||||
amount: 100,
|
||||
amount_refunded: 0,
|
||||
},
|
||||
captured_at: new Date(),
|
||||
amount: 100,
|
||||
amount_refunded: 0
|
||||
}]),
|
||||
]),
|
||||
}),
|
||||
refundRepository: MockRepository(),
|
||||
pp_default_provider: testPayServiceMock,
|
||||
featureFlagRouter,
|
||||
}
|
||||
const providerService = new PaymentProviderService(container)
|
||||
|
||||
@@ -206,29 +214,25 @@ describe(`PaymentProviderService`, () => {
|
||||
})
|
||||
|
||||
it("successfully delete session", async () => {
|
||||
await providerService.deleteSession(
|
||||
{
|
||||
id: "session",
|
||||
provider_id: "default_provider",
|
||||
data: {
|
||||
id: "1234",
|
||||
},
|
||||
}
|
||||
)
|
||||
await providerService.deleteSession({
|
||||
id: "session",
|
||||
provider_id: "default_provider",
|
||||
data: {
|
||||
id: "1234",
|
||||
},
|
||||
})
|
||||
|
||||
expect(testPayServiceMock.deletePayment).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it("successfully delete session", async () => {
|
||||
await providerService.deleteSession(
|
||||
{
|
||||
id: "session",
|
||||
provider_id: "default_provider",
|
||||
data: {
|
||||
id: "1234",
|
||||
},
|
||||
}
|
||||
)
|
||||
await providerService.deleteSession({
|
||||
id: "session",
|
||||
provider_id: "default_provider",
|
||||
data: {
|
||||
id: "1234",
|
||||
},
|
||||
})
|
||||
|
||||
expect(testPayServiceMock.deletePayment).toBeCalledTimes(1)
|
||||
})
|
||||
@@ -265,22 +269,27 @@ describe(`PaymentProviderService`, () => {
|
||||
|
||||
it("successfully cancel payment", async () => {
|
||||
await providerService.cancelPayment({
|
||||
id: "pay_jadazdjk"
|
||||
id: "pay_jadazdjk",
|
||||
})
|
||||
expect(testPayServiceMock.cancelPayment).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it("successfully capture payment", async () => {
|
||||
await providerService.capturePayment({
|
||||
id: "pay_jadazdjk"
|
||||
id: "pay_jadazdjk",
|
||||
})
|
||||
expect(testPayServiceMock.capturePayment).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it("successfully refund payment", async () => {
|
||||
await providerService.refundPayment([{
|
||||
id: "pay_jadazdjk"
|
||||
}], 50)
|
||||
await providerService.refundPayment(
|
||||
[
|
||||
{
|
||||
id: "pay_jadazdjk",
|
||||
},
|
||||
],
|
||||
50
|
||||
)
|
||||
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 PaymentCollectionService } from "./payment-collection"
|
||||
export { default as PaymentProviderService } from "./payment-provider"
|
||||
export { default as PaymentService } from "./payment"
|
||||
export { default as PriceListService } from "./price-list"
|
||||
export { default as PricingService } from "./pricing"
|
||||
export { default as ProductService } from "./product"
|
||||
|
||||
@@ -661,7 +661,7 @@ export default class OrderEditService extends TransactionBaseService {
|
||||
|
||||
let orderEdit = await this.retrieve(orderEditId, {
|
||||
relations: ["changes"],
|
||||
select: ["id", "requested_at"],
|
||||
select: ["id", "order_id", "requested_at"],
|
||||
})
|
||||
|
||||
if (!orderEdit.changes?.length) {
|
||||
|
||||
@@ -6,18 +6,17 @@ import { buildQuery, isDefined, setMetadata } from "../utils"
|
||||
import { PaymentCollectionRepository } from "../repositories/payment-collection"
|
||||
import {
|
||||
Customer,
|
||||
Payment,
|
||||
PaymentCollection,
|
||||
PaymentCollectionStatus,
|
||||
PaymentSession,
|
||||
PaymentSessionStatus,
|
||||
Refund,
|
||||
} from "../models"
|
||||
import { TransactionBaseService } from "../interfaces"
|
||||
import {
|
||||
CustomerService,
|
||||
EventBusService,
|
||||
PaymentProviderService,
|
||||
PaymentService,
|
||||
} from "./index"
|
||||
|
||||
import {
|
||||
@@ -40,10 +39,6 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
UPDATED: "payment-collection.updated",
|
||||
DELETED: "payment-collection.deleted",
|
||||
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
|
||||
@@ -298,7 +293,7 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
async refreshPaymentSession(
|
||||
paymentCollectionId: string,
|
||||
sessionId: string,
|
||||
sessionInput: PaymentCollectionSessionInput
|
||||
sessionInput: Omit<PaymentCollectionSessionInput, "amount">
|
||||
): Promise<PaymentSession> {
|
||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const paymentCollectionRepository = manager.getCustomRepository(
|
||||
@@ -328,10 +323,10 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
(sess) => sessionId === sess?.id
|
||||
)
|
||||
|
||||
if (session?.amount !== sessionInput.amount) {
|
||||
if (!session) {
|
||||
throw new MedusaError(
|
||||
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
|
||||
})
|
||||
|
||||
if (session.payment_authorized_at) {
|
||||
if (session.payment_authorized_at && payCol.authorized_amount) {
|
||||
payCol.authorized_amount -= session.amount
|
||||
}
|
||||
|
||||
@@ -401,7 +396,9 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
}
|
||||
|
||||
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) {
|
||||
authorizedAmount += session.amount
|
||||
continue
|
||||
@@ -411,14 +408,21 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
.withTransaction(manager)
|
||||
.authorizePayment(session, context)
|
||||
|
||||
if (auth) {
|
||||
payCol.payment_sessions[i] = auth
|
||||
}
|
||||
|
||||
if (auth?.status === PaymentSessionStatus.AUTHORIZED) {
|
||||
authorizedAmount += session.amount
|
||||
|
||||
const inputData: Omit<PaymentProviderDataInput, "customer"> = {
|
||||
const inputData: Omit<PaymentProviderDataInput, "customer"> & {
|
||||
payment_session: PaymentSession
|
||||
} = {
|
||||
amount: session.amount,
|
||||
currency_code: payCol.currency_code,
|
||||
provider_id: session.provider_id,
|
||||
resource_id: payCol.id,
|
||||
payment_session: auth,
|
||||
}
|
||||
|
||||
payCol.payments.push(
|
||||
@@ -447,235 +451,4 @@ export default class PaymentCollectionService extends TransactionBaseService {
|
||||
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,
|
||||
} from "../models"
|
||||
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 InjectedDependencies = {
|
||||
@@ -25,6 +28,8 @@ type InjectedDependencies = {
|
||||
paymentProviderRepository: typeof PaymentProviderRepository
|
||||
paymentRepository: typeof PaymentRepository
|
||||
refundRepository: typeof RefundRepository
|
||||
paymentService: PaymentService
|
||||
featureFlagRouter: FlagRouter
|
||||
} & {
|
||||
[key in `${PaymentProviderKey}`]:
|
||||
| AbstractPaymentService
|
||||
@@ -44,6 +49,8 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
protected readonly paymentRepository_: typeof PaymentRepository
|
||||
protected readonly refundRepository_: typeof RefundRepository
|
||||
|
||||
protected readonly featureFlagRouter_: FlagRouter
|
||||
|
||||
constructor(container: InjectedDependencies) {
|
||||
super(container)
|
||||
|
||||
@@ -53,6 +60,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
this.paymentProviderRepository_ = container.paymentProviderRepository
|
||||
this.paymentRepository_ = container.paymentRepository
|
||||
this.refundRepository_ = container.refundRepository
|
||||
this.featureFlagRouter_ = container.featureFlagRouter
|
||||
}
|
||||
|
||||
async registerInstalledProviders(providerIds: string[]): Promise<void> {
|
||||
@@ -184,9 +192,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
sessionInput: PaymentProviderDataInput
|
||||
): Promise<PaymentSession> {
|
||||
return await this.atomicPhase_(async (transactionManager) => {
|
||||
const provider: AbstractPaymentService = this.retrieveProvider(
|
||||
sessionInput.provider_id
|
||||
)
|
||||
const provider = this.retrieveProvider(sessionInput.provider_id)
|
||||
const sessionData = await provider
|
||||
.withTransaction(transactionManager)
|
||||
.createPaymentNew(sessionInput)
|
||||
@@ -396,7 +402,9 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
}
|
||||
|
||||
async createPaymentNew(
|
||||
paymentInput: Omit<PaymentProviderDataInput, "customer">
|
||||
paymentInput: Omit<PaymentProviderDataInput, "customer"> & {
|
||||
payment_session: PaymentSession
|
||||
}
|
||||
): Promise<Payment> {
|
||||
return await this.atomicPhase_(async (transactionManager) => {
|
||||
const { payment_session, currency_code, amount, provider_id } =
|
||||
@@ -407,18 +415,13 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
.withTransaction(transactionManager)
|
||||
.getPaymentData(payment_session)
|
||||
|
||||
const paymentRepo = transactionManager.getCustomRepository(
|
||||
this.paymentRepository_
|
||||
)
|
||||
|
||||
const created = paymentRepo.create({
|
||||
const paymentService = this.container_.paymentService
|
||||
return await paymentService.withTransaction(transactionManager).create({
|
||||
provider_id,
|
||||
amount,
|
||||
currency_code,
|
||||
data: paymentData,
|
||||
})
|
||||
|
||||
return await paymentRepo.save(created)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -427,20 +430,10 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
data: { order_id?: string; swap_id?: string }
|
||||
): Promise<Payment> {
|
||||
return await this.atomicPhase_(async (transactionManager) => {
|
||||
const payment = await this.retrievePayment(paymentId)
|
||||
|
||||
if (data?.order_id) {
|
||||
payment.order_id = data.order_id
|
||||
}
|
||||
|
||||
if (data?.swap_id) {
|
||||
payment.swap_id = data.swap_id
|
||||
}
|
||||
|
||||
const payRepo = transactionManager.getCustomRepository(
|
||||
this.paymentRepository_
|
||||
)
|
||||
return await payRepo.save(payment)
|
||||
const paymentService = this.container_.paymentService
|
||||
return await paymentService
|
||||
.withTransaction(transactionManager)
|
||||
.update(paymentId, data)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -465,6 +458,13 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
session.data = data
|
||||
session.status = status
|
||||
|
||||
if (
|
||||
this.featureFlagRouter_.isFeatureEnabled(OrderEditingFeatureFlag.key) &&
|
||||
status === PaymentSessionStatus.AUTHORIZED
|
||||
) {
|
||||
session.payment_authorized_at = new Date()
|
||||
}
|
||||
|
||||
const sessionRepo = transactionManager.getCustomRepository(
|
||||
this.paymentSessionRepository_
|
||||
)
|
||||
@@ -562,7 +562,7 @@ export default class PaymentProviderService extends TransactionBaseService {
|
||||
if (refundable < amount) {
|
||||
throw new MedusaError(
|
||||
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) {
|
||||
throw new MedusaError(
|
||||
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.adjustments",
|
||||
"items.tax_lines",
|
||||
"payment_collection",
|
||||
]
|
||||
|
||||
export const defaultOrderEditFields: (keyof OrderEdit)[] = [
|
||||
@@ -45,6 +46,7 @@ export const defaultOrderEditFields: (keyof OrderEdit)[] = [
|
||||
"canceled_by",
|
||||
"canceled_at",
|
||||
"internal_note",
|
||||
"payment_collection_id",
|
||||
]
|
||||
|
||||
export const storeOrderEditNotAllowedFieldsAndRelations = [
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
Customer,
|
||||
PaymentCollection,
|
||||
PaymentCollectionType,
|
||||
PaymentSession,
|
||||
} from "../models"
|
||||
|
||||
export type CreatePaymentCollectionInput = {
|
||||
@@ -29,8 +28,6 @@ export type PaymentProviderDataInput = {
|
||||
currency_code: string
|
||||
provider_id: string
|
||||
amount: number
|
||||
payment_session?: PaymentSession
|
||||
payment_description?: string
|
||||
cart_id?: string
|
||||
cart?: Cart
|
||||
metadata?: any
|
||||
@@ -48,7 +45,6 @@ export const defaultPaymentCollectionFields: (keyof PaymentCollection)[] = [
|
||||
"description",
|
||||
"amount",
|
||||
"authorized_amount",
|
||||
"refunded_amount",
|
||||
"currency_code",
|
||||
"metadata",
|
||||
"region",
|
||||
|
||||
Reference in new issue
Block a user