feat(core-flows,payment,medusa,types): Refund reasons management API (#8436)
* feat(core-flows,payment,medusa,types): add ability to set and manage refund reasons * fix(payment): validate total amount when refunding payment (#8437) Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com> * feature: introduce additional_data to the product endpoints (#8405) * chore(docs): Generated References (#8440) Generated the following references: - `product` * chore: align payment database schema * Update packages/core/core-flows/src/payment-collection/steps/create-refund-reasons.ts Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> * chore: address review --------- Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com> Co-authored-by: Harminder Virk <virk.officials@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
co-authored by
Oli Juhl
Carlos R. L. Rodrigues
Harminder Virk
github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
parent
8fb079786d
commit
0ff5b975e7
@@ -127,14 +127,17 @@ medusaIntegrationTestRunner({
|
|||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
|
|
||||||
// refund
|
const refundReason = (
|
||||||
|
await api.post(`/admin/refund-reasons`, { label: "test" }, adminHeaders)
|
||||||
|
).data.refund_reason
|
||||||
|
|
||||||
|
// BREAKING: reason is now refund_reason_id
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`/admin/payments/${payment.id}/refund`,
|
`/admin/payments/${payment.id}/refund`,
|
||||||
{
|
{
|
||||||
amount: 500,
|
amount: 500,
|
||||||
// BREAKING: We should probably introduce reason and notes in V2 too
|
refund_reason_id: refundReason.id,
|
||||||
// reason: "return",
|
note: "Do not like it",
|
||||||
// note: "Do not like it",
|
|
||||||
},
|
},
|
||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
@@ -155,6 +158,11 @@ medusaIntegrationTestRunner({
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
amount: 500,
|
amount: 500,
|
||||||
|
note: "Do not like it",
|
||||||
|
refund_reason_id: refundReason.id,
|
||||||
|
refund_reason: expect.objectContaining({
|
||||||
|
label: "test",
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
amount: 1000,
|
amount: 1000,
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||||
|
import {
|
||||||
|
adminHeaders,
|
||||||
|
createAdminUser,
|
||||||
|
} from "../../../helpers/create-admin-user"
|
||||||
|
|
||||||
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
|
medusaIntegrationTestRunner({
|
||||||
|
testSuite: ({ dbConnection, api, getContainer }) => {
|
||||||
|
let refundReason1
|
||||||
|
let refundReason2
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const appContainer = getContainer()
|
||||||
|
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
||||||
|
|
||||||
|
refundReason1 = (
|
||||||
|
await api.post(
|
||||||
|
"/admin/refund-reasons",
|
||||||
|
{ label: "reason 1 - too big" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.refund_reason
|
||||||
|
|
||||||
|
refundReason2 = (
|
||||||
|
await api.post(
|
||||||
|
"/admin/refund-reasons",
|
||||||
|
{ label: "reason 2 - too small" },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.refund_reason
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("GET /admin/refund-reasons", () => {
|
||||||
|
it("should list refund reasons and query count", async () => {
|
||||||
|
const response = await api
|
||||||
|
.get("/admin/refund-reasons", adminHeaders)
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.count).toEqual(2)
|
||||||
|
expect(response.data.refund_reasons).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
label: "reason 1 - too big",
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
label: "reason 2 - too small",
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should list refund-reasons with specific query", async () => {
|
||||||
|
const response = await api.get(
|
||||||
|
"/admin/refund-reasons?q=1",
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.count).toEqual(1)
|
||||||
|
expect(response.data.refund_reasons).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
label: "reason 1 - too big",
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("POST /admin/refund-reasons", () => {
|
||||||
|
it("should create a refund reason", async () => {
|
||||||
|
const response = await api.post(
|
||||||
|
"/admin/refund-reasons",
|
||||||
|
{
|
||||||
|
label: "reason test",
|
||||||
|
description: "test description",
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.refund_reason).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
label: "reason test",
|
||||||
|
description: "test description",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("POST /admin/refund-reasons/:id", () => {
|
||||||
|
it("should correctly update refund reason", async () => {
|
||||||
|
const response = await api.post(
|
||||||
|
`/admin/refund-reasons/${refundReason1.id}`,
|
||||||
|
{
|
||||||
|
label: "reason test",
|
||||||
|
description: "test description",
|
||||||
|
},
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.refund_reason).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
label: "reason test",
|
||||||
|
description: "test description",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("GET /admin/refund-reasons/:id", () => {
|
||||||
|
it("should fetch a refund reason", async () => {
|
||||||
|
const response = await api.get(
|
||||||
|
`/admin/refund-reasons/${refundReason1.id}`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.refund_reason).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: refundReason1.id,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("DELETE /admin/refund-reasons/:id", () => {
|
||||||
|
it("should remove refund reasons", async () => {
|
||||||
|
const deleteResponse = await api.delete(
|
||||||
|
`/admin/refund-reasons/${refundReason1.id}`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(deleteResponse.data).toEqual({
|
||||||
|
id: refundReason1.id,
|
||||||
|
object: "refund_reason",
|
||||||
|
deleted: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
await api
|
||||||
|
.get(`/admin/refund-reasons/${refundReason1.id}`, adminHeaders)
|
||||||
|
.catch((error) => {
|
||||||
|
expect(error.response.data.type).toEqual("not_found")
|
||||||
|
expect(error.response.data.message).toEqual(
|
||||||
|
`Refund reason with id: ${refundReason1.id.id} not found`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { CreateRefundReasonDTO, IPaymentModuleService } from "@medusajs/types"
|
||||||
|
import { ModuleRegistrationName } from "@medusajs/utils"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
export const createRefundReasonStepId = "create-refund-reason"
|
||||||
|
export const createRefundReasonStep = createStep(
|
||||||
|
createRefundReasonStepId,
|
||||||
|
async (data: CreateRefundReasonDTO[], { container }) => {
|
||||||
|
const service = container.resolve<IPaymentModuleService>(
|
||||||
|
ModuleRegistrationName.PAYMENT
|
||||||
|
)
|
||||||
|
|
||||||
|
const refundReasons = await service.createRefundReasons(data)
|
||||||
|
|
||||||
|
return new StepResponse(
|
||||||
|
refundReasons,
|
||||||
|
refundReasons.map((rr) => rr.id)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
async (ids, { container }) => {
|
||||||
|
if (!ids?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const service = container.resolve<IPaymentModuleService>(
|
||||||
|
ModuleRegistrationName.PAYMENT
|
||||||
|
)
|
||||||
|
|
||||||
|
await service.deleteRefundReasons(ids)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { IPaymentModuleService } from "@medusajs/types"
|
||||||
|
import { ModuleRegistrationName } from "@medusajs/utils"
|
||||||
|
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
export const deleteRefundReasonsStepId = "delete-refund-reasons"
|
||||||
|
export const deleteRefundReasonsStep = createStep(
|
||||||
|
deleteRefundReasonsStepId,
|
||||||
|
async (ids: string[], { container }) => {
|
||||||
|
const service = container.resolve<IPaymentModuleService>(
|
||||||
|
ModuleRegistrationName.PAYMENT
|
||||||
|
)
|
||||||
|
|
||||||
|
await service.softDeleteRefundReasons(ids)
|
||||||
|
|
||||||
|
return new StepResponse(void 0, ids)
|
||||||
|
},
|
||||||
|
async (prevCustomerIds, { container }) => {
|
||||||
|
if (!prevCustomerIds?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const service = container.resolve<IPaymentModuleService>(
|
||||||
|
ModuleRegistrationName.PAYMENT
|
||||||
|
)
|
||||||
|
|
||||||
|
await service.restoreRefundReasons(prevCustomerIds)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
export * from "./create-payment-session"
|
export * from "./create-payment-session"
|
||||||
|
export * from "./create-refund-reasons"
|
||||||
export * from "./delete-payment-sessions"
|
export * from "./delete-payment-sessions"
|
||||||
|
export * from "./delete-refund-reasons"
|
||||||
export * from "./update-payment-collection"
|
export * from "./update-payment-collection"
|
||||||
|
export * from "./update-refund-reasons"
|
||||||
export * from "./validate-deleted-payment-sessions"
|
export * from "./validate-deleted-payment-sessions"
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { IPaymentModuleService, UpdateRefundReasonDTO } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
ModuleRegistrationName,
|
||||||
|
getSelectsAndRelationsFromObjectArray,
|
||||||
|
promiseAll,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
export const updateRefundReasonStepId = "update-refund-reasons"
|
||||||
|
export const updateRefundReasonsStep = createStep(
|
||||||
|
updateRefundReasonStepId,
|
||||||
|
async (data: UpdateRefundReasonDTO[], { container }) => {
|
||||||
|
const ids = data.map((d) => d.id)
|
||||||
|
const { selects, relations } = getSelectsAndRelationsFromObjectArray(data)
|
||||||
|
const service = container.resolve<IPaymentModuleService>(
|
||||||
|
ModuleRegistrationName.PAYMENT
|
||||||
|
)
|
||||||
|
|
||||||
|
const prevRefundReasons = await service.listRefundReasons(
|
||||||
|
{ id: ids },
|
||||||
|
{ select: selects, relations }
|
||||||
|
)
|
||||||
|
|
||||||
|
const reasons = await service.updateRefundReasons(data)
|
||||||
|
|
||||||
|
return new StepResponse(reasons, prevRefundReasons)
|
||||||
|
},
|
||||||
|
async (previousData, { container }) => {
|
||||||
|
if (!previousData) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const service = container.resolve<IPaymentModuleService>(
|
||||||
|
ModuleRegistrationName.PAYMENT
|
||||||
|
)
|
||||||
|
|
||||||
|
await promiseAll(
|
||||||
|
previousData.map((refundReason) =>
|
||||||
|
service.updateRefundReasons(refundReason)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { CreateRefundReasonDTO, RefundReasonDTO } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
WorkflowData,
|
||||||
|
WorkflowResponse,
|
||||||
|
createWorkflow,
|
||||||
|
} from "@medusajs/workflows-sdk"
|
||||||
|
import { createRefundReasonStep } from "../steps/create-refund-reasons"
|
||||||
|
|
||||||
|
export const createRefundReasonsWorkflowId = "create-refund-reasons-workflow"
|
||||||
|
export const createRefundReasonsWorkflow = createWorkflow(
|
||||||
|
createRefundReasonsWorkflowId,
|
||||||
|
(
|
||||||
|
input: WorkflowData<{ data: CreateRefundReasonDTO[] }>
|
||||||
|
): WorkflowResponse<RefundReasonDTO[]> => {
|
||||||
|
return new WorkflowResponse(createRefundReasonStep(input.data))
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import {
|
||||||
|
WorkflowData,
|
||||||
|
WorkflowResponse,
|
||||||
|
createWorkflow,
|
||||||
|
} from "@medusajs/workflows-sdk"
|
||||||
|
import { deleteRefundReasonsStep } from "../steps"
|
||||||
|
|
||||||
|
export const deleteRefundReasonsWorkflowId = "delete-refund-reasons-workflow"
|
||||||
|
export const deleteRefundReasonsWorkflow = createWorkflow(
|
||||||
|
deleteRefundReasonsWorkflowId,
|
||||||
|
(input: WorkflowData<{ ids: string[] }>): WorkflowResponse<void> => {
|
||||||
|
return new WorkflowResponse(deleteRefundReasonsStep(input.ids))
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1 +1,3 @@
|
|||||||
export * from "./create-payment-session"
|
export * from "./create-payment-session"
|
||||||
|
export * from "./create-refund-reasons"
|
||||||
|
export * from "./update-refund-reasons"
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { RefundReasonDTO, UpdateRefundReasonDTO } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
WorkflowData,
|
||||||
|
WorkflowResponse,
|
||||||
|
createWorkflow,
|
||||||
|
} from "@medusajs/workflows-sdk"
|
||||||
|
import { updateRefundReasonsStep } from "../steps"
|
||||||
|
|
||||||
|
export const updateRefundReasonsWorkflowId = "update-refund-reasons"
|
||||||
|
export const updateRefundReasonsWorkflow = createWorkflow(
|
||||||
|
updateRefundReasonsWorkflowId,
|
||||||
|
(
|
||||||
|
input: WorkflowData<UpdateRefundReasonDTO[]>
|
||||||
|
): WorkflowResponse<RefundReasonDTO[]> => {
|
||||||
|
return new WorkflowResponse(updateRefundReasonsStep(input))
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { BaseFilterable } from "../../dal"
|
||||||
import {
|
import {
|
||||||
BasePayment,
|
BasePayment,
|
||||||
BasePaymentCollection,
|
BasePaymentCollection,
|
||||||
@@ -7,6 +8,7 @@ import {
|
|||||||
BasePaymentProviderFilters,
|
BasePaymentProviderFilters,
|
||||||
BasePaymentSession,
|
BasePaymentSession,
|
||||||
BasePaymentSessionFilters,
|
BasePaymentSessionFilters,
|
||||||
|
RefundReason,
|
||||||
} from "./common"
|
} from "./common"
|
||||||
|
|
||||||
export interface AdminPaymentProvider extends BasePaymentProvider {
|
export interface AdminPaymentProvider extends BasePaymentProvider {
|
||||||
@@ -42,3 +44,23 @@ export interface AdminPaymentsResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminPaymentFilters extends BasePaymentFilters {}
|
export interface AdminPaymentFilters extends BasePaymentFilters {}
|
||||||
|
|
||||||
|
// Refund reason
|
||||||
|
|
||||||
|
export interface AdminRefundReason extends RefundReason {}
|
||||||
|
export interface RefundReasonFilters extends BaseFilterable<AdminRefundReason> {
|
||||||
|
id?: string | string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RefundReasonResponse {
|
||||||
|
refund_reason: AdminRefundReason
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RefundReasonsResponse {
|
||||||
|
refund_reasons: AdminRefundReason[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminCreateRefundReason {
|
||||||
|
label: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|||||||
@@ -262,6 +262,21 @@ export interface BaseRefund {
|
|||||||
*/
|
*/
|
||||||
amount: BigNumberValue
|
amount: BigNumberValue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the refund_reason that is associated with the refund
|
||||||
|
*/
|
||||||
|
refund_reason_id?: string | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the refund_reason that is associated with the refund
|
||||||
|
*/
|
||||||
|
refund_reason?: RefundReason | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A field to add some additional information about the refund
|
||||||
|
*/
|
||||||
|
note?: string | null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The creation date of the refund.
|
* The creation date of the refund.
|
||||||
*/
|
*/
|
||||||
@@ -338,6 +353,33 @@ export interface BasePaymentSession {
|
|||||||
payment?: BasePayment
|
payment?: BasePayment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RefundReason {
|
||||||
|
/**
|
||||||
|
* The ID of the refund reason
|
||||||
|
*/
|
||||||
|
id: string
|
||||||
|
/**
|
||||||
|
* The label of the refund reason
|
||||||
|
*/
|
||||||
|
label: string
|
||||||
|
/**
|
||||||
|
* The description of the refund reason
|
||||||
|
*/
|
||||||
|
description?: string | null
|
||||||
|
/**
|
||||||
|
* The metadata of the refund reason
|
||||||
|
*/
|
||||||
|
metadata: Record<string, unknown> | null
|
||||||
|
/**
|
||||||
|
* When the refund reason was created
|
||||||
|
*/
|
||||||
|
created_at: Date | string
|
||||||
|
/**
|
||||||
|
* When the refund reason was updated
|
||||||
|
*/
|
||||||
|
updated_at: Date | string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The filters to apply on the retrieved payment collection.
|
* The filters to apply on the retrieved payment collection.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -477,6 +477,21 @@ export interface RefundDTO {
|
|||||||
*/
|
*/
|
||||||
amount: BigNumberValue
|
amount: BigNumberValue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the refund_reason that is associated with the refund
|
||||||
|
*/
|
||||||
|
refund_reason_id?: string | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the refund_reason that is associated with the refund
|
||||||
|
*/
|
||||||
|
refund_reason?: RefundReasonDTO | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A field to add some additional information about the refund
|
||||||
|
*/
|
||||||
|
note?: string | null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The creation date of the refund.
|
* The creation date of the refund.
|
||||||
*/
|
*/
|
||||||
@@ -583,3 +598,38 @@ export interface FilterablePaymentProviderProps
|
|||||||
*/
|
*/
|
||||||
is_enabled?: boolean
|
is_enabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FilterableRefundReasonProps
|
||||||
|
extends BaseFilterable<FilterableRefundReasonProps> {
|
||||||
|
/**
|
||||||
|
* The IDs to filter the refund reasons by.
|
||||||
|
*/
|
||||||
|
id?: string | string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RefundReasonDTO {
|
||||||
|
/**
|
||||||
|
* The ID of the refund reason
|
||||||
|
*/
|
||||||
|
id: string
|
||||||
|
/**
|
||||||
|
* The label of the refund reason
|
||||||
|
*/
|
||||||
|
label: string
|
||||||
|
/**
|
||||||
|
* The description of the refund reason
|
||||||
|
*/
|
||||||
|
description?: string | null
|
||||||
|
/**
|
||||||
|
* The metadata of the refund reason
|
||||||
|
*/
|
||||||
|
metadata: Record<string, unknown> | null
|
||||||
|
/**
|
||||||
|
* When the refund reason was created
|
||||||
|
*/
|
||||||
|
created_at: Date | string
|
||||||
|
/**
|
||||||
|
* When the refund reason was updated
|
||||||
|
*/
|
||||||
|
updated_at: Date | string
|
||||||
|
}
|
||||||
|
|||||||
@@ -212,6 +212,16 @@ export interface CreateRefundDTO {
|
|||||||
*/
|
*/
|
||||||
payment_id: string
|
payment_id: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The associated refund reason's ID.
|
||||||
|
*/
|
||||||
|
refund_reason_id?: string | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A text field that adds some information about the refund
|
||||||
|
*/
|
||||||
|
note?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Who refunded the payment. For example,
|
* Who refunded the payment. For example,
|
||||||
* a user's ID.
|
* a user's ID.
|
||||||
@@ -323,3 +333,37 @@ export interface ProviderWebhookPayload {
|
|||||||
headers: Record<string, unknown>
|
headers: Record<string, unknown>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CreateRefundReasonDTO {
|
||||||
|
/**
|
||||||
|
* The label of the refund reason
|
||||||
|
*/
|
||||||
|
label: string
|
||||||
|
/**
|
||||||
|
* The description of the refund reason
|
||||||
|
*/
|
||||||
|
description?: string | null
|
||||||
|
/**
|
||||||
|
* The metadata of the refund reason
|
||||||
|
*/
|
||||||
|
metadata?: Record<string, unknown> | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateRefundReasonDTO {
|
||||||
|
/**
|
||||||
|
* The id of the refund reason
|
||||||
|
*/
|
||||||
|
id: string
|
||||||
|
/**
|
||||||
|
* The label of the refund reason
|
||||||
|
*/
|
||||||
|
label?: string
|
||||||
|
/**
|
||||||
|
* The description of the refund reason
|
||||||
|
*/
|
||||||
|
description?: string | null
|
||||||
|
/**
|
||||||
|
* The metadata of the refund reason
|
||||||
|
*/
|
||||||
|
metadata?: Record<string, unknown> | null
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FindConfig } from "../common"
|
import { FindConfig } from "../common"
|
||||||
|
import { RestoreReturn, SoftDeleteReturn } from "../dal"
|
||||||
import { IModuleService } from "../modules-sdk"
|
import { IModuleService } from "../modules-sdk"
|
||||||
import { Context } from "../shared-context"
|
import { Context } from "../shared-context"
|
||||||
import {
|
import {
|
||||||
@@ -9,21 +10,25 @@ import {
|
|||||||
FilterablePaymentProviderProps,
|
FilterablePaymentProviderProps,
|
||||||
FilterablePaymentSessionProps,
|
FilterablePaymentSessionProps,
|
||||||
FilterableRefundProps,
|
FilterableRefundProps,
|
||||||
|
FilterableRefundReasonProps,
|
||||||
PaymentCollectionDTO,
|
PaymentCollectionDTO,
|
||||||
PaymentDTO,
|
PaymentDTO,
|
||||||
PaymentProviderDTO,
|
PaymentProviderDTO,
|
||||||
PaymentSessionDTO,
|
PaymentSessionDTO,
|
||||||
RefundDTO,
|
RefundDTO,
|
||||||
|
RefundReasonDTO,
|
||||||
} from "./common"
|
} from "./common"
|
||||||
import {
|
import {
|
||||||
CreateCaptureDTO,
|
CreateCaptureDTO,
|
||||||
CreatePaymentCollectionDTO,
|
CreatePaymentCollectionDTO,
|
||||||
CreatePaymentSessionDTO,
|
CreatePaymentSessionDTO,
|
||||||
CreateRefundDTO,
|
CreateRefundDTO,
|
||||||
|
CreateRefundReasonDTO,
|
||||||
PaymentCollectionUpdatableFields,
|
PaymentCollectionUpdatableFields,
|
||||||
ProviderWebhookPayload,
|
ProviderWebhookPayload,
|
||||||
UpdatePaymentDTO,
|
UpdatePaymentDTO,
|
||||||
UpdatePaymentSessionDTO,
|
UpdatePaymentSessionDTO,
|
||||||
|
UpdateRefundReasonDTO,
|
||||||
UpsertPaymentCollectionDTO,
|
UpsertPaymentCollectionDTO,
|
||||||
} from "./mutations"
|
} from "./mutations"
|
||||||
|
|
||||||
@@ -817,6 +822,199 @@ export interface IPaymentModuleService extends IModuleService {
|
|||||||
sharedContext?: Context
|
sharedContext?: Context
|
||||||
): Promise<RefundDTO[]>
|
): Promise<RefundDTO[]>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method creates refund reasons.
|
||||||
|
*
|
||||||
|
* @param {CreateRefundReasonDTO[]} data - The refund reasons to create.
|
||||||
|
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||||
|
* @returns {Promise<RefundReasonDTO[]>} The created refund reasons.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const refundReasons =
|
||||||
|
* await paymentModuleService.createRefundReasons([
|
||||||
|
* {
|
||||||
|
* label: "Too big",
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* label: "Too big",
|
||||||
|
* },
|
||||||
|
* ])
|
||||||
|
*/
|
||||||
|
createRefundReasons(
|
||||||
|
data: CreateRefundReasonDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<RefundReasonDTO[]>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method creates a refund reason.
|
||||||
|
*
|
||||||
|
* @param {CreateRefundReasonDTO} data - The refund reason to create.
|
||||||
|
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||||
|
* @returns {Promise<RefundReasonDTO>} The created refund reason.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const refundReason =
|
||||||
|
* await paymentModuleService.createRefundReasons({
|
||||||
|
* label: "Too big",
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
createRefundReasons(
|
||||||
|
data: CreateRefundReasonDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<RefundReasonDTO>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method deletes a refund reason by its ID.
|
||||||
|
*
|
||||||
|
* @param {string[]} refundReasonId - The refund reason's ID.
|
||||||
|
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||||
|
* @returns {Promise<void>} Resolves when the refund reason is deleted successfully.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await paymentModuleService.deleteRefundReasons([
|
||||||
|
* "refr_123",
|
||||||
|
* "refr_321",
|
||||||
|
* ])
|
||||||
|
*/
|
||||||
|
deleteRefundReasons(
|
||||||
|
refundReasonId: string[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method deletes a refund reason by its ID.
|
||||||
|
*
|
||||||
|
* @param {string} refundReasonId - The refund reason's ID.
|
||||||
|
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||||
|
* @returns {Promise<void>} Resolves when the refund reason is deleted successfully.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await paymentModuleService.deleteRefundReasons(
|
||||||
|
* "refr_123"
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
deleteRefundReasons(
|
||||||
|
refundReasonId: string,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method soft deletes refund reasons by their IDs.
|
||||||
|
*
|
||||||
|
* @param {string[]} refundReasonId - The IDs of refund reasons.
|
||||||
|
* @param {SoftDeleteReturn<TReturnableLinkableKeys>} config - An object that is used to specify an entity's related entities that should be soft-deleted when the main entity is soft-deleted.
|
||||||
|
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||||
|
* @returns {Promise<void | Record<TReturnableLinkableKeys, string[]>>} An object that includes the IDs of related records that were also soft deleted.
|
||||||
|
* If there are no related records, the promise resolves to `void`.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await paymentModule.softDeleteRefundReasons(["cus_123"])
|
||||||
|
*/
|
||||||
|
softDeleteRefundReasons<TReturnableLinkableKeys extends string = string>(
|
||||||
|
refundReasonId: string[],
|
||||||
|
config?: SoftDeleteReturn<TReturnableLinkableKeys>,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<Record<TReturnableLinkableKeys, string[]> | void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method restores soft deleted refund reason by their IDs.
|
||||||
|
*
|
||||||
|
* @param {string[]} refundReasonId - The IDs of refund reasons.
|
||||||
|
* @param {RestoreReturn<TReturnableLinkableKeys>} config - Configurations determining which relations to restore along with each of the refund reason. You can pass to its `returnLinkableKeys`
|
||||||
|
* property any of the refund reason's relation attribute names.
|
||||||
|
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||||
|
* @returns {Promise<void | Record<TReturnableLinkableKeys, string[]>>} An object that includes the IDs of related records that were restored.
|
||||||
|
* If there are no related records restored, the promise resolves to `void`.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await paymentModule.restoreRefundReasons(["cus_123"])
|
||||||
|
*/
|
||||||
|
restoreRefundReasons<TReturnableLinkableKeys extends string = string>(
|
||||||
|
refundReasonId: string[],
|
||||||
|
config?: RestoreReturn<TReturnableLinkableKeys>,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<Record<TReturnableLinkableKeys, string[]> | void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method updates an existing refund reason.
|
||||||
|
*
|
||||||
|
* @param {UpdateRefundReasonDTO} data - The attributes to update in the refund reason.
|
||||||
|
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||||
|
* @returns {Promise<RefundReasonDTO>} The updated refund reason.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const refundReason =
|
||||||
|
* await paymentModuleService.updateRefundReasons(
|
||||||
|
* [{
|
||||||
|
* id: "refr_test1",
|
||||||
|
* amount: 3000,
|
||||||
|
* }]
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
updateRefundReasons(
|
||||||
|
data: UpdateRefundReasonDTO[],
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<RefundReasonDTO[]>
|
||||||
|
|
||||||
|
updateRefundReasons(
|
||||||
|
data: UpdateRefundReasonDTO,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<RefundReasonDTO>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method retrieves a paginated list of refund reasons based on optional filters and configuration.
|
||||||
|
*
|
||||||
|
* @param {FilterableRefundReasonProps} filters - The filters to apply on the retrieved refund reason.
|
||||||
|
* @param {FindConfig<RefundReasonDTO>} config - The configurations determining how the refund reason is retrieved. Its properties, such as `select` or `relations`, accept the
|
||||||
|
* attributes or relations associated with a refund reason.
|
||||||
|
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||||
|
* @returns {Promise<RefundReasonDTO[]>} The list of refund reasons.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* To retrieve a list of refund reasons using their IDs:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* const refundReasons =
|
||||||
|
* await paymentModuleService.listRefundReasons({
|
||||||
|
* id: ["refr_123", "refr_321"],
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* To specify relations that should be retrieved within the refund :
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* const refundReasons =
|
||||||
|
* await paymentModuleService.listRefundReasons(
|
||||||
|
* {
|
||||||
|
* id: ["refr_123", "refr_321"],
|
||||||
|
* },
|
||||||
|
* {}
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* By default, only the first `15` records are retrieved. You can control pagination by specifying the `skip` and `take` properties of the `config` parameter:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* const refundReasons =
|
||||||
|
* await paymentModuleService.listRefundReasons(
|
||||||
|
* {
|
||||||
|
* id: ["refr_123", "refr_321"],
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* take: 20,
|
||||||
|
* skip: 2,
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
listRefundReasons(
|
||||||
|
filters?: FilterableRefundReasonProps,
|
||||||
|
config?: FindConfig<RefundReasonDTO>,
|
||||||
|
sharedContext?: Context
|
||||||
|
): Promise<RefundReasonDTO[]>
|
||||||
|
|
||||||
/* ********** HOOKS ********** */
|
/* ********** HOOKS ********** */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const POST = async (
|
|||||||
input: {
|
input: {
|
||||||
payment_id: id,
|
payment_id: id,
|
||||||
created_by: req.auth_context.actor_id,
|
created_by: req.auth_context.actor_id,
|
||||||
amount: req.validatedBody.amount,
|
...req.validatedBody,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ export const defaultAdminPaymentFields = [
|
|||||||
"captures.amount",
|
"captures.amount",
|
||||||
"refunds.id",
|
"refunds.id",
|
||||||
"refunds.amount",
|
"refunds.amount",
|
||||||
|
"refunds.note",
|
||||||
"refunds.payment_id",
|
"refunds.payment_id",
|
||||||
|
"refunds.refund_reason.label",
|
||||||
]
|
]
|
||||||
|
|
||||||
export const listTransformQueryConfig = {
|
export const listTransformQueryConfig = {
|
||||||
|
|||||||
@@ -55,5 +55,17 @@ export type AdminCreatePaymentRefundType = z.infer<
|
|||||||
export const AdminCreatePaymentRefund = z
|
export const AdminCreatePaymentRefund = z
|
||||||
.object({
|
.object({
|
||||||
amount: z.number().optional(),
|
amount: z.number().optional(),
|
||||||
|
refund_reason_id: z.string().optional(),
|
||||||
|
note: z.string().optional(),
|
||||||
|
})
|
||||||
|
.strict()
|
||||||
|
|
||||||
|
export type AdminCreatePaymentRefundReasonType = z.infer<
|
||||||
|
typeof AdminCreatePaymentRefundReason
|
||||||
|
>
|
||||||
|
export const AdminCreatePaymentRefundReason = z
|
||||||
|
.object({
|
||||||
|
label: z.string(),
|
||||||
|
description: z.string().optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import {
|
||||||
|
deleteReturnReasonsWorkflow,
|
||||||
|
updateRefundReasonsWorkflow,
|
||||||
|
} from "@medusajs/core-flows"
|
||||||
|
import { RefundReasonResponse } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
AuthenticatedMedusaRequest,
|
||||||
|
MedusaResponse,
|
||||||
|
} from "../../../../types/routing"
|
||||||
|
import { refetchEntity } from "../../../utils/refetch-entity"
|
||||||
|
import { AdminUpdatePaymentRefundReasonType } from "../validators"
|
||||||
|
|
||||||
|
export const GET = async (
|
||||||
|
req: AuthenticatedMedusaRequest,
|
||||||
|
res: MedusaResponse<RefundReasonResponse>
|
||||||
|
) => {
|
||||||
|
const refund_reason = await refetchEntity(
|
||||||
|
"refund_reason",
|
||||||
|
req.params.id,
|
||||||
|
req.scope,
|
||||||
|
req.remoteQueryConfig.fields
|
||||||
|
)
|
||||||
|
|
||||||
|
res.json({ refund_reason })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const POST = async (
|
||||||
|
req: AuthenticatedMedusaRequest<AdminUpdatePaymentRefundReasonType>,
|
||||||
|
res: MedusaResponse<RefundReasonResponse>
|
||||||
|
) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
await updateRefundReasonsWorkflow(req.scope).run({
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
...req.validatedBody,
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const refund_reason = await refetchEntity(
|
||||||
|
"refund_reason",
|
||||||
|
req.params.id,
|
||||||
|
req.scope,
|
||||||
|
req.remoteQueryConfig.fields
|
||||||
|
)
|
||||||
|
|
||||||
|
res.json({ refund_reason })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DELETE = async (
|
||||||
|
req: AuthenticatedMedusaRequest,
|
||||||
|
res: MedusaResponse
|
||||||
|
) => {
|
||||||
|
const { id } = req.params
|
||||||
|
const input = { ids: [id] }
|
||||||
|
|
||||||
|
await deleteReturnReasonsWorkflow(req.scope).run({ input })
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
id,
|
||||||
|
object: "refund_reason",
|
||||||
|
deleted: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { MiddlewareRoute } from "@medusajs/framework"
|
||||||
|
import { validateAndTransformBody } from "../../utils/validate-body"
|
||||||
|
import { validateAndTransformQuery } from "../../utils/validate-query"
|
||||||
|
import * as queryConfig from "./query-config"
|
||||||
|
import {
|
||||||
|
AdminCreatePaymentRefundReason,
|
||||||
|
AdminGetRefundReasonsParams,
|
||||||
|
AdminUpdatePaymentRefundReason,
|
||||||
|
} from "./validators"
|
||||||
|
|
||||||
|
export const adminRefundReasonsRoutesMiddlewares: MiddlewareRoute[] = [
|
||||||
|
{
|
||||||
|
method: ["GET"],
|
||||||
|
matcher: "/admin/refund-reasons",
|
||||||
|
middlewares: [
|
||||||
|
validateAndTransformQuery(
|
||||||
|
AdminGetRefundReasonsParams,
|
||||||
|
queryConfig.listTransformQueryConfig
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/admin/refund-reasons",
|
||||||
|
middlewares: [
|
||||||
|
validateAndTransformBody(AdminCreatePaymentRefundReason),
|
||||||
|
validateAndTransformQuery(
|
||||||
|
AdminGetRefundReasonsParams,
|
||||||
|
queryConfig.retrieveTransformQueryConfig
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/admin/refund-reasons/:id",
|
||||||
|
middlewares: [
|
||||||
|
validateAndTransformBody(AdminUpdatePaymentRefundReason),
|
||||||
|
validateAndTransformQuery(
|
||||||
|
AdminGetRefundReasonsParams,
|
||||||
|
queryConfig.retrieveTransformQueryConfig
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: ["GET"],
|
||||||
|
matcher: "/admin/refund-reasons/:id",
|
||||||
|
middlewares: [
|
||||||
|
validateAndTransformQuery(
|
||||||
|
AdminGetRefundReasonsParams,
|
||||||
|
queryConfig.retrieveTransformQueryConfig
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: ["DELETE"],
|
||||||
|
matcher: "/admin/refund-reasons/:id",
|
||||||
|
middlewares: [
|
||||||
|
validateAndTransformQuery(
|
||||||
|
AdminGetRefundReasonsParams,
|
||||||
|
queryConfig.retrieveTransformQueryConfig
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
export const defaultAdminRefundReasonFields = [
|
||||||
|
"id",
|
||||||
|
"label",
|
||||||
|
"description",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"deleted_at",
|
||||||
|
]
|
||||||
|
|
||||||
|
export const defaultAdminRetrieveRefundReasonFields = [
|
||||||
|
...defaultAdminRefundReasonFields,
|
||||||
|
]
|
||||||
|
|
||||||
|
export const retrieveTransformQueryConfig = {
|
||||||
|
defaultFields: defaultAdminRetrieveRefundReasonFields,
|
||||||
|
isList: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const listTransformQueryConfig = {
|
||||||
|
defaults: defaultAdminRefundReasonFields,
|
||||||
|
defaultLimit: 20,
|
||||||
|
isList: true,
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { createRefundReasonsWorkflow } from "@medusajs/core-flows"
|
||||||
|
import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework"
|
||||||
|
import {
|
||||||
|
PaginatedResponse,
|
||||||
|
RefundReasonResponse,
|
||||||
|
RefundReasonsResponse,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import { refetchEntities, refetchEntity } from "../../utils/refetch-entity"
|
||||||
|
import { AdminCreatePaymentRefundReasonType } from "./validators"
|
||||||
|
|
||||||
|
export const GET = async (
|
||||||
|
req: AuthenticatedMedusaRequest,
|
||||||
|
res: MedusaResponse<PaginatedResponse<RefundReasonsResponse>>
|
||||||
|
) => {
|
||||||
|
const { rows: refund_reasons, metadata } = await refetchEntities(
|
||||||
|
"refund_reasons",
|
||||||
|
req.filterableFields,
|
||||||
|
req.scope,
|
||||||
|
req.remoteQueryConfig.fields,
|
||||||
|
req.remoteQueryConfig.pagination
|
||||||
|
)
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
refund_reasons,
|
||||||
|
count: metadata.count,
|
||||||
|
offset: metadata.skip,
|
||||||
|
limit: metadata.take,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const POST = async (
|
||||||
|
req: AuthenticatedMedusaRequest<AdminCreatePaymentRefundReasonType>,
|
||||||
|
res: MedusaResponse<RefundReasonResponse>
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
result: [refundReason],
|
||||||
|
} = await createRefundReasonsWorkflow(req.scope).run({
|
||||||
|
input: { data: [req.validatedBody] },
|
||||||
|
})
|
||||||
|
|
||||||
|
const refund_reason = await refetchEntity(
|
||||||
|
"refund_reason",
|
||||||
|
refundReason.id,
|
||||||
|
req.scope,
|
||||||
|
req.remoteQueryConfig.fields
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({ refund_reason })
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
import { createFindParams, createOperatorMap } from "../../utils/validators"
|
||||||
|
|
||||||
|
export type AdminCreatePaymentRefundReasonType = z.infer<
|
||||||
|
typeof AdminCreatePaymentRefundReason
|
||||||
|
>
|
||||||
|
export const AdminCreatePaymentRefundReason = z
|
||||||
|
.object({
|
||||||
|
label: z.string(),
|
||||||
|
description: z.string().nullish(),
|
||||||
|
})
|
||||||
|
.strict()
|
||||||
|
|
||||||
|
export type AdminUpdatePaymentRefundReasonType = z.infer<
|
||||||
|
typeof AdminUpdatePaymentRefundReason
|
||||||
|
>
|
||||||
|
export const AdminUpdatePaymentRefundReason = z
|
||||||
|
.object({
|
||||||
|
label: z.string().optional(),
|
||||||
|
description: z.string().nullish(),
|
||||||
|
})
|
||||||
|
.strict()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters used to filter and configure the pagination of the retrieved refund reason.
|
||||||
|
*/
|
||||||
|
export const AdminGetRefundReasonsParams = createFindParams({
|
||||||
|
limit: 15,
|
||||||
|
offset: 0,
|
||||||
|
}).merge(
|
||||||
|
z.object({
|
||||||
|
id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||||
|
q: z.string().optional(),
|
||||||
|
created_at: createOperatorMap().optional(),
|
||||||
|
updated_at: createOperatorMap().optional(),
|
||||||
|
deleted_at: createOperatorMap().optional(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
export type AdminGetRefundReasonsParamsType = z.infer<
|
||||||
|
typeof AdminGetRefundReasonsParams
|
||||||
|
>
|
||||||
@@ -23,6 +23,7 @@ import { adminProductTagRoutesMiddlewares } from "./admin/product-tags/middlewar
|
|||||||
import { adminProductTypeRoutesMiddlewares } from "./admin/product-types/middlewares"
|
import { adminProductTypeRoutesMiddlewares } from "./admin/product-types/middlewares"
|
||||||
import { adminProductRoutesMiddlewares } from "./admin/products/middlewares"
|
import { adminProductRoutesMiddlewares } from "./admin/products/middlewares"
|
||||||
import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares"
|
import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares"
|
||||||
|
import { adminRefundReasonsRoutesMiddlewares } from "./admin/refund-reasons/middlewares"
|
||||||
import { adminRegionRoutesMiddlewares } from "./admin/regions/middlewares"
|
import { adminRegionRoutesMiddlewares } from "./admin/regions/middlewares"
|
||||||
import { adminReservationRoutesMiddlewares } from "./admin/reservations/middlewares"
|
import { adminReservationRoutesMiddlewares } from "./admin/reservations/middlewares"
|
||||||
import { adminReturnReasonRoutesMiddlewares } from "./admin/return-reasons/middlewares"
|
import { adminReturnReasonRoutesMiddlewares } from "./admin/return-reasons/middlewares"
|
||||||
@@ -107,5 +108,6 @@ export default defineMiddlewares([
|
|||||||
...storeReturnReasonRoutesMiddlewares,
|
...storeReturnReasonRoutesMiddlewares,
|
||||||
...adminReturnReasonRoutesMiddlewares,
|
...adminReturnReasonRoutesMiddlewares,
|
||||||
...adminClaimRoutesMiddlewares,
|
...adminClaimRoutesMiddlewares,
|
||||||
|
...adminRefundReasonsRoutesMiddlewares,
|
||||||
...adminExchangeRoutesMiddlewares,
|
...adminExchangeRoutesMiddlewares,
|
||||||
])
|
])
|
||||||
|
|||||||
+9
@@ -23,6 +23,7 @@ moduleIntegrationTestRunner<IPaymentModuleService>({
|
|||||||
"payment",
|
"payment",
|
||||||
"paymentCollection",
|
"paymentCollection",
|
||||||
"paymentProvider",
|
"paymentProvider",
|
||||||
|
"refundReason",
|
||||||
])
|
])
|
||||||
|
|
||||||
Object.keys(linkable).forEach((key) => {
|
Object.keys(linkable).forEach((key) => {
|
||||||
@@ -54,6 +55,14 @@ moduleIntegrationTestRunner<IPaymentModuleService>({
|
|||||||
field: "paymentProvider",
|
field: "paymentProvider",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
refundReason: {
|
||||||
|
id: {
|
||||||
|
linkable: "refund_reason_id",
|
||||||
|
primaryKey: "id",
|
||||||
|
serviceName: "payment",
|
||||||
|
field: "refundReason",
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,21 @@ import {
|
|||||||
PaymentCollection,
|
PaymentCollection,
|
||||||
PaymentProvider,
|
PaymentProvider,
|
||||||
PaymentSession,
|
PaymentSession,
|
||||||
|
RefundReason,
|
||||||
} from "@models"
|
} from "@models"
|
||||||
|
|
||||||
export const joinerConfig = defineJoinerConfig(Modules.PAYMENT, {
|
export const joinerConfig = defineJoinerConfig(Modules.PAYMENT, {
|
||||||
models: [Payment, PaymentCollection, PaymentProvider, PaymentSession],
|
models: [
|
||||||
|
Payment,
|
||||||
|
PaymentCollection,
|
||||||
|
PaymentProvider,
|
||||||
|
PaymentSession,
|
||||||
|
RefundReason,
|
||||||
|
],
|
||||||
linkableKeys: {
|
linkableKeys: {
|
||||||
payment_id: Payment.name,
|
payment_id: Payment.name,
|
||||||
payment_collection_id: PaymentCollection.name,
|
payment_collection_id: PaymentCollection.name,
|
||||||
payment_provider_id: PaymentProvider.name,
|
payment_provider_id: PaymentProvider.name,
|
||||||
|
refund_reason_id: RefundReason.name,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,81 @@
|
|||||||
|
import { Migration } from "@mikro-orm/migrations"
|
||||||
|
|
||||||
|
export class Migration20240806072619 extends Migration {
|
||||||
|
async up(): Promise<void> {
|
||||||
|
this.addSql(
|
||||||
|
'create table if not exists "refund_reason" ("id" text not null, "label" text not null, "description" text null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "refund_reason_pkey" primary key ("id"));'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "payment_session" drop constraint if exists "payment_session_status_check";'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "payment_session" drop constraint if exists "payment_session_payment_collection_id_foreign";'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "payment_session" alter column "status" type text using ("status"::text);'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
"alter table if exists \"payment_session\" add constraint \"payment_session_status_check\" check (\"status\" in ('authorized', 'captured', 'pending', 'requires_more', 'error', 'canceled'));"
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'create index if not exists "IDX_payment_session_deleted_at" on "payment_session" ("deleted_at");'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql('drop index if exists "IDX_capture_deleted_at";')
|
||||||
|
this.addSql('drop index if exists "IDX_refund_deleted_at";')
|
||||||
|
this.addSql(
|
||||||
|
'create index if not exists "IDX_payment_payment_session_id" on "payment" ("payment_session_id");'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "payment" add constraint "payment_payment_session_id_unique" unique ("payment_session_id");'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'create index if not exists "IDX_capture_deleted_at" on "capture" ("deleted_at");'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "refund" add column if not exists "refund_reason_id" text null, add column if not exists "note" text null;'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'create index if not exists "IDX_refund_deleted_at" on "refund" ("deleted_at");'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(): Promise<void> {
|
||||||
|
this.addSql('drop table if exists "refund_reason" cascade;')
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "payment_session" drop constraint if exists "payment_session_status_check";'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql('drop index if exists "IDX_capture_deleted_at";')
|
||||||
|
|
||||||
|
this.addSql('drop index if exists "IDX_payment_payment_session_id";')
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "payment" drop constraint if exists "payment_payment_session_id_unique";'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'create index if not exists "IDX_capture_deleted_at" on "payment" ("deleted_at");'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
'create index if not exists "IDX_refund_deleted_at" on "payment" ("deleted_at");'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "payment_session" alter column "status" type text using ("status"::text);'
|
||||||
|
)
|
||||||
|
this.addSql(
|
||||||
|
"alter table if exists \"payment_session\" add constraint \"payment_session_status_check\" check (\"status\" in ('authorized', 'pending', 'requires_more', 'error', 'canceled'));"
|
||||||
|
)
|
||||||
|
this.addSql('drop index if exists "IDX_payment_session_deleted_at";')
|
||||||
|
this.addSql('drop index if exists "IDX_refund_deleted_at";')
|
||||||
|
this.addSql(
|
||||||
|
'alter table if exists "refund" drop column if exists "refund_reason_id";'
|
||||||
|
)
|
||||||
|
this.addSql('alter table if exists "refund" drop column if exists "note";')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,9 @@ export default class Capture {
|
|||||||
})
|
})
|
||||||
payment!: Payment
|
payment!: Payment
|
||||||
|
|
||||||
|
@Property({ columnType: "jsonb", nullable: true })
|
||||||
|
metadata: Record<string, unknown> | null = null
|
||||||
|
|
||||||
@Property({
|
@Property({
|
||||||
onCreate: () => new Date(),
|
onCreate: () => new Date(),
|
||||||
columnType: "timestamptz",
|
columnType: "timestamptz",
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ export { default as PaymentMethodToken } from "./payment-method-token"
|
|||||||
export { default as PaymentProvider } from "./payment-provider"
|
export { default as PaymentProvider } from "./payment-provider"
|
||||||
export { default as PaymentSession } from "./payment-session"
|
export { default as PaymentSession } from "./payment-session"
|
||||||
export { default as Refund } from "./refund"
|
export { default as Refund } from "./refund"
|
||||||
|
export { default as RefundReason } from "./refund-reason"
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default class PaymentMethodToken {
|
|||||||
@Property({
|
@Property({
|
||||||
columnType: "timestamptz",
|
columnType: "timestamptz",
|
||||||
nullable: true,
|
nullable: true,
|
||||||
index: "IDX_payment_metod_token_deleted_at",
|
index: "IDX_payment_method_token_deleted_at",
|
||||||
})
|
})
|
||||||
deleted_at: Date | null = null
|
deleted_at: Date | null = null
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,9 @@ export default class PaymentSession {
|
|||||||
})
|
})
|
||||||
payment?: Rel<Payment> | null
|
payment?: Rel<Payment> | null
|
||||||
|
|
||||||
|
@Property({ columnType: "jsonb", nullable: true })
|
||||||
|
metadata: Record<string, unknown> | null = null
|
||||||
|
|
||||||
@Property({
|
@Property({
|
||||||
onCreate: () => new Date(),
|
onCreate: () => new Date(),
|
||||||
columnType: "timestamptz",
|
columnType: "timestamptz",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
DALUtils,
|
DALUtils,
|
||||||
MikroOrmBigNumberProperty,
|
MikroOrmBigNumberProperty,
|
||||||
Searchable,
|
Searchable,
|
||||||
|
createPsqlIndexStatementHelper,
|
||||||
generateEntityId,
|
generateEntityId,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import {
|
import {
|
||||||
@@ -28,7 +29,13 @@ import Refund from "./refund"
|
|||||||
|
|
||||||
type OptionalPaymentProps = DAL.ModelDateColumns
|
type OptionalPaymentProps = DAL.ModelDateColumns
|
||||||
|
|
||||||
@Entity({ tableName: "payment" })
|
const tableName = "payment"
|
||||||
|
const ProviderIdIndex = createPsqlIndexStatementHelper({
|
||||||
|
tableName,
|
||||||
|
columns: "provider_id",
|
||||||
|
})
|
||||||
|
|
||||||
|
@Entity({ tableName })
|
||||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||||
export default class Payment {
|
export default class Payment {
|
||||||
[OptionalProps]?: OptionalPaymentProps
|
[OptionalProps]?: OptionalPaymentProps
|
||||||
@@ -46,6 +53,7 @@ export default class Payment {
|
|||||||
currency_code: string
|
currency_code: string
|
||||||
|
|
||||||
@Property({ columnType: "text" })
|
@Property({ columnType: "text" })
|
||||||
|
@ProviderIdIndex.MikroORMIndex()
|
||||||
provider_id: string
|
provider_id: string
|
||||||
|
|
||||||
@Searchable()
|
@Searchable()
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { DALUtils, generateEntityId, Searchable } from "@medusajs/utils"
|
||||||
|
import {
|
||||||
|
BeforeCreate,
|
||||||
|
Entity,
|
||||||
|
Filter,
|
||||||
|
OnInit,
|
||||||
|
PrimaryKey,
|
||||||
|
Property,
|
||||||
|
} from "@mikro-orm/core"
|
||||||
|
|
||||||
|
@Entity({ tableName: "refund_reason" })
|
||||||
|
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||||
|
export default class RefundReason {
|
||||||
|
@PrimaryKey({ columnType: "text" })
|
||||||
|
id: string
|
||||||
|
|
||||||
|
@Searchable()
|
||||||
|
@Property({ columnType: "text" })
|
||||||
|
label: string
|
||||||
|
|
||||||
|
@Property({ columnType: "text", nullable: true })
|
||||||
|
description: string | null = null
|
||||||
|
|
||||||
|
@Property({ columnType: "jsonb", nullable: true })
|
||||||
|
metadata: Record<string, unknown> | null = null
|
||||||
|
|
||||||
|
@Property({
|
||||||
|
onCreate: () => new Date(),
|
||||||
|
columnType: "timestamptz",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
})
|
||||||
|
created_at: Date
|
||||||
|
|
||||||
|
@Property({
|
||||||
|
onCreate: () => new Date(),
|
||||||
|
onUpdate: () => new Date(),
|
||||||
|
columnType: "timestamptz",
|
||||||
|
defaultRaw: "now()",
|
||||||
|
})
|
||||||
|
updated_at: Date
|
||||||
|
|
||||||
|
@Property({ columnType: "timestamptz", nullable: true })
|
||||||
|
deleted_at: Date | null = null
|
||||||
|
|
||||||
|
@BeforeCreate()
|
||||||
|
onCreate() {
|
||||||
|
this.id = generateEntityId(this.id, "refr")
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnInit()
|
||||||
|
onInit() {
|
||||||
|
this.id = generateEntityId(this.id, "refr")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BigNumberRawValue } from "@medusajs/types"
|
import { BigNumberRawValue, DAL } from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
BigNumber,
|
BigNumber,
|
||||||
MikroOrmBigNumberProperty,
|
MikroOrmBigNumberProperty,
|
||||||
@@ -9,14 +9,24 @@ import {
|
|||||||
Entity,
|
Entity,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
OnInit,
|
OnInit,
|
||||||
|
OptionalProps,
|
||||||
PrimaryKey,
|
PrimaryKey,
|
||||||
Property,
|
Property,
|
||||||
Rel,
|
Rel,
|
||||||
} from "@mikro-orm/core"
|
} from "@mikro-orm/core"
|
||||||
import Payment from "./payment"
|
import Payment from "./payment"
|
||||||
|
import RefundReason from "./refund-reason"
|
||||||
|
|
||||||
|
type OptionalProps =
|
||||||
|
| "note"
|
||||||
|
| "refund_reason_id"
|
||||||
|
| "refund_reason"
|
||||||
|
| DAL.ModelDateColumns
|
||||||
|
|
||||||
@Entity({ tableName: "refund" })
|
@Entity({ tableName: "refund" })
|
||||||
export default class Refund {
|
export default class Refund {
|
||||||
|
[OptionalProps]?: OptionalProps
|
||||||
|
|
||||||
@PrimaryKey({ columnType: "text" })
|
@PrimaryKey({ columnType: "text" })
|
||||||
id: string
|
id: string
|
||||||
|
|
||||||
@@ -36,6 +46,20 @@ export default class Refund {
|
|||||||
@Property({ columnType: "text", nullable: true })
|
@Property({ columnType: "text", nullable: true })
|
||||||
payment_id: string
|
payment_id: string
|
||||||
|
|
||||||
|
@ManyToOne(() => RefundReason, {
|
||||||
|
columnType: "text",
|
||||||
|
mapToPk: true,
|
||||||
|
fieldName: "refund_reason_id",
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
refund_reason_id: string | null = null
|
||||||
|
|
||||||
|
@ManyToOne(() => RefundReason, { persist: false, nullable: true })
|
||||||
|
refund_reason: Rel<RefundReason> | null = null
|
||||||
|
|
||||||
|
@Property({ columnType: "text", nullable: true })
|
||||||
|
note: string | null = null
|
||||||
|
|
||||||
@Property({
|
@Property({
|
||||||
onCreate: () => new Date(),
|
onCreate: () => new Date(),
|
||||||
columnType: "timestamptz",
|
columnType: "timestamptz",
|
||||||
@@ -66,10 +90,12 @@ export default class Refund {
|
|||||||
@BeforeCreate()
|
@BeforeCreate()
|
||||||
onCreate() {
|
onCreate() {
|
||||||
this.id = generateEntityId(this.id, "ref")
|
this.id = generateEntityId(this.id, "ref")
|
||||||
|
this.refund_reason_id ??= this.refund_reason?.id || null
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnInit()
|
@OnInit()
|
||||||
onInit() {
|
onInit() {
|
||||||
this.id = generateEntityId(this.id, "ref")
|
this.id = generateEntityId(this.id, "ref")
|
||||||
|
this.refund_reason_id ??= this.refund_reason?.id || null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
PaymentSessionDTO,
|
PaymentSessionDTO,
|
||||||
ProviderWebhookPayload,
|
ProviderWebhookPayload,
|
||||||
RefundDTO,
|
RefundDTO,
|
||||||
|
RefundReasonDTO,
|
||||||
UpdatePaymentCollectionDTO,
|
UpdatePaymentCollectionDTO,
|
||||||
UpdatePaymentDTO,
|
UpdatePaymentDTO,
|
||||||
UpdatePaymentSessionDTO,
|
UpdatePaymentSessionDTO,
|
||||||
@@ -47,6 +48,7 @@ import {
|
|||||||
PaymentCollection,
|
PaymentCollection,
|
||||||
PaymentSession,
|
PaymentSession,
|
||||||
Refund,
|
Refund,
|
||||||
|
RefundReason,
|
||||||
} from "@models"
|
} from "@models"
|
||||||
import { joinerConfig } from "../joiner-config"
|
import { joinerConfig } from "../joiner-config"
|
||||||
import PaymentProviderService from "./payment-provider"
|
import PaymentProviderService from "./payment-provider"
|
||||||
@@ -67,6 +69,7 @@ const generateMethodForModels = {
|
|||||||
Payment,
|
Payment,
|
||||||
Capture,
|
Capture,
|
||||||
Refund,
|
Refund,
|
||||||
|
RefundReason,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class PaymentModuleService
|
export default class PaymentModuleService
|
||||||
@@ -76,6 +79,7 @@ export default class PaymentModuleService
|
|||||||
Payment: { dto: PaymentDTO }
|
Payment: { dto: PaymentDTO }
|
||||||
Capture: { dto: CaptureDTO }
|
Capture: { dto: CaptureDTO }
|
||||||
Refund: { dto: RefundDTO }
|
Refund: { dto: RefundDTO }
|
||||||
|
RefundReason: { dto: RefundReasonDTO }
|
||||||
}>(generateMethodForModels)
|
}>(generateMethodForModels)
|
||||||
implements IPaymentModuleService
|
implements IPaymentModuleService
|
||||||
{
|
{
|
||||||
@@ -784,6 +788,8 @@ export default class PaymentModuleService
|
|||||||
payment: data.payment_id,
|
payment: data.payment_id,
|
||||||
amount: data.amount,
|
amount: data.amount,
|
||||||
created_by: data.created_by,
|
created_by: data.created_by,
|
||||||
|
note: data.note,
|
||||||
|
refund_reason_id: data.refund_reason_id,
|
||||||
},
|
},
|
||||||
sharedContext
|
sharedContext
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user