fix(payment): Partial refunds (#8603)
* fix(payment): Partial payment provider refunds * add tests
This commit is contained in:
1 parent
c92aa3e397
commit
9de9b3825f
3 files changed
+168
-26
No files matched your search
@@ -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 () => {
|
it("should not update payment collection of other orders", async () => {
|
||||||
await setupTaxStructure(container.resolve(ModuleRegistrationName.TAX))
|
await setupTaxStructure(container.resolve(ModuleRegistrationName.TAX))
|
||||||
await seedStorefrontDefaults(container, "dkk")
|
await seedStorefrontDefaults(container, "dkk")
|
||||||
|
|||||||
+46
@@ -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 () => {
|
it("should throw if refund is greater than captured amount", async () => {
|
||||||
await service.capturePayment({
|
await service.capturePayment({
|
||||||
amount: 50,
|
amount: 50,
|
||||||
|
|||||||
@@ -717,10 +717,25 @@ export default class PaymentModuleService
|
|||||||
data: CreateRefundDTO,
|
data: CreateRefundDTO,
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<PaymentDTO> {
|
): 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 {
|
try {
|
||||||
await this.refundPaymentFromProvider_(payment, sharedContext)
|
await this.refundPaymentFromProvider_(payment, refund, sharedContext)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await super.deleteRefunds(data.payment_id, sharedContext)
|
await super.deleteRefunds(data.payment_id, sharedContext)
|
||||||
throw error
|
throw error
|
||||||
@@ -740,25 +755,10 @@ export default class PaymentModuleService
|
|||||||
|
|
||||||
@InjectTransactionManager("baseRepository_")
|
@InjectTransactionManager("baseRepository_")
|
||||||
private async refundPayment_(
|
private async refundPayment_(
|
||||||
|
payment: Payment,
|
||||||
data: CreateRefundDTO,
|
data: CreateRefundDTO,
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<Payment> {
|
): Promise<Refund> {
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!data.amount) {
|
if (!data.amount) {
|
||||||
data.amount = payment.amount as BigNumberInput
|
data.amount = payment.amount as BigNumberInput
|
||||||
}
|
}
|
||||||
@@ -771,10 +771,7 @@ export default class PaymentModuleService
|
|||||||
return MathBN.add(refundedAmount, next.raw_amount)
|
return MathBN.add(refundedAmount, next.raw_amount)
|
||||||
}, MathBN.convert(0))
|
}, MathBN.convert(0))
|
||||||
|
|
||||||
const totalRefundedAmount = MathBN.add(
|
const totalRefundedAmount = MathBN.add(refundedAmount, data.amount)
|
||||||
refundedAmount,
|
|
||||||
data.amount
|
|
||||||
)
|
|
||||||
|
|
||||||
if (MathBN.lt(capturedAmount, totalRefundedAmount)) {
|
if (MathBN.lt(capturedAmount, totalRefundedAmount)) {
|
||||||
throw new MedusaError(
|
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,
|
payment: data.payment_id,
|
||||||
amount: data.amount,
|
amount: data.amount,
|
||||||
@@ -794,12 +791,13 @@ export default class PaymentModuleService
|
|||||||
sharedContext
|
sharedContext
|
||||||
)
|
)
|
||||||
|
|
||||||
return payment
|
return refund
|
||||||
}
|
}
|
||||||
|
|
||||||
@InjectManager("baseRepository_")
|
@InjectManager("baseRepository_")
|
||||||
private async refundPaymentFromProvider_(
|
private async refundPaymentFromProvider_(
|
||||||
payment: Payment,
|
payment: Payment,
|
||||||
|
refund: Refund,
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
) {
|
) {
|
||||||
const paymentData = await this.paymentProviderService_.refundPayment(
|
const paymentData = await this.paymentProviderService_.refundPayment(
|
||||||
@@ -807,7 +805,7 @@ export default class PaymentModuleService
|
|||||||
data: payment.data!,
|
data: payment.data!,
|
||||||
provider_id: payment.provider_id,
|
provider_id: payment.provider_id,
|
||||||
},
|
},
|
||||||
payment.raw_amount
|
refund.raw_amount
|
||||||
)
|
)
|
||||||
|
|
||||||
await this.paymentService_.update(
|
await this.paymentService_.update(
|
||||||
|
|||||||
Reference in new issue
Block a user