Kaizen
Browse modulesCheckoutcheckout/serverClasses

Class: RefundService

Defined in: server/checkout/refund-service.ts:154

The RefundService (proposal §Refunds: prepare → authorize → execute → reconcile). Refunds mirror the order's two planes — a Plane-1 breakdown (refund_items) inside an approval workflow, producing Plane-2 money facts (refund payment_transactions) that recompute() turns into refunded_total and the projection.

The pure engine (computeRefundBreakdown, stage 2) owns the component math; this service assembles the RefundableOrderView from repo reads, persists the request/items, gates authorization, and drives per-tender settlement with the deterministic in-item-creation-order fill (the documented procedural-counter exception to compute-by-default — amount_refunded_cents is maintained under the order lock in the SAME transaction as the refund/ refund_return txn, since the per-item allocation cannot be re-derived from tender-level transactions).

Extends

  • BaseCheckoutService

Constructors

Constructor

new RefundService(deps: CheckoutFeatureDeps): RefundService;

Defined in: server/checkout/base-checkout-service.ts:43

Parameters

ParameterType
depsCheckoutFeatureDeps

Returns

RefundService

Inherited from

BaseCheckoutService.constructor

Properties

deps

protected readonly deps: CheckoutFeatureDeps;

Defined in: server/checkout/base-checkout-service.ts:43

Inherited from

BaseCheckoutService.deps

Accessors

actorId

Get Signature

get protected actorId(): string | null;

Defined in: server/checkout/base-checkout-service.ts:45

Returns

string | null

Inherited from

BaseCheckoutService.actorId

Methods

authorizeRefund()

authorizeRefund(refundRequestId: string): Promise<BigIntsAsNumbers<{
  authorizedAt: Date | null;
  authorizedBy: string | null;
  completedAt: Date | null;
  createdAt: Date;
  createdBy: string | null;
  disbursementMethod: string | null;
  id: string;
  idempotencyKey: string | null;
  initiatedBy: string | null;
  notes: string | null;
  orderId: string;
  organizationId: string;
  reason: string | null;
  requestedAmountCents: bigint;
  requestHash: string | null;
  status: RefundRequestStatus;
  updatedAt: Date;
  updatedBy: string | null;
}>>;

Defined in: server/checkout/refund-service.ts:322

The role-gated approval step (proposal §Refunds, §Factory). Consults the injected refundPolicy — 'allow-any-actor' authorizes anyone; a RefundPolicy throws to deny (the request stays pending_authorization). On success: authorized + authorizedBy/authorizedAt.

Parameters

ParameterType
refundRequestIdstring

Returns

Promise<BigIntsAsNumbers<{ authorizedAt: Date | null; authorizedBy: string | null; completedAt: Date | null; createdAt: Date; createdBy: string | null; disbursementMethod: string | null; id: string; idempotencyKey: string | null; initiatedBy: string | null; notes: string | null; orderId: string; organizationId: string; reason: string | null; requestedAmountCents: bigint; requestHash: string | null; status: RefundRequestStatus; updatedAt: Date; updatedBy: string | null; }>>


executeRefund()

executeRefund(refundRequestId: string): Promise<ExecuteRefundResult>;

Defined in: server/checkout/refund-service.ts:380

Execute an authorized refund (proposal §Refunds execute + §Per-component settlement). Re-runnable on authorized/partially_completed/processing: the durable processing claim serializes provider calls per order and is also the crash-recovery state. Each run attempts only the UNFILLED remainder, per tender, idempotently. For each tender in the suggested split:

  • compute the tender's still-unfilled amount from the current item fills;
  • provider.refund() idempotently (key = refund:{requestId}:{paymentId}:{attempt}) OUTSIDE any transaction (the ManualProvider balance-ledger exception runs inside settlement);
  • on success, in ONE transaction under the order + payment lock: record the refund txn through record() (the only txn write path), fill refund_items.amount_refunded_cents in item-creation order (each item before the next), recompute the tender + order buckets, append order.refunded.

A tender whose provider call throws leaves that tender unfilled and the request partially_completed (or authorized if none succeeded) — no double refund, no stranded money. disbursementMethod routes through ManualProvider (a refund txn whose provider is manual attached to the original tender) → the request settles completed_manually.

