# From a $1,000 payment to $600 in revenue Date: October 29, 2026 Author: Thomas Neckel Position: CEO and Co-founder Source: https://fragment.dev/blog/prepaid-payment-lifecycle [Documentation index](https://fragment.dev/llms.txt) A customer can pay in full while the business still owes the whole service. For a prepaid workshop, collecting money and earning revenue happen at different times. And a cancellation after partial delivery changes a different obligation from a cancellation before the workshop begins. A payment status cannot decide those business facts. The accounting needs to preserve them. ## One booking and four entries We exercised this distinction in a development sandbox with one synthetic $1,000 prepaid booking. The configured accounts tracked payment assets, unearned service obligations, earned revenue, processing fees and refund payable. The flow collected $1,000, earned $600 of service, approved a $100 cancellation and issued a $100 partial refund against the original payment. | Event | Financial effect in this model | Who posted the entry | | --- | --- | --- | | Payment settles | Payment assets increase by $1,000; unearned obligation increases by $1,000. A $29.30 fee increases fee expense and reduces payment assets. | Native Payment Type accounting | | $600 of service is delivered | Unearned obligation decreases by $600; earned revenue increases by $600. | Application-approved earning entry | | $100 cancellation is approved | Unearned obligation decreases by $100; refund payable increases by $100. | Application-approved cancellation entry | | $100 refund is initiated | Payment assets decrease by $100; refund payable decreases by $100. The refund's reported outcome remains separate. | Native Payment Type accounting | The $29.30 fee was an observed sandbox value, not a price quote. The refund later reached settled status. The final balances explained the booking: - Payment assets: $870.70. - Unearned service obligation: $300. - Earned revenue: $600. - Refund payable: $0. - Processing fee expense: $29.30. There were four ledger entries: two generated by the native payment lifecycle and two explicitly posted for approved business decisions. The application did not post the settlement, fee or refund-initiation effects a second time. ## The business decision stays explicit The cancellation entry created the approved refund obligation. The refund API moved money against that obligation. The application had to choose the amount and the policy before making either request. And a zero refund-payable balance in this configured model does not replace the refund's processing, settled or failed status. The financial entry and the reported payment outcome answer different questions. A payment-account balance also does not establish receipt in a bank account. Fragment's [Payment Type accounting](https://fragment.dev/guides/make-payments.md) supplies the supported native lifecycle entries. The application supplies earning and cancellation entries appropriate to its own business model. A cancellation of already delivered service may require a different approved treatment from the unearned cancellation shown here. ## The remaining obligation limits earning After earning $600 and cancelling $100, the booking had $300 of unearned obligation left. We attempted another $400 earning entry. The configured nonnegative postcondition rejected it. The balances stayed unchanged. The condition protected that earning entry through the reviewed ledger posting path; it did not establish an approval gate on a separate payment or refund call. This was one sandbox lifecycle. Failed-refund execution and bank-receipt matching were not part of it. Through collection, delivery, cancellation and refund, the same booking had several financial facts. Keeping them separate is what lets the books explain the payment. ## Evidence appendix These selected observations come from one development-sandbox lifecycle on October 28, 2026. Workspace, ledger, payment and entry identities are omitted. Amounts below are USD cents. | Check | Recorded value or result | | --- | --- | | Payment amount and final status | `amount: "100000"`; `status: "settled"` | | Total refunded amount | `refundedAmount: "10000"` | | Refund amount and final status | `amount: "10000"`; `status: "settled"` | | Settlement event | `PaymentSettledEvent` links to its ledger entry. | | Refund initiation event | `PaymentRefundInitiatedEvent` links to its ledger entry. | | Refund settlement event | `PaymentRefundSettledEvent` changes status; no second refund entry was posted. | The earning entry reduced unearned liability and increased income by the requested amount. Its configured condition required the unearned account's total balance to remain nonnegative. | Guard check | Configuration or observed result | | --- | --- | | Condition on the unearned account | `{"postcondition":{"totalBalance":{"gte":"0"}}}` | | Unearned balance before the rejected request | `30000` | | Requested additional earning | `40000` | | Result | Rejected; unearned balance remained `30000`. | The condition shown is an excerpt, not a complete deployable schema. See [Payment Type configuration](https://fragment.dev/guides/make-payments.md), [posting business entries](https://fragment.dev/guides/post-ledger-entries.md) and [balance conditions and consistency](https://fragment.dev/guides/configure-consistency.md) for the implementation contracts. The [refund recovery example](https://fragment.dev/blog/lost-refund-response.md) records the replay checks from this same lifecycle.