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:
@@ -23,6 +23,7 @@ moduleIntegrationTestRunner<IPaymentModuleService>({
|
||||
"payment",
|
||||
"paymentCollection",
|
||||
"paymentProvider",
|
||||
"refundReason",
|
||||
])
|
||||
|
||||
Object.keys(linkable).forEach((key) => {
|
||||
@@ -54,6 +55,14 @@ moduleIntegrationTestRunner<IPaymentModuleService>({
|
||||
field: "paymentProvider",
|
||||
},
|
||||
},
|
||||
refundReason: {
|
||||
id: {
|
||||
linkable: "refund_reason_id",
|
||||
primaryKey: "id",
|
||||
serviceName: "payment",
|
||||
field: "refundReason",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -4,13 +4,21 @@ import {
|
||||
PaymentCollection,
|
||||
PaymentProvider,
|
||||
PaymentSession,
|
||||
RefundReason,
|
||||
} from "@models"
|
||||
|
||||
export const joinerConfig = defineJoinerConfig(Modules.PAYMENT, {
|
||||
models: [Payment, PaymentCollection, PaymentProvider, PaymentSession],
|
||||
models: [
|
||||
Payment,
|
||||
PaymentCollection,
|
||||
PaymentProvider,
|
||||
PaymentSession,
|
||||
RefundReason,
|
||||
],
|
||||
linkableKeys: {
|
||||
payment_id: Payment.name,
|
||||
payment_collection_id: PaymentCollection.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
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
|
||||
@@ -5,3 +5,4 @@ export { default as PaymentMethodToken } from "./payment-method-token"
|
||||
export { default as PaymentProvider } from "./payment-provider"
|
||||
export { default as PaymentSession } from "./payment-session"
|
||||
export { default as Refund } from "./refund"
|
||||
export { default as RefundReason } from "./refund-reason"
|
||||
|
||||
@@ -45,7 +45,7 @@ export default class PaymentMethodToken {
|
||||
@Property({
|
||||
columnType: "timestamptz",
|
||||
nullable: true,
|
||||
index: "IDX_payment_metod_token_deleted_at",
|
||||
index: "IDX_payment_method_token_deleted_at",
|
||||
})
|
||||
deleted_at: Date | null = null
|
||||
|
||||
|
||||
@@ -79,6 +79,9 @@ export default class PaymentSession {
|
||||
})
|
||||
payment?: Rel<Payment> | null
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
DALUtils,
|
||||
MikroOrmBigNumberProperty,
|
||||
Searchable,
|
||||
createPsqlIndexStatementHelper,
|
||||
generateEntityId,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
@@ -28,7 +29,13 @@ import Refund from "./refund"
|
||||
|
||||
type OptionalPaymentProps = DAL.ModelDateColumns
|
||||
|
||||
@Entity({ tableName: "payment" })
|
||||
const tableName = "payment"
|
||||
const ProviderIdIndex = createPsqlIndexStatementHelper({
|
||||
tableName,
|
||||
columns: "provider_id",
|
||||
})
|
||||
|
||||
@Entity({ tableName })
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
export default class Payment {
|
||||
[OptionalProps]?: OptionalPaymentProps
|
||||
@@ -46,6 +53,7 @@ export default class Payment {
|
||||
currency_code: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
@ProviderIdIndex.MikroORMIndex()
|
||||
provider_id: string
|
||||
|
||||
@Searchable()
|
||||
|
||||
54
packages/modules/payment/src/models/refund-reason.ts
Normal file
54
packages/modules/payment/src/models/refund-reason.ts
Normal file
@@ -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 {
|
||||
BigNumber,
|
||||
MikroOrmBigNumberProperty,
|
||||
@@ -9,14 +9,24 @@ import {
|
||||
Entity,
|
||||
ManyToOne,
|
||||
OnInit,
|
||||
OptionalProps,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
Rel,
|
||||
} from "@mikro-orm/core"
|
||||
import Payment from "./payment"
|
||||
import RefundReason from "./refund-reason"
|
||||
|
||||
type OptionalProps =
|
||||
| "note"
|
||||
| "refund_reason_id"
|
||||
| "refund_reason"
|
||||
| DAL.ModelDateColumns
|
||||
|
||||
@Entity({ tableName: "refund" })
|
||||
export default class Refund {
|
||||
[OptionalProps]?: OptionalProps
|
||||
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@@ -36,6 +46,20 @@ export default class Refund {
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
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({
|
||||
onCreate: () => new Date(),
|
||||
columnType: "timestamptz",
|
||||
@@ -66,10 +90,12 @@ export default class Refund {
|
||||
@BeforeCreate()
|
||||
onCreate() {
|
||||
this.id = generateEntityId(this.id, "ref")
|
||||
this.refund_reason_id ??= this.refund_reason?.id || null
|
||||
}
|
||||
|
||||
@OnInit()
|
||||
onInit() {
|
||||
this.id = generateEntityId(this.id, "ref")
|
||||
this.refund_reason_id ??= this.refund_reason?.id || null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
PaymentSessionDTO,
|
||||
ProviderWebhookPayload,
|
||||
RefundDTO,
|
||||
RefundReasonDTO,
|
||||
UpdatePaymentCollectionDTO,
|
||||
UpdatePaymentDTO,
|
||||
UpdatePaymentSessionDTO,
|
||||
@@ -47,6 +48,7 @@ import {
|
||||
PaymentCollection,
|
||||
PaymentSession,
|
||||
Refund,
|
||||
RefundReason,
|
||||
} from "@models"
|
||||
import { joinerConfig } from "../joiner-config"
|
||||
import PaymentProviderService from "./payment-provider"
|
||||
@@ -67,6 +69,7 @@ const generateMethodForModels = {
|
||||
Payment,
|
||||
Capture,
|
||||
Refund,
|
||||
RefundReason,
|
||||
}
|
||||
|
||||
export default class PaymentModuleService
|
||||
@@ -76,6 +79,7 @@ export default class PaymentModuleService
|
||||
Payment: { dto: PaymentDTO }
|
||||
Capture: { dto: CaptureDTO }
|
||||
Refund: { dto: RefundDTO }
|
||||
RefundReason: { dto: RefundReasonDTO }
|
||||
}>(generateMethodForModels)
|
||||
implements IPaymentModuleService
|
||||
{
|
||||
@@ -784,6 +788,8 @@ export default class PaymentModuleService
|
||||
payment: data.payment_id,
|
||||
amount: data.amount,
|
||||
created_by: data.created_by,
|
||||
note: data.note,
|
||||
refund_reason_id: data.refund_reason_id,
|
||||
},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user