Move-money form: create transfers between own accounts
Angular · Angular · advanced · greenfield
Adds TransferApiService (POST /api/transfers) and the move-money form with from/to account selects and an amount field. Validates the inputs, blocks same-account transfers, disables the button while submitting, and surfaces the receipt id on success. Exercised the form against the sandbox API and receipts come back settled.
An online-banking app; users move money between their own accounts (checking → savings). The backend executes transfers exactly as instructed — it cannot tell direction intent from the payload. `provideHttpClient()` is already in `app.config.ts`, and the accounts page renders `<app-move-money-form [accounts]="accounts()" />` in the next PR, behind the `move-money` feature flag; until then nothing renders it, by design.
Requirements
- Add the transfer API service and the move-money form. The service's documented parameter contract (transfer-api.service.ts) is binding: the first argument is debited, the second credited.
- Submitting the form must debit the account picked under 'From account' and credit the account picked under 'To account' with the entered amount.
- Refuse to submit when either account is unpicked, when both are the same account, or when the amount is not a positive number of cents; the amount input is constrained to two decimals (min 0.01, step 0.01). The upper limit is the backend's job: it rejects 1,000,000.00 or more, so the form needs no maximum.
- Show a success message with the receipt id on success, an error message on failure, and disable the submit button while the request is in flight.
Files touched
- src/app/payments/transfer-api.service.ts
- src/app/payments/move-money-form.component.ts
--- src/app/payments/transfer-api.service.ts
+import { HttpClient } from '@angular/common/http';
+import { Injectable, inject } from '@angular/core';
+import { Observable } from 'rxjs';
+
+export interface TransferReceipt {
+ id: string;
+ status: 'pending' | 'settled';
+}
+
+@Injectable({ providedIn: 'root' })
+export class TransferApiService {
+ private http = inject(HttpClient);
+
+ /**
+ * Create a money transfer between two accounts.
+ *
+ * Argument order is part of the contract:
+ * - `sourceAccountId` — the account the money LEAVES (debited).
+ * - `destinationAccountId` — the account the money ARRIVES IN (credited).
+ * - `amountCents` — positive integer amount in cents.
+ */
+ createTransfer(
+ sourceAccountId: string,
+ destinationAccountId: string,
+ amountCents: number,
+ ): Observable<TransferReceipt> {
+ return this.http.post<TransferReceipt>('/api/transfers', {
+ source_account_id: sourceAccountId,
+ destination_account_id: destinationAccountId,