P. Real estate accounting: collect property photography payments
How do you use FRAGMENT for real estate accounting?
For real estate accounting, collect approved property photography invoices through FRAGMENT's card checkout. Record the delivered service invoice and create a Payment tied to its identifier. FRAGMENT matches the settled receipt to that invoice and records the processing fee separately.
| Operation | Who handles it? |
|---|---|
| Authorize the photography invoice and collectible amount | Your application |
| Collect a card payment from the listing customer | FRAGMENT checkout and payments API |
| Post settlement and processing-fee accounting | FRAGMENT, using your Payment Type |
| Record approved photography invoice | Your application posts photography_invoice_approve |
Set up the Ledger and Payment Account
Use the Quickstart to configure your SDK and create a Ledger with the example schema. Use property-photographer as its ik. Configure a Payment Account before collecting payments. The schema marks assets/payments with payment: { "enabled": true }.
- assets
- payments
- receivables:<id>
- outstanding
- income
- photography:<id>
- expenses
- payment-fees
The receivable records an approved, earned invoice. Settlement reduces the outstanding amount without recognizing that income twice. Processing fees are a separate expense.
| Account | What it tracks |
|---|---|
assets/payments | Card receipts less processing fees |
assets/receivables:<invoice_id>/outstanding | Uncollected invoice amount |
income/photography:<invoice_id> | Recognized income |
expenses/payment-fees | FRAGMENT processing fees |
Record the approved invoice before collecting
Confirm the delivered package, listing customer, property reference, and accepted invoice total before collecting payment. Post the approved invoice once before creating its Payment. The entry records the receivable and earned income. Your accounting policy determines the recognition date. Use the AddLedgerEntry mutation with these variables.
{
"ik": "photography_invoice_approve-1-v1",
"entry": {
"ledger": {
"ik": "property-photographer"
},
"type": "photography_invoice_approve",
"typeVersion": 1,
"parameters": {
"invoice_id": "1",
"amount": "10000"
}
}
}Define the Payment Type
Add photography_invoice to payments.types in your Schema. Its payment.amount reads the server-approved amount. Add the settlement accounting below to these fields; the complete schema contains both.
{
"type": "photography_invoice",
"typeVersion": 1,
"status": "active",
"payment": {
"amount": "{{amount}}",
"direction": "payin"
}
}Set accounting.settled.lines to these four lines. FRAGMENT supplies settled_amount and fragment_fee_amount; your application supplies invoice_id. The listing customer receives credit for the gross amount. The Payment Account receives that amount less the processing fee.
[
{"key": "settlement", "account": {"path": "assets/payments"}, "amount": "{{settled_amount}}", "system": "payment_settlement_line"},
{"key": "fee", "account": {"path": "assets/payments"}, "amount": "-{{fragment_fee_amount}}", "system": "payment_fee_line"},
{"key": "receipt", "account": {"path": "assets/receivables:{{invoice_id}}/outstanding"}, "amount": "-{{settled_amount}}"},
{"key": "fee-expense", "account": {"path": "expenses/payment-fees"}, "amount": "{{fragment_fee_amount}}"}
]Create a Payment from your backend
Resolve the approved photography invoice, payer, and amount from authenticated application records. Call createPayment with a stable idempotency key. Amounts are integer strings in USD cents. Return the Payment to your frontend; keep API credentials on your server.
const { createPayment } = await fragment.createPayment({
ik: 'photography_invoice-1-v1',
ledgerIk: 'property-photographer',
type: 'photography_invoice',
typeVersion: 1,
parameters: { invoice_id: '1', amount: '10000' },
});
if (createPayment.__typename !== 'CreatePaymentResult') {
throw new Error(createPayment.message);
}
return { payment: createPayment.payment };Embed FRAGMENT checkout in your product
Mount Checkout inside PaymentElements from @fragment-dev/payment-elements. Pass the Payment returned by your backend and the payer's email. The Payment starts in needs_payment_method. Your Pay button calls confirm() to charge the card.
- Browser
- Your server
- FRAGMENT
- Card rail
- createPayment
- Payment
- Payment
- confirm
- capture
- captured
Place checkout in the photography invoice screen. Use onConfirmed to update that screen. Verify the Payment's settled status on your backend before marking the receipt available.
import { Checkout, PaymentElements, type Payment } from '@fragment-dev/payment-elements';
import { PayButton } from './PayButton';
type Props = { payment: Payment; email: string; onConfirmed: () => void };
export const PaymentCheckout = ({ payment, email, onConfirmed }: Props) => (
<PaymentElements>
<Checkout
payment={payment}
billingDetails={{ email }}
onConfirmed={onConfirmed}
onError={(message) => console.error(message)}
>
<PayButton />
</Checkout>
</PaymentElements>
);Disable the Pay button until checkout can confirm the Payment. Show errors beside the form so the customer can correct their card details.
import { usePayment } from '@fragment-dev/payment-elements';
export const PayButton = () => {
const { confirm, canConfirm, isConfirming, isLoading, error } = usePayment();
return <>
<button type="button" disabled={isLoading || !canConfirm || isConfirming}
onClick={() => canConfirm && confirm()}>Pay by card</button>
{error && <p role="alert">{error}</p>}
</>;
};Inspect the allowed entry types
FRAGMENT posts settlement accounting automatically. Your application posts the separate business entry after authorization. Do not submit native settlement entries through addLedgerEntry.
Payment Settled
Records the gross card receipt and processing fee. Reduces the outstanding receivable.
Invoice Approved
Record approved photography invoice. Post this entry once before collecting the invoice.
Walk through the expected balances
These diagrams show a $100 approved invoice followed by its $100 card payment. The assumed $3 fee illustrates accounting; it is not a pricing quote. FRAGMENT supplies the actual fee. Only photography_invoice_approve is posted by your application.
Step 1: invoice-approved
Amount parameters, in cents: amount=10000. Expected outcome: posted.
Expected result: The assets/receivables:1/outstanding balance increases from 0 to 10,000 USD cents. The income balance increases from 0 to 10,000 USD cents.
- assets/payments
- 0
- assets/receivables:1/outstanding
- 10000
- income/photography:1
- 10000
- expenses/payment-fees
- 0
Step 2: payment-settled
Amount parameters, in cents: settled_amount=10000, fragment_fee_amount=300. Expected outcome: posted.
Expected result: The assets/payments balance increases from 0 to 9,700 USD cents. The assets/receivables:1/outstanding balance decreases from 10,000 to 0 USD cents. The expenses/payment-fees balance increases from 0 to 300 USD cents.
- assets/payments
- 9700
- assets/receivables:1/outstanding
- 0
- income/photography:1
- 10000
- expenses/payment-fees
- 300
What happens if the card payment fails?
Store the Payment identifier with the photography invoice and operation key. A client callback alone does not establish settlement. Recover server-side status when the response is lost.
| Situation | Application behavior |
|---|---|
| Card is declined | Keep the receipt unavailable. Let the payer correct checkout or use the documented retryPayment flow. |
| Request times out | Recover the operation with its existing key and exact payload. |
| Payment remains processing | Display its pending state and avoid creating a second collection. |
| Payment settles | Use the automatically posted entries; do not post a second receipt. |
Enforce the application-side input contract
Serialize collection attempts and cap each payment at the approved invoice balance. Payment settlement accounting does not enforce that collection limit. Bind the payer, Ledger, record, and amount on your server. The application contract records these checks.
| Check | Required behavior |
|---|---|
| Ownership | Resolve the record from the authenticated payer's permissions. |
| Amount | Validate positive integer cents against the approved amount. |
| Retry | Reuse each operation's key and exact payload. |
| Concurrent requests | Serialize decisions for the same record and recover in-flight operations. |
Check the real estate business rules
Confirm the delivered package, listing customer, property reference, and accepted invoice total before collecting payment. Keep those decisions in the property photographer's application and pass only approved identifiers and amounts to FRAGMENT.
| Situation | Handling |
|---|---|
| Package upgrades | Approve additional photos, floor plans, or video before increasing the invoice amount. |
| Listing changes | Retain the original property and customer association when a listing reference changes. |
| Service scope | This example tracks photography receipts; it does not model escrow, commissions, or property sale proceeds. |
Test the checkout and accounting flow
Use a sandbox Ledger and the documented test cards. Compare actual entries with the illustrative snapshots. The snapshots do not execute checkout or prove settlement.
- Post the approved invoice once, then collect it through checkout.
- Verify the Payment status, gross receipt, fee expense, and remaining balance.
- Retry with the same key and verify that the operation does not duplicate its financial effect.
- Test a decline, a lost response, and concurrent collection requests.