# Delivery completion must credit its driver's wallet

24 September 2026. This is an actual local application rehearsal with fictional delivery records, not evidence of a physical delivery, paid customer order or external transfer.

## Reproduced failure

A fresh customer signed up through the normal customer endpoint, registered through `POST /client/logistics/register`, and was approved by the owner through `PUT /logistics/delivery/agents/:agentId/approve`. The owner created a manually priced training job (customer price 9.00, driver pay 6.00), assigned this driver, and the driver's own authenticated endpoints completed pickup and dropoff in order. No source wallet credit endpoint was used.

`PUT /client/logistics/jobs/:jobId/complete` returned a completed job, but the driver had no wallet and no earning transaction. The log recorded `TypeError: Cannot read properties of undefined (reading 'toString')` inside the caught wallet-credit block. Persisted job earnings remained pending.

| Record | Identifier |
| --- | --- |
| Fresh driver customer and linked agent | `6ab57885719102fa54fc875d` |
| Job | `6ab57885719102fa54fc8762` |
| Job number | `V9L8YAVMR1` |
| Expected job-generated earning | 6.00, category `earning`, reference `V9L8YAVMR1` |

Actual progression: `en_route_pickup` → `arrived_pickup` → `picked_up` → `en_route_dropoff` → `arrived_dropoff` → `delivered` → incorrectly `completed` without a wallet credit. Stops explicitly said they were fictional local rehearsal locations. No geocoding, physical proof or external recipient contact was claimed. Notifications use the verified organization-local SMTP catcher.

## Repair

Mongo repository updates remove `sk` from the object passed to them. Completion updated the agent, then reused `agent.sk` to resolve its customer. The repair resolves and retains the driver's identity before writes. It propagates missing linkage and credit errors instead of claiming success, and passes computed earning/pricing data into the final persisted job update.

Completion now requires a delivered job, or an older completed job whose earning needs recovery. A completed job with persisted credited earnings returns without another credit. Concurrent requests within this service instance share one in-flight completion; retry logic checks the persisted wallet transaction's job reference and category before creating another credit. Conflicting or duplicated existing entries require review instead of another credit. A recovered legacy completion does not increment driver performance a second time. This is local retry protection; distributed simultaneous workers and database-crash atomicity have not been claimed as verified.

Seven actual-service regressions passed: identity-stripping repository update, repeat/concurrent local completion, failed-credit recovery, legacy completed/pending recovery, existing-ledger retry, refusing undelivered work, and refusing missing customer linkage. [Test output](assets/finance-local/delivery-earning-tests.txt). Full application TypeScript validation passed. The compiled service was installed and verified after the coordinated local API restart.

## Actual recovery and fresh completion passed

The original driver's normal completion endpoint recovered job `V9L8YAVMR1`. Persisted earnings became `credited`, and canonical wallet `6ab57a1aa6d48d3a4b227550` contained one 6.00 earning. Repeating completion left the balance at 6.00 without another transaction.

A second fresh job, `CB9N2IKCXN` (`6ab57a30a6d48d3a4b227556`), then followed the complete driver lifecycle after the fix. It produced its own 6.00 earning. The wallet now holds 12.00 with exactly two completed `credit` transactions, category `earning`, separately referencing those two job numbers. Base earnings are 12.00, adjustments are zero, reserve is zero and lifetime payout debits are zero. No wallet-credit endpoint was called for this driver.

Actual Studio verification: **Finance → Wallets → driver.earning.20260924@example.invalid** opened **Wallet Details** with available balance 12.00. Expanding **Earnings Breakdown** showed **Base Earnings 12.00** and **Adjustments 0.00**. Expanding **Transactions** showed the two **Earning** entries, **Delivery job V9L8YAVMR1** and **Delivery job CB9N2IKCXN**, each +6.00. This proves the software's earning workflow, not physical delivery or receipt of customer payment.

![Actual earned wallet and its earning breakdown](assets/finance-local/delivery-earned-wallet.png)

![Actual job-referenced earning transactions](assets/finance-local/delivery-earned-transactions.png)

[Sanitized earning evidence](assets/finance-local/delivery-earned-proof.json). Existing ACH training fixtures were preserved separately.

## Executed developer sequence

Use the organization `orgid` header on every request. Sign up a separate training customer normally and retain its returned token privately. Owner requests below use the owner's bearer; driver requests use that customer's bearer. The rehearsal's organization routes notification email exclusively to its local SMTP catcher.

