fix(payment): Partial refunds (#8603)
* fix(payment): Partial payment provider refunds * add tests
This commit is contained in:
@@ -170,6 +170,104 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
})
|
||||
|
||||
it("should issue multiple refunds", async () => {
|
||||
await api.post(
|
||||
`/admin/payments/${payment.id}/capture`,
|
||||
undefined,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const refundReason = (
|
||||
await api.post(`/admin/refund-reasons`, { label: "test" }, adminHeaders)
|
||||
).data.refund_reason
|
||||
|
||||
await api.post(
|
||||
`/admin/payments/${payment.id}/refund`,
|
||||
{
|
||||
amount: 250,
|
||||
refund_reason_id: refundReason.id,
|
||||
note: "Do not like it",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
await api.post(
|
||||
`/admin/payments/${payment.id}/refund`,
|
||||
{
|
||||
amount: 250,
|
||||
refund_reason_id: refundReason.id,
|
||||
note: "Do not like it",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const refundedPayment = (
|
||||
await api.get(`/admin/payments/${payment.id}`, adminHeaders)
|
||||
).data.payment
|
||||
|
||||
expect(refundedPayment).toEqual(
|
||||
expect.objectContaining({
|
||||
id: payment.id,
|
||||
currency_code: "usd",
|
||||
amount: 1000,
|
||||
captured_at: expect.any(String),
|
||||
captures: [
|
||||
expect.objectContaining({
|
||||
amount: 1000,
|
||||
}),
|
||||
],
|
||||
refunds: [
|
||||
expect.objectContaining({
|
||||
amount: 250,
|
||||
note: "Do not like it",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 250,
|
||||
note: "Do not like it",
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw if refund exceeds captured total", async () => {
|
||||
await api.post(
|
||||
`/admin/payments/${payment.id}/capture`,
|
||||
undefined,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const refundReason = (
|
||||
await api.post(`/admin/refund-reasons`, { label: "test" }, adminHeaders)
|
||||
).data.refund_reason
|
||||
|
||||
await api.post(
|
||||
`/admin/payments/${payment.id}/refund`,
|
||||
{
|
||||
amount: 250,
|
||||
refund_reason_id: refundReason.id,
|
||||
note: "Do not like it",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const e = await api
|
||||
.post(
|
||||
`/admin/payments/${payment.id}/refund`,
|
||||
{
|
||||
amount: 1000,
|
||||
refund_reason_id: refundReason.id,
|
||||
note: "Do not like it",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(e.response.data.message).toEqual(
|
||||
"You cannot refund more than what is captured on the payment."
|
||||
)
|
||||
})
|
||||
|
||||
it("should not update payment collection of other orders", async () => {
|
||||
await setupTaxStructure(container.resolve(ModuleRegistrationName.TAX))
|
||||
await seedStorefrontDefaults(container, "dkk")
|
||||
|
||||
@@ -685,6 +685,52 @@ moduleIntegrationTestRunner<IPaymentModuleService>({
|
||||
)
|
||||
})
|
||||
|
||||
it("should fully refund a payment through two refunds", async () => {
|
||||
await service.capturePayment({
|
||||
amount: 100,
|
||||
payment_id: "pay-id-2",
|
||||
})
|
||||
|
||||
const refundedPaymentOne = await service.refundPayment({
|
||||
amount: 50,
|
||||
payment_id: "pay-id-2",
|
||||
})
|
||||
|
||||
const refundedPaymentTwo = await service.refundPayment({
|
||||
amount: 50,
|
||||
payment_id: "pay-id-2",
|
||||
})
|
||||
|
||||
expect(refundedPaymentOne).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "pay-id-2",
|
||||
amount: 100,
|
||||
refunds: [
|
||||
expect.objectContaining({
|
||||
created_by: null,
|
||||
amount: 50,
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
expect(refundedPaymentTwo).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "pay-id-2",
|
||||
amount: 100,
|
||||
refunds: [
|
||||
expect.objectContaining({
|
||||
created_by: null,
|
||||
amount: 50,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
created_by: null,
|
||||
amount: 50,
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw if refund is greater than captured amount", async () => {
|
||||
await service.capturePayment({
|
||||
amount: 50,
|
||||
|
||||
@@ -717,10 +717,25 @@ export default class PaymentModuleService
|
||||
data: CreateRefundDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<PaymentDTO> {
|
||||
const payment = await this.refundPayment_(data, sharedContext)
|
||||
const payment = await this.paymentService_.retrieve(
|
||||
data.payment_id,
|
||||
{
|
||||
select: [
|
||||
"id",
|
||||
"data",
|
||||
"provider_id",
|
||||
"payment_collection_id",
|
||||
"amount",
|
||||
"raw_amount",
|
||||
],
|
||||
relations: ["captures.raw_amount", "refunds.raw_amount"],
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
const refund = await this.refundPayment_(payment, data, sharedContext)
|
||||
|
||||
try {
|
||||
await this.refundPaymentFromProvider_(payment, sharedContext)
|
||||
await this.refundPaymentFromProvider_(payment, refund, sharedContext)
|
||||
} catch (error) {
|
||||
await super.deleteRefunds(data.payment_id, sharedContext)
|
||||
throw error
|
||||
@@ -740,25 +755,10 @@ export default class PaymentModuleService
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
private async refundPayment_(
|
||||
payment: Payment,
|
||||
data: CreateRefundDTO,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<Payment> {
|
||||
const payment = await this.paymentService_.retrieve(
|
||||
data.payment_id,
|
||||
{
|
||||
select: [
|
||||
"id",
|
||||
"data",
|
||||
"provider_id",
|
||||
"payment_collection_id",
|
||||
"amount",
|
||||
"raw_amount",
|
||||
],
|
||||
relations: ["captures.raw_amount", "refunds.raw_amount"],
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
): Promise<Refund> {
|
||||
if (!data.amount) {
|
||||
data.amount = payment.amount as BigNumberInput
|
||||
}
|
||||
@@ -771,10 +771,7 @@ export default class PaymentModuleService
|
||||
return MathBN.add(refundedAmount, next.raw_amount)
|
||||
}, MathBN.convert(0))
|
||||
|
||||
const totalRefundedAmount = MathBN.add(
|
||||
refundedAmount,
|
||||
data.amount
|
||||
)
|
||||
const totalRefundedAmount = MathBN.add(refundedAmount, data.amount)
|
||||
|
||||
if (MathBN.lt(capturedAmount, totalRefundedAmount)) {
|
||||
throw new MedusaError(
|
||||
@@ -783,7 +780,7 @@ export default class PaymentModuleService
|
||||
)
|
||||
}
|
||||
|
||||
await this.refundService_.create(
|
||||
const refund = await this.refundService_.create(
|
||||
{
|
||||
payment: data.payment_id,
|
||||
amount: data.amount,
|
||||
@@ -794,12 +791,13 @@ export default class PaymentModuleService
|
||||
sharedContext
|
||||
)
|
||||
|
||||
return payment
|
||||
return refund
|
||||
}
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
private async refundPaymentFromProvider_(
|
||||
payment: Payment,
|
||||
refund: Refund,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
const paymentData = await this.paymentProviderService_.refundPayment(
|
||||
@@ -807,7 +805,7 @@ export default class PaymentModuleService
|
||||
data: payment.data!,
|
||||
provider_id: payment.provider_id,
|
||||
},
|
||||
payment.raw_amount
|
||||
refund.raw_amount
|
||||
)
|
||||
|
||||
await this.paymentService_.update(
|
||||
|
||||
Reference in New Issue
Block a user