Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 39s
refund.sx — refund as a second flow-on-sx flow (request -> approve -> settle) with two suspension points (approval = human/policy decision, settle = provider). refund-begin! records :refund-requested and suspends at approval; refund-approve! advances to settle; refund-settle! records :refunded (idempotent) and completes; refund-reject! records :refund-rejected and cancels. Only :refunded moves the books. Reuses order.sx flow helpers. Completes the Phase 5 backlog. Total 278/278 across 17 suites. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
98 lines
2.8 KiB
Plaintext
98 lines
2.8 KiB
Plaintext
;; lib/commerce/refund.sx — refund lifecycle as a second flow-on-sx flow.
|
|
;;
|
|
;; A refund is request → approve → settle, with TWO genuine suspension points:
|
|
;; approval (a human/policy decision) and settlement (the provider issuing the
|
|
;; refund). Like order.sx the flow is pure orchestration carrying only the
|
|
;; order-id; the SX driver does all ledger IO and reuses order.sx's generic flow
|
|
;; helpers (order-flow-waiting/-resume/-status, order-susp-id).
|
|
;;
|
|
;; refund-begin! → ledger :refund-requested, flow suspends at 'approve
|
|
;; refund-approve! → resume past approval, flow suspends at 'settle
|
|
;; refund-settle! → ledger :refunded (idempotent), flow completes
|
|
;; refund-reject! → ledger :refund-rejected, flow cancelled
|
|
;;
|
|
;; Only :refunded moves the books (recon.sx), so a requested-but-unsettled or
|
|
;; rejected refund leaves reconciliation unchanged.
|
|
|
|
(define
|
|
refund-flow-src
|
|
"(defflow refund-lifecycle (lambda (oid) (begin (request (quote approve) oid) (request (quote settle) oid))))")
|
|
|
|
(define
|
|
refund-make-env
|
|
(fn
|
|
()
|
|
(let
|
|
((env (flow-make-env)))
|
|
(begin (flow-run-in env refund-flow-src) env))))
|
|
|
|
;; Register the refund flow into an existing (e.g. order) env.
|
|
(define
|
|
refund-flow-load!
|
|
(fn (env) (begin (flow-run-in env refund-flow-src) env)))
|
|
|
|
(define
|
|
refund-flow-start
|
|
(fn
|
|
(env oid)
|
|
(flow-run-in env (str "(flow/start refund-lifecycle \"" oid "\")"))))
|
|
|
|
;; --- ledger writes ---
|
|
|
|
(define
|
|
refund-request
|
|
(fn
|
|
(b oid ref at amount)
|
|
(persist/append-once
|
|
b
|
|
(order-stream oid)
|
|
(str "refund-req/" ref)
|
|
:refund-requested at
|
|
{:amount amount :ref ref})))
|
|
|
|
;; --- lifecycle ---
|
|
|
|
;; Open a refund: record the request, start the flow, suspend at approval.
|
|
(define
|
|
refund-begin!
|
|
(fn
|
|
(env b oid ref at amount)
|
|
(begin
|
|
(refund-request b oid ref at amount)
|
|
(order-susp-id (refund-flow-start env oid)))))
|
|
|
|
(define
|
|
refund-approve!
|
|
(fn
|
|
(env id)
|
|
(if
|
|
(= (order-flow-waiting env id) "approve")
|
|
(begin (order-flow-resume env id :approved) :approved)
|
|
:not-pending-approval)))
|
|
|
|
(define
|
|
refund-reject!
|
|
(fn
|
|
(env b oid id at reason)
|
|
(if
|
|
(= (order-flow-waiting env id) "approve")
|
|
(begin
|
|
(persist/append b (order-stream oid) :refund-rejected at {:reason reason})
|
|
(flow-run-in env (str "(flow/cancel " id ")"))
|
|
:rejected)
|
|
:not-pending-approval)))
|
|
|
|
;; Settle (provider issued the refund): idempotent — only acts while waiting on
|
|
;; settle, so a replayed provider callback returns :already-settled.
|
|
(define
|
|
refund-settle!
|
|
(fn
|
|
(env b id oid ref at amount)
|
|
(if
|
|
(= (order-flow-waiting env id) "settle")
|
|
(begin
|
|
(order-refund b oid ref at amount)
|
|
(order-flow-resume env id :settled)
|
|
:settled)
|
|
:already-settled)))
|