1. Driver: `POST /client/logistics/register` with `{"vehicleType":"car","vehicleMake":"Training","vehicleModel":"Local Fixture","licensePlate":"TRAINING-NO-ROAD"}`. Save the returned agent `sk`. This registration requires owner approval; do not change status through generic record editing.
2. Owner: `PUT /logistics/delivery/agents/<agent id>/approve` with `{"notes":"Local fictional workflow review only; no real driver onboarding asserted"}`.
3. Owner: `POST /logistics/delivery/jobs` using the explicit manual-pricing fixture below. Save the returned job `sk` and job number. No quote-provider call or customer payment was performed.

```json
{
  "requireSystemQuote": false,
  "customer": {"firstName":"Fictional","lastName":"Training Sender"},
  "stops": [
    {"type":"pickup","location":{"address":"TRAINING PICKUP — no physical collection"},"instructions":"Local application lifecycle test; no actual goods"},
    {"type":"dropoff","location":{"address":"TRAINING DROPOFF — no physical delivery"},"instructions":"Local application lifecycle test; no actual recipient"}
  ],
  "pricing": {"customerPays":9,"driverPays":6,"distance":0},
  "notes":"Fictional local delivery rehearsal — no physical delivery or customer charge"
}
```

4. Owner: `PUT /logistics/delivery/jobs/<job id>/assign` with `{"agentId":"<returned agent id>"}`. This is an explicit assignment; no marketplace broadcast is needed.
5. Driver: send these `PUT /client/logistics/jobs/<job id>/<action>` calls in order, checking the returned status each time. Do not skip to completion.

| Action | JSON body | Observed status |
| --- | --- | --- |
| `start-pickup` | `{}` | `en_route_pickup` |
| `arrive-pickup` | `{"stopIndex":0}` | `arrived_pickup` |
| `complete-pickup` | `{"stopIndex":0,"proof":{"notes":"Fictional local rehearsal; no physical pickup"}}` | `picked_up` |
| `start-dropoff` | `{}` | `en_route_dropoff` |
| `arrive-dropoff` | `{"stopIndex":1}` | `arrived_dropoff` |
| `complete-dropoff` | `{"stopIndex":1,"proof":{"notes":"Fictional local rehearsal; no physical delivery"}}` | `delivered` |
| `complete` | `{}` | `completed`, persisted earnings `credited` |

6. Driver: `GET /client/finance/wallet`. Confirm `wallet.owner.id` matches the signed-in customer's ID, `transactions` contains an `earning` credit for this job number, and available balance increased by the driver pay. Owner: open the same wallet in Studio to inspect the earning breakdown and transaction. A quoted driver amount or completed job without the corresponding transaction is insufficient.

## Additional summary defect found during real UI verification

The earlier ACH file remained built, not submitted, with 5.40 reserved. Nevertheless its wallet row displayed **5.40 paid**. `getWalletPayoutSummary()` incorrectly included `processing` with `completed` in paid totals. The fix keeps processing in pending totals and counts only completed payouts as paid. Two service regressions passed, including mixed completed/processing/approved/pending/cancelled/failed records. The compiled correction was loaded during a coordinated local API restart. The actual summary endpoint returned paid 0.00, pending 5.40, and Studio's wallet row now displays **$0.00 paid · $5.40 pending**. [Actual endpoint proof](assets/finance-local/processing-payout-summary-proof.json).

![Actual corrected pending-versus-paid wallet summary](assets/finance-local/processing-payout-summary-fixed.png)

Two additional isolated service checks passed: a deliberately failed test rail releases its reserve without increasing balance or creating a debit, and an isolated completion creates one debit then refuses another completion. These tests used in-memory records and a stubbed rail; they are not PayPal acceptance or live settlement evidence. [All four summary/lifecycle test results](assets/finance-local/payout-summary-tests.txt). The live ACH batch remains built/not submitted; no completion, failure, return or settlement was fabricated for it.

Current API session: `81597`. Private job replay state is in `~/.local/state/appmint-learner-review/delivery-earning-review.json` and `delivery-earning-fresh-review.json`. Do not repeat either lifecycle or create manual credits. Both completed jobs already have their own ledger entry.

## PayPal readiness inspected without provider calls

No organization-local PayPal configuration was found. The shared active configuration is marked sandbox and has `clientId`, but lacks the `appSecret` required by the inspected provider. No controlled sandbox payout recipient is known. No PayPal request was sent, no payout was requested and no credential was printed.

[Organization metadata](assets/finance-local/paypal-local-readiness.json), [shared configuration metadata](assets/finance-local/paypal-shared-readiness.json). Required next inputs for provider acceptance are a working sandbox secret and a controlled sandbox recipient; Stripe checkout credentials cannot execute this payout rail.
