# API Reference — AnimFX Store

> **Note**: This is primarily a **server-rendered web application** (HTML views).  
> The "API" here refers to **webhook endpoints** for payment providers and **internal AJAX endpoints** for cart operations.

---

## 📋 Base URL

```
https://yourdomain.com
```

All endpoints are relative to the base URL.

---

## 🔐 Authentication

| Endpoint Type | Auth Method |
|---------------|-------------|
| Webhooks (Paddle, Binance, PayPal) | Signature verification (HMAC/Ed25519/PayPal API) |
| Cart AJAX | Session cookie (`ci_session`) + CSRF token |
| Admin pages | Session + `user_role === 'admin'` |

---

## 🛒 Cart Endpoints (AJAX)

### Add to Cart
```
POST /cart/add
Content-Type: application/x-www-form-urlencoded
```

**Parameters:**
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `product_id` | integer | ✅ | Product ID |
| `csrf_test_name` | string | ✅ | CSRF token |

**Response:**
```json
{
  "success": true,
  "cart_count": 3,
  "message": "Product added to cart"
}
```

### Remove from Cart
```
POST /cart/remove
Content-Type: application/x-www-form-urlencoded
```

**Parameters:**
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `product_id` | integer | ✅ | Product ID |
| `csrf_test_name` | string | ✅ | CSRF token |

**Response:**
```json
{
  "success": true,
  "cart_count": 2
}
```

### Clear Cart
```
GET /cart/clear
```

**Response:** Redirect to `/cart` with flash message.

---

## 💳 Payment Webhooks

> **Critical**: These endpoints are called by **payment provider servers**, not browsers.  
> They **must** be accessible via **public HTTPS** with **no authentication**, **no redirects**, **no CSRF**.

### Paddle Webhook
```
POST /payment/webhook/paddle
Content-Type: application/json
Headers:
  P-Signature: <base64_ed25519_signature>
  P-Public-Key: <base64_ed25519_public_key>
```

**Event:** `transaction.completed` (JSON v2)

**Verification:** Ed25519 signature over raw body using `paddle_public_key` setting.

**Success Response:** `200 OK` with body `OK`

**Failure Response:** `400 Bad Request` with body `Invalid webhook`

---

### Binance Pay Webhook
```
POST /payment/webhook/binance
Content-Type: application/json
Headers:
  BinancePay-Timestamp: <milliseconds>
  BinancePay-Nonce: <32_hex_chars>
  BinancePay-Signature: <hmac_sha256_hex>
  BinancePay-Certificate-SN: <merchant_api_key>
  BinancePay-RecvWindow: 30000
```

**Event:** `bizType=PAY` with `status=PAID`

**Verification:** HMAC-SHA256 over `timestamp\nnonce\nbody\n` using `binance_api_secret`.

**Success Response:** `200 OK` with body `OK`

**Failure Response:** `400 Bad Request` with body `Invalid webhook`

---

### PayPal Webhook
```
POST /payment/webhook/paypal
Content-Type: application/json
Headers:
  PAYPAL-AUTH-ALGO: SHA256withRSA
  PAYPAL-CERT-URL: https://api.paypal.com/v1/notifications/cert/CERT-...
  PAYPAL-TRANSMISSION-ID: <uuid>
  PAYPAL-TRANSMISSION-SIG: <base64_signature>
  PAYPAL-TRANSMISSION-TIME: <ISO8601_timestamp>
```

**Event:** `PAYMENT.CAPTURE.COMPLETED`

**Verification:** POST to PayPal `verify-webhook-signature` API with all headers + webhook event JSON.  
Requires `paypal_webhook_id` setting. Cert URL host must be `*.paypal.com`.

**Success Response:** `200 OK` with body `OK`

**Failure Response:** `400 Bad Request` with body `Invalid webhook`

---

## 🔄 PayPal Return URL

### Capture Approved Payment
```
GET /orders/paypal-return/{order_code}?token={paypal_order_id}
```

**Parameters:**
| Name | Type | Description |
|------|------|-------------|
| `order_code` | string | Store order code (e.g., `INV-AB12CD`) |
| `token` | string | PayPal order ID from `approve` link |

