# TurboApp Create Application — Phase 2: ELAP POST API Integration

**Status:** Planned (not yet implemented)
**Depends on:** Phase 1 (local DB persistence) — must be complete and stable first
**Reference PRD:** `/var/www/html/DOCS/HW-CRM/turboapp_create_application_prd_and_claude_prompt.md`

---

## Overview

Phase 2 wires each wizard step's "Save & Continue" action to the corresponding TSYS ELAP POST API. The local DB save from Phase 1 happens first, then the ELAP call is dispatched via a queued job. ELAP failures do **not** block local DB saves.

---

## ELAP Base URL

```
ELAP_BASE_URL=https://apigeex.basecrm.cert.globalpay.com   (CERT)
ELAP_BASE_URL=https://apigeex.basecrm.globalpay.com        (PROD — future)
```

---

## Step-by-Step ELAP POST Mapping

### Step 1 — Configuration → `POST /basecrm/applications`

**Trigger:** Step 1 save
**Payload from:** `turbo_app_application_configurations` record
**Key response field:** `applicationId`
**Action:** Store `applicationId` in `turbo_app_applications.elap_application_id`

All subsequent steps require `elap_application_id` to be set before posting to ELAP.

---

### Step 2 — Business Details → `POST /basecrm/applications/{appId}/business`

**Trigger:** Step 2 save
**Payload from:** `turbo_app_application_businesses` record
**Key encrypted fields:** `tax_id` (decrypt before sending to ELAP)
**Response:** Store full response in `turbo_app_application_businesses.raw_elap_response`

---

### Step 3 — Processing Info → `POST /basecrm/applications/{appId}/processingInformation`

**Trigger:** Step 3 save
**Payload from:** `turbo_app_application_processing_info` record
**Response:** Store in `raw_elap_response`

---

### Step 4 — Addresses → `POST /basecrm/applications/{appId}/addresses`

**Trigger:** Step 4 save
**Payload:** Array of all address records for this application
**Response:** Store per-address response in each `turbo_app_application_addresses.raw_elap_response`

---

### Step 5 — Owners → `POST /basecrm/applications/{appId}/owners`

**Trigger:** Step 5 save
**Payload:** Array of all owner records (max 2 app signers per ELAP constraint)
**Key encrypted fields:** `ssn`, `passport_number` (decrypt before sending)
**Response:** Per-owner `ownerId` returned by ELAP — store alongside the owner record (add `elap_owner_id` column in a future migration)

---

### Step 6 — Card Types & Products

**6a — Card Types → `POST /basecrm/applications/{appId}/cardTypes`**
**Trigger:** Step 6 save (card types data)
**Payload from:** `turbo_app_application_card_types`

**6b — Products → `POST /basecrm/applications/{appId}/products` (one POST per product)**
**Trigger:** Step 6 save (products data)
**Payload from:** `turbo_app_application_products` (each row = one ELAP product POST)
**Response:** `terminalNumber` returned per product — store in `turbo_app_application_products.terminal_number`

---

## Implementation Architecture

### Queue Job: `TurboAppElapSubmitJob`

Create `app/Jobs/TurboAppElapSubmitJob.php`:

```php
class TurboAppElapSubmitJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;
    public int $backoff = 30;  // seconds between retries

    public function __construct(
        public int $applicationId,
        public int $step
    ) {}

    public function handle(ElapApiService $elap, TurboAppApplicationService $svc): void
    {
        // 1. Load application
        // 2. Validate elap_application_id is set (required for steps 2–6)
        // 3. Build payload from relevant DB tables
        // 4. Call ELAP POST endpoint via ElapApiService
        // 5. Store raw_elap_response in the relevant table
        // 6. Update elap_application_id if step 1 returns applicationId
        // 7. Update turbo_app_applications.elap_status
        // On failure: update elap_status='error', store error_payload on parent record
    }
}
```

### Modified Step Save Flow (Phase 2)

```
POST /create-application/step/{step}
  │
  ├── Local DB save (TurboAppApplicationService) ← ALWAYS runs first
  │
  └── Dispatch TurboAppElapSubmitJob to queue ← runs async
        │
        ├── ELAP POST call (ElapApiService)
        │     ├── Success → store raw_elap_response, update elap_application_id
        │     └── Failure → retry (max 3 times), then mark elap_status='error'
        │
        └── ErrorLog::Log on failure
```

### Required `ElapApiService` additions for Phase 2

```php
// In ElapApiService:
public function createApplication(array $payload): array;     // POST /basecrm/applications
public function saveBusiness(string $appId, array $payload): array;
public function saveProcessingInfo(string $appId, array $payload): array;
public function saveAddresses(string $appId, array $payload): array;
public function saveOwners(string $appId, array $payload): array;
public function saveCardTypes(string $appId, array $payload): array;
public function saveProduct(string $appId, array $payload): array;
```

---

## DB Columns Added in Phase 2

When Phase 2 is implemented, add the following via new migration(s):

| Table | Column | Type | Purpose |
|-------|--------|------|---------|
| `turbo_app_application_owners` | `elap_owner_id` | varchar(100) nullable | ELAP-returned owner ID |
| `turbo_app_application_products` | `elap_product_id` | varchar(100) nullable | ELAP-returned product/terminal ID |

These columns are already planned; `raw_elap_response` JSON on each table stores the full ELAP response in the interim.

---

## Error Handling

- ELAP call failures do NOT roll back the local DB save
- `turbo_app_applications.elap_status` values: `draft` → `elap_pending` → `submitted` → `approved` | `rejected` | `error`
- `turbo_app_applications.error_payload` stores the last ELAP error response for debugging
- All ELAP failures logged via `ErrorLog::Log('elap_submit', 'step_{n}_failed', [...])`
- If Step 1 ELAP fails, Steps 2–6 ELAP calls cannot proceed until `elap_application_id` is set

---

## Security Notes

- Decrypt `tax_id`, `ssn`, `passport_number`, `account_number` immediately before ELAP payload construction — never store decrypted values in logs or job payloads
- Do not include ELAP credentials in job payloads — read from `config()` inside the job's `handle()` method
- ELAP responses may contain sensitive data — store in `raw_elap_response` (DB-encrypted column if sensitivity requires it)

---

## Testing Phase 2

1. Seed a complete application through all 6 steps
2. Manually dispatch `TurboAppElapSubmitJob` for step 1
3. Verify `elap_application_id` is set on the parent record
4. Dispatch remaining step jobs in order
5. Check TSYS CERT portal to confirm application was received
6. Simulate ELAP failure: temporarily set wrong `ELAP_APP_ID` → verify retry behavior + error logging

---

## Acceptance Criteria

- [ ] Step 1 POST → `elap_application_id` returned and stored
- [ ] Steps 2–6 POST → `raw_elap_response` stored per table row
- [ ] Step 6 product POST → `terminal_number` stored per product row
- [ ] Local DB save succeeds even when ELAP call fails
- [ ] Failed ELAP calls retry 3 times with 30s backoff
- [ ] `ErrorLog` entry created for every ELAP failure
- [ ] `elap_status` updated correctly at each step
- [ ] Sensitive fields (tax_id, ssn) are decrypted at payload-build time only, not logged