Parameters

ParameterType
refundRequestIdstring

Returns

Promise<ExecuteRefundResult>


getRequest()

getRequest(refundRequestId: string): Promise<BigIntsAsNumbers<{
  authorizedAt: Date | null;
  authorizedBy: string | null;
  completedAt: Date | null;
  createdAt: Date;
  createdBy: string | null;
  disbursementMethod: string | null;
  id: string;
  idempotencyKey: string | null;
  initiatedBy: string | null;
  notes: string | null;
  orderId: string;
  organizationId: string;
  reason: string | null;
  requestedAmountCents: bigint;
  requestHash: string | null;
  status: RefundRequestStatus;
  updatedAt: Date;
  updatedBy: string | null;
}>>;

Defined in: server/checkout/refund-service.ts:685

Parameters

ParameterType
refundRequestIdstring

Returns

Promise<BigIntsAsNumbers<{ authorizedAt: Date | null; authorizedBy: string | null; completedAt: Date | null; createdAt: Date; createdBy: string | null; disbursementMethod: string | null; id: string; idempotencyKey: string | null; initiatedBy: string | null; notes: string | null; orderId: string; organizationId: string; reason: string | null; requestedAmountCents: bigint; requestHash: string | null; status: RefundRequestStatus; updatedAt: Date; updatedBy: string | null; }>>


handleRefundWebhook()

handleRefundWebhook(
   refundRequestId: string, 
   provider: string, 
   transactions: CanonicalTxn[]
): Promise<ExecuteRefundResult>;

Defined in: server/checkout/refund-service.ts:626

Async refund settlement (proposal §Refunds reconcile): refund.updated webhooks settle async refunds; a refund.failed AFTER a recorded success appends a refund_return txn (dedupe key {providerObjectId}:refund_return — a distinct row so the immutability rule holds and the upsert cannot no-op), recompute() nets it out of refundedCents, the allocation is reversed in REVERSE-FILL order (drain from the last item with a non-zero amount_refunded_cents backward — a read-current-state operation, not a replay), and the request returns to authorized (re-executable).

The adapter's ParsedWebhook.transactions carry refund/refund_return canonical events linked to the refund request; the caller supplies the request id (webhook routing to the refund request is the adapter/consumer's concern — the port carries refundRequestId on RefundInput, echoed back on the object).

Parameters

ParameterType
refundRequestIdstring
providerstring
transactionsCanonicalTxn[]

Returns

Promise<ExecuteRefundResult>


listForOrder()

listForOrder(orderId: string): Promise<BigIntsAsNumbers<{
  authorizedAt: Date | null;
  authorizedBy: string | null;
  completedAt: Date | null;
  createdAt: Date;
  createdBy: string | null;
  disbursementMethod: string | null;
  id: string;
  idempotencyKey: string | null;
  initiatedBy: string | null;
  notes: string | null;
  orderId: string;
  organizationId: string;
  reason: string | null;
  requestedAmountCents: bigint;
  requestHash: string | null;
  status: RefundRequestStatus;
  updatedAt: Date;
  updatedBy: string | null;
}>[]>;

Defined in: server/checkout/refund-service.ts:694

Parameters

ParameterType
orderIdstring

Returns

Promise<BigIntsAsNumbers<{ authorizedAt: Date | null; authorizedBy: string | null; completedAt: Date | null; createdAt: Date; createdBy: string | null; disbursementMethod: string | null; id: string; idempotencyKey: string | null; initiatedBy: string | null; notes: string | null; orderId: string; organizationId: string; reason: string | null; requestedAmountCents: bigint; requestHash: string | null; status: RefundRequestStatus; updatedAt: Date; updatedBy: string | null; }>[]>


prepareRefund()

prepareRefund(orderId: string, input: PrepareRefundInput): Promise<BigIntsAsNumbers<{
  authorizedAt: Date | null;
  authorizedBy: string | null;
  completedAt: Date | null;
  createdAt: Date;
  createdBy: string | null;
  disbursementMethod: string | null;
  id: string;
  idempotencyKey: string | null;
  initiatedBy: string | null;
  notes: string | null;
  orderId: string;
  organizationId: string;
  reason: string | null;
  requestedAmountCents: bigint;
  requestHash: string | null;
  status: RefundRequestStatus;
  updatedAt: Date;
  updatedBy: string | null;
}>>;

