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
| Parameter | Type |
|---|---|
deps | CheckoutFeatureDeps |
Returns
RefundService
Inherited from
BaseCheckoutService.constructorProperties
deps
protected readonly deps: CheckoutFeatureDeps;Defined in: server/checkout/base-checkout-service.ts:43
Inherited from
BaseCheckoutService.depsAccessors
actorId
Get Signature
get protected actorId(): string | null;Defined in: server/checkout/base-checkout-service.ts:45
Returns
string | null
Inherited from
BaseCheckoutService.actorIdMethods
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
| Parameter | Type |
|---|---|
refundRequestId | string |
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
refundtxn throughrecord()(the only txn write path), fillrefund_items.amount_refunded_centsin item-creation order (each item before the next), recompute the tender + order buckets, appendorder.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
| Parameter | Type |
|---|---|
refundRequestId | string |
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
| Parameter | Type |
|---|---|
refundRequestId | string |
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
| Parameter | Type |
|---|---|
refundRequestId | string |
provider | string |
transactions | CanonicalTxn[] |
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
| Parameter | Type |
|---|---|
orderId | string |
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
scopeids are caller-supplied); getCapabilities(tender.tenderMethod)for EVERY tender insuggestedTenderSplit—supportsPartialRefund: falseagainst a partial →UnsupportedCapabilityErrorat 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
| Parameter | Type |
|---|---|
orderId | string |
input | PrepareRefundInput |
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
| Parameter | Type |
|---|---|
repos | CheckoutRepositories |
payment | PaymentRow |
overrides? | Partial<Pick<TenderFacts, "clientActionRequired" | "clientActionRequiredAt">> |
Returns
Promise<{
fold: TenderComputation;
patch: TenderBucketPatch;
}>
Inherited from
BaseCheckoutService.recomputeTenderFromLedgerreconcileClosedAt()
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
| Parameter | Type |
|---|---|
repos | CheckoutRepositories |
order | OrderRow |
now | Date |
Returns
Promise<void>
Inherited from
BaseCheckoutService.reconcileClosedAtrecordCanonicalTransactions()
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
| Parameter | Type |
|---|---|
repos | CheckoutRepositories |
context | RecordTxnContext |
transactions | CanonicalTxn[] |
Returns
Promise<RecordedCanonicalTxn[]>
Inherited from
BaseCheckoutService.recordCanonicalTransactionstransaction()
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
| Parameter | Type |
|---|---|
fn | (deps: CheckoutFeatureDeps) => Promise<T> |
Returns
Promise<T>
Inherited from
BaseCheckoutService.transaction