**Flow:**
1. User approves payment on PayPal
2. PayPal redirects to this URL with `token`
3. Controller calls `paypalCapture(token)`
4. If successful → `confirmPaypalCapture()` → order marked `paid`
5. Redirect to order detail with success flash

**Error Cases:**
- Missing/invalid `token` → redirect to order with error flash
- Capture failed → redirect with error (contact support)
- Amount/currency mismatch → redirect with error (manual review)
- Already paid → redirect with success flash

---

## 📦 Internal Models (Reference)

### Order Status Values
| Value | Meaning |
|-------|---------|
| `pending` | Created, awaiting payment |
| `paid` | Payment confirmed (auto or manual) |
| `cancelled` | User cancelled / expired |
| `rejected` | Admin rejected manual payment |

### Payment Method Values
| Value | Gateway | Confirmation |
|-------|---------|--------------|
| `bank_transfer` | Manual | Admin confirms via proof upload |
| `paddle` | Paddle Billing | Automatic (webhook) |
| `binance` | Binance Pay | Automatic (webhook) |
| `paypal` | PayPal | Automatic (webhook + return URL) |

---

## 🧪 Testing Webhooks Locally

### ngrok (Exposing Localhost)
```bash
# Start dev server
php spark serve --port 8080

# In another terminal
ngrok http 8080

# Use ngrok HTTPS URL in provider dashboard:
# https://abcd1234.ngrok.io/payment/webhook/paddle
```

### Paddle CLI (Simulate Webhook)
```bash
# Install: https://developer.paddle.com/cli
paddle webhook simulate transaction.completed \
  --url https://yourdomain.com/payment/webhook/paddle \
  --public-key <your_paddle_public_key>
```

### PayPal Sandbox (Simulate)
1. PayPal Developer → Sandbox → Webhooks → **Simulate Event**
2. Select `PAYMENT.CAPTURE.COMPLETED`
3. Enter your webhook URL
4. Check logs: `tail -f writable/logs/log-$(date +%Y-%m-%d).log`

---

## 📊 Rate Limits & Timeouts

| Endpoint | Limit | Timeout |
|----------|-------|---------|
| Cart AJAX | 60 req/min per IP | 10s |
| Webhooks | Provider-dependent | 5s (must respond 200) |
| PayPal Capture | 1 per return | 30s |

---

## 🔧 Webhook Troubleshooting

| Issue | Cause | Fix |
|-------|-------|-----|
| `400 Invalid webhook` (Paddle) | Missing/wrong `P-Signature` or `P-Public-Key` | Check `paddle_public_key` setting matches dashboard |
| `400 Invalid webhook` (Binance) | HMAC mismatch | Verify `binance_api_secret` matches dashboard |
| `400 Invalid webhook` (PayPal) | `verify-webhook-signature` returns FAIL | Check `paypal_webhook_id`, cert URL host, headers |
| Webhook not received | URL not accessible / firewall | Test with `curl -X POST https://domain.com/payment/webhook/...` |
| Duplicate order confirmation | Idempotency check failed | `payment_txn_id` unique constraint + `markOrderPaid()` guard |

---

## 📝 Adding New Webhook Endpoint

1. Add route in `app/Config/Routes.php`:
```php
$routes->post('payment/webhook/newgateway', 'Payment::webhookNewgateway');
```

2. Add method in `app/Controllers/Payment.php`:
```php
public function webhookNewgateway()
{
    $ok = (new PaymentGateway())->handleNewgatewayWebhook($this->request, $this->settings);
    return $this->response->setStatusCode($ok ? 200 : 400)->setBody($ok ? 'OK' : 'Invalid webhook');
}
```

3. Implement in `PaymentGateway`:
```php
public function handleNewgatewayWebhook(IncomingRequest $request, array $s): bool
{
    // 1. Verify signature
    // 2. Parse event
    // 3. Validate amount/currency/order
    // 4. confirmNewgatewayCapture() or markOrderPaid()
    // 5. Return true/false
}
```

4. Add admin settings fields for credentials

---

*Last updated: 2026-09-27*