Skip to content

Commit

Permalink
feat(core-flows,payment,medusa,types): Refund reasons management API (#…
Browse files Browse the repository at this point in the history
…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 <[email protected]>

* 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 <[email protected]>

* chore: address review

---------

Co-authored-by: Carlos R. L. Rodrigues <[email protected]>
Co-authored-by: Harminder Virk <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Oli Juhl <[email protected]>
  • Loading branch information
5 people authored Aug 6, 2024
1 parent 8fb0797 commit 0ff5b97
Show file tree
Hide file tree
Showing 36 changed files with 2,412 additions and 9 deletions.
16 changes: 12 additions & 4 deletions integration-tests/http/__tests__/payment/admin/payment.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,17 @@ medusaIntegrationTestRunner({
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(
`/admin/payments/${payment.id}/refund`,
{
amount: 500,
// BREAKING: We should probably introduce reason and notes in V2 too
// reason: "return",
// note: "Do not like it",
refund_reason_id: refundReason.id,
note: "Do not like it",
},
adminHeaders
)
Expand All @@ -155,6 +158,11 @@ medusaIntegrationTestRunner({
expect.objectContaining({
id: expect.any(String),
amount: 500,
note: "Do not like it",
refund_reason_id: refundReason.id,
refund_reason: expect.objectContaining({
label: "test",
}),
}),
],
amount: 1000,
Expand Down
155 changes: 155 additions & 0 deletions integration-tests/http/__tests__/refund-reason/refund-reason.spec.ts
Original file line number Diff line number Diff line change
@@ -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`
)
})
})
})
},
})
Original file line number Diff line number Diff line change
@@ -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)
}
)
Original file line number Diff line number Diff line change
@@ -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)
}
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export * from "./create-payment-session"
export * from "./create-refund-reasons"
export * from "./delete-payment-sessions"
export * from "./delete-refund-reasons"
export * from "./update-payment-collection"
export * from "./update-refund-reasons"
export * from "./validate-deleted-payment-sessions"
Original file line number Diff line number Diff line change
@@ -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)
)
)
}
)
Original file line number Diff line number Diff line change
@@ -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))
}
)
Original file line number Diff line number Diff line change
@@ -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))
}
)
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from "./create-payment-session"
export * from "./create-refund-reasons"
export * from "./update-refund-reasons"
Original file line number Diff line number Diff line change
@@ -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))
}
)
22 changes: 22 additions & 0 deletions packages/core/types/src/http/payment/admin.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BaseFilterable } from "../../dal"
import {
BasePayment,
BasePaymentCollection,
Expand All @@ -7,6 +8,7 @@ import {
BasePaymentProviderFilters,
BasePaymentSession,
BasePaymentSessionFilters,
RefundReason,
} from "./common"

export interface AdminPaymentProvider extends BasePaymentProvider {
Expand Down Expand Up @@ -42,3 +44,23 @@ export interface AdminPaymentsResponse {
}

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
}
Loading

0 comments on commit 0ff5b97

Please sign in to comment.