# Charge Lifecycle

### State Machine Flow

```
 PENDING ──(payment seen on-chain)──▶ CONFIRMED 
   │                                       │
   │ (cancel req / expiry)                 │
   └──────────────▶ CANCELLED              │
                                            │
                                            ▼
                              PAYOUT_INITIATED 
                                            │
                                            │ (tx broadcast success)
                                            ▼
                                    PAYOUT_CONFIRMED
                                            │
                                            ▼
                                       COMPLETED
```

***

### 🟢 States Explained

#### 1. **PENDING**

* **How it gets here**:\
  Created via `/charges/createCharge` (awaiting customer to pay sBTC).
* **What happens**:
  * Temp wallet address generated.
  * Charge expires in 15 min (TTL).
* **Exits**:
  * → **CONFIRMED** when payment detected on-chain.
  * → **CANCELLED** if merchant cancels manually OR TTL expires.

> ⚠️ **Note:** Charges expire in 15 minutes (configurable) — once expired, address is no longer valid for payment.

***

#### 2. **CONFIRMED**

* **How it gets here**:\
  `chargeProcessor` sees inbound sBTC transfer into temp wallet.
* **What happens**:
  * Status updated.
  * Webhook fired: `charge.confirmed`. (signed with your `webhookSecret`).
* **Exits**:
  * → **PAYOUT\_INITIATED** when payout flow begins.

***

#### 3. **PAYOUT\_INITIATED**

* **How it gets here**:\
  Processor attempts to sweep funds from temp wallet → merchant’s payout address.
* **What happens**:
  * Broadcasts Stacks tx (`transferSbtc`).
  * Logs `payoutTxId` in DB.
* **Exits**:
  * → **FAILED** if tx broadcast/rejected.
  * → **PAYOUT\_CONFIRMED** if tx accepted.

***

#### 4. **PAYOUT\_CONFIRMED**

* **How it gets here**:\
  Blockchain confirms the payout tx.
* **What happens**:
  * Webhook fired: `charge.completed`.
  * Charge marked stable.
* **Exits**:
  * → **COMPLETED** after final DB + webhook ack.

***

#### 5. **COMPLETED**

* **Final State.**
* Charge successfully processed + payout delivered.
* No further transitions.

> ⚠️ **Note:** Duplicate webhook events may still arrive (idempotency required).

***

#### 6. **CANCELLED**

* **How it gets here**:
  * Merchant cancels via `/charges/:id/cancel`.
  * Or TTL expires before payment.
* **Terminal state.**

> ⚠️ **Note:** If a customer later tries to pay, funds will still land in the temp wallet but won’t trigger processing.

***

#### 7. **FAILED**

* **How it gets here**:
  * Payout tx couldn’t be broadcast.
  * Funds stuck/unrecoverable.
* **Possible recovery**:
  * `recoverStuckCharges` tries to retry/recover.
* **Terminal (unless manually retried).**

***

### 📧 Webhook Trigger

* **COMPLETED** → `charge.completed`

```
{
"type": "charge.completed",
"eventId": "8a1e20b2-...:payout_completed",
"occurredAt": "2025-08-18T17:45:00Z",
"data": {
"chargeId": "8a1e20b2-...",
"amount": 0.002,
"payoutTxId": "0xabc123...",
"status": "COMPLETED"
}


```

* **EventId** is globally unique → use it for **idempotency** on your side.
* **Signature:** Each payload is signed with your `webhookSecret` (`X-SBTC-Signature`).
* **Retries:** Up to **3 attempts**, exponential backoff (2s → 4s → 6s).

***

**⚠️ Common Pitfalls**

* Customer pays after TTL expired → funds land, but charge stays `CANCELLED`.
* Merchant ignores retries → may miss `charge.completed` ack.
* Duplicate webhook delivery → must dedupe by `eventId`.
* Merchant server not handling raw body in HMAC check → signature fails.

***


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://spay.gitbook.io/spay-docs/integrations/charge-lifecycle.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