Defined in: server/checkout/refund-service.ts:176

Build the RefundableOrderView from repo reads, delegate to the shared computeRefundBreakdown, validate, and persist refund_requests (pending_authorization) + refund_items.

Validations the DB/engine cannot own:

  • every referenced component belongs to THIS order (a cross-order pointer the engine never emits, but the spec's scope ids are caller-supplied);
  • getCapabilities(tender.tenderMethod) for EVERY tender in suggestedTenderSplit — supportsPartialRefund: false against a partial → UnsupportedCapabilityError at PREPARE time, so an authorized refund is always executable (proposal §Refunds);
  • Σ suggestedTenderSplit === requestedAmountCents — the engine's split never throws on tender-capacity shortfall (it drains what exists), so the shortfall assertion is the service's (stage-2 contract).

Parameters

ParameterType
orderIdstring
inputPrepareRefundInput

Returns

Promise<BigIntsAsNumbers<{ authorizedAt: Date | null; authorizedBy: string | null; completedAt: Date | null; createdAt: Date; createdBy: string | null; disbursementMethod: string | null; id: string; idempotencyKey: string | null; initiatedBy: string | null; notes: string | null; orderId: string; organizationId: string; reason: string | null; requestedAmountCents: bigint; requestHash: string | null; status: RefundRequestStatus; updatedAt: Date; updatedBy: string | null; }>>


recomputeTenderFromLedger()

protected recomputeTenderFromLedger(
   repos: CheckoutRepositories, 
   payment: PaymentRow, 
   overrides?: Partial<Pick<TenderFacts, "clientActionRequired" | "clientActionRequiredAt">>
): Promise<{
  fold: TenderComputation;
  patch: TenderBucketPatch;
}>;

Defined in: server/checkout/base-checkout-service.ts:94

Re-project a tender from its persisted ledger without writing it. Explicit clientActionRequiredAt: null clears the stored timestamp.

Parameters

ParameterType
reposCheckoutRepositories
paymentPaymentRow
overrides?Partial<Pick<TenderFacts, "clientActionRequired" | "clientActionRequiredAt">>

Returns

Promise<{ fold: TenderComputation; patch: TenderBucketPatch; }>

Inherited from

BaseCheckoutService.recomputeTenderFromLedger

reconcileClosedAt()

protected reconcileClosedAt(
   repos: CheckoutRepositories, 
   order: OrderRow, 
   now: Date
): Promise<void>;

Defined in: server/checkout/base-checkout-service.ts:121

Reconcile closedAt and the once-per-order order.paid event after a money write. The caller must hold the order lock in an ambient transaction.

Parameters

ParameterType
reposCheckoutRepositories
orderOrderRow
nowDate

Returns

Promise<void>

Inherited from

BaseCheckoutService.reconcileClosedAt

recordCanonicalTransactions()

protected recordCanonicalTransactions(
   repos: CheckoutRepositories, 
   context: RecordTxnContext, 
   transactions: CanonicalTxn[]
): Promise<RecordedCanonicalTxn[]>;

Defined in: server/checkout/base-checkout-service.ts:61

Record canonical transactions sequentially and identify both fresh inserts and pending rows resolved in place. Callers own the resulting side effects.

Parameters

ParameterType
reposCheckoutRepositories
contextRecordTxnContext
transactionsCanonicalTxn[]

Returns

Promise<RecordedCanonicalTxn[]>

Inherited from

BaseCheckoutService.recordCanonicalTransactions

transaction()

protected transaction<T>(fn: (deps: CheckoutFeatureDeps) => Promise<T>): Promise<T>;

Defined in: server/checkout/base-checkout-service.ts:49

Type Parameters

Type Parameter
T

Parameters

ParameterType
fn(deps: CheckoutFeatureDeps) => Promise<T>

Returns

Promise<T>

Inherited from

BaseCheckoutService.transaction

On this page