# Finance learner walkthrough — 24 September 2026

Course: [Follow the money](../course-content/appmint-manage-payments-and-payouts.md).

**Local application work completed; external payment rails remain untested.** Normal owner and customer sign-ins were used in the registered recovery training organization, Studio Manager 0.6.2, local API3312. The independent Chromium context did not share another walkthrough's page or session. No production deployment, external provider call, payout, customer charge, refund or bank file was submitted.

## Application defect fixed and retested

An ordinary customer could send `PUT /client/finance/payout-methods/:id` with `{"status":"verified"}` and mark their own unverified destination verified. Reproduced against a fictional, nonpayable PayPal address with zero wallet balance. This was a real HTTP 200 response, not just a source inference.

`appengine/src/finance/finance-client.service.ts` now rejects customer-controlled status changes other than disabling the method. It passes an explicit allowlist of label/default/disable fields to the payout service; the trusted administrative/provider verification path is unchanged.

Validation:

- Five focused tests in `finance-client.service.spec.ts` passed: forged verified/pending/unexpected statuses refused before wallet writes; ordinary metadata updates stay on the authenticated customer's wallet; customer disable remains supported.
- Full `tsconfig.build.json` compilation passed into a separate output directory.
- After the coordinated local API restart, forged verification returned 403; rename/default 200; disable 200 and persisted; reset-to-pending 403.
- Deleted the fictional destination after testing; readback returned zero methods, zero balance and zero payouts. The customer wallet remains a zero-balance record.

Proof: [before fix](assets/finance-local/destination-readback.json), [live retest and cleanup](assets/finance-local/destination-fixed-readback.json).

## Learner exercise followed in the UI

| Exercise | Observed result |
| --- | --- |
| Finance Dashboard | Eight tabs; initial zero wallets and payouts |
| Payout Rules | Manual approval, blank exception, Only When Asked saved; same selections after leaving/reopening |
| Create Wallet | TUTORIAL-LEDGER-01, partner, USD, zero balance, explicit fictional owner marker; saved once |
| Credit | One 5.40 adjustment, TUTORIAL-LEDGER-01; balance 5.40 |
| Reversal | One 5.40 adjustment debit, TUTORIAL-LEDGER-01-REVERSAL; balance 0 |
| Refusal | Further 1.00 debit refused with Insufficient balance; no third transaction |
| Transaction detail/reopen | Both entries persisted; reversal shows5.40→0 and its exact reference/description |
| Payments | Existing earlier manual invoice payment remained unchanged; no provider connected |
| Take Payment | Current Payment request form, amount/payer/purpose/collection options; no submission |
| Verify Payment | Gateway and provider reference inputs; no invented verification attempt |
| Gateway | No transactions found / No payment gateways configured |
| Payouts | Zero payout records |
| ACH Runs | Nothing waiting and No ACH run yet; no file built |

The generic wallet-save operation returned the module to Dashboard after its success notice. The course now tells learners to reopen Wallets in that case. The ledger still has no canonical customer owner; the course deliberately identifies it as a standalone practice record.

Independent [ledger readback](assets/finance-local/ledger-readback.json) confirms wallet `6ab510fbbe10fd42cba8a643`, final balance 0, exactly two completed adjustment rows (R5KJITWXQ7LR, V1G2N916ETU0).

![The two persisted adjustments](assets/finance-local/07-two-transactions.png)

![Rules retained after reopening](assets/finance-local/09-rules-reopened.png)

## Customer setup and ownership checks

Using a controlled Nia customer account with its own normal customer bearer:

- `GET /client/finance/wallet` returned 200 with no wallet before preparation.
- `GET /client/finance/payout-methods` returned an empty array and prepared a customer-owned zero wallet.
- Adding a fictional PayPal destination returned 201/pending; readback matched the submitted label/address.
- `wallet.owner.id` matched the customer profile's `sk`; owner datatype was customer.
- Missing bearer 401, customer requesting staff `/finance/wallets` 403, different controlled customer updating Nia's method 404.
- The other customer's ownership probe also prepared their own empty wallet; it did not alter Nia's method. No monetary entries exist on either customer wallet.

[Authentication readback](assets/finance-local/customer-auth-readback.json). No administrator or application token was needed in the customer client. No credentials appear in the public evidence.

## Course corrections completed

Removed the instruction to find the customer Finance route in source. Added exact `/client/finance` paths, ordinary customer authentication prerequisite/link, request bodies, record identity comparison, array/record response shapes, persisted destination checks and practice cleanup.

Removed the circular provider-preparation handoff. The course now states precisely where the local exercise ends and links the required Stripe sandbox/key/testing and PayPal payout prerequisites. It does not claim Appmint provider setup has been rehearsed.

Replaced the obsolete standalone Take Payment “No payment data” instruction and screenshot with the actual current Payment request UI. Updated the production guide so a future video does not narrate obsolete behavior.

## Remaining acceptance gate

A controlled, authorized provider sandbox is still needed for checkout, gateway charge lookup, refund, payout request/approval/execution and provider outcomes. ACH generation and bank submission/settlement were not exercised. No completion credit is claimed for those tasks. Pending requirements are kept in [the finance learner-review file](../learner-review/appmint-manage-payments-and-payouts.md).

Documentation validation: the Appmint build passed (227 pages); Chromium opened the generated finance course and all 21 referenced image URLs returned HTTP 200. The current Take Payment capture was visually inspected.
