# Public Payment APIs

This document outlines the public payment APIs available in the payment service. These endpoints do not require authentication and are designed for external integrations.

## Base URL

```
http://localhost:8080/api/v1
```

## Endpoints

### 1. Initiate Payment

Initiates an M-Pesa STK Push payment request.

**Endpoint:**
```
POST /payments/initiate
```

**Method:** POST

**Access:** Public (No authentication required)

**Request Body:**
```json
{
  "client_id": "550e8400-e29b-41d4-a716-446655440000",
  "package_id": "660e8400-e29b-41d4-a716-446655440001",
  "phone_number": "+254712345678",
  "description": "Internet package - Professional Plan"
}
```

**Request Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `client_id` | UUID | Yes | The unique identifier of the client |
| `package_id` | UUID | Yes | The unique identifier of the internet package |
| `phone_number` | String | Yes | The phone number to send M-Pesa STK prompt to (format: +254xxxxxxxxx) |
| `description` | String | Yes | Payment description/notes |

**Success Response (200 OK):**
```json
{
  "transaction_id": "770e8400-e29b-41d4-a716-446655440002",
  "checkout_id": "abc123xyz456",
  "message": "Payment initiated successfully",
  "status": "pending"
}
```

**Error Responses:**

**400 Bad Request** - Missing or invalid fields:
```json
{
  "error": "Missing required fields: client_id, package_id, description"
}
```

**400 Bad Request** - Invalid UUID format:
```json
{
  "error": "Invalid client_id format"
}
```

**403 Forbidden** - Invalid or inactive client:
```json
{
  "error": "Invalid or inactive client"
}
```

**500 Internal Server Error** - Payment initiation failed:
```json
{
  "error": "failed to initiate STK push: [error details]"
}
```

---

### 2. Check Payment Status

Checks the status of a previously initiated payment.

**Endpoint:**
```
POST /payments/status
```

**Method:** POST

**Access:** Public (No authentication required)

**Request Body:**
```json
{
  "client_id": "550e8400-e29b-41d4-a716-446655440000",
  "checkout_id": "abc123xyz456"
}
```

**Request Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `client_id` | UUID | Yes | The unique identifier of the client |
| `checkout_id` | String | Yes | The checkout ID returned from initiate payment |

**Success Response (200 OK):**
```json
{
  "transaction_id": "770e8400-e29b-41d4-a716-446655440002",
  "checkout_id": "abc123xyz456",
  "status": "completed",
  "amount": 1500,
  "reference": "PKG-20260128150605-abc123",
  "phone_number": "+254712345678"
}
```

**Status Values:**
- `pending` - Payment initiated, awaiting M-Pesa confirmation
- `completed` - Payment successful (Safaricom confirmed)
- `failed` - Payment failed
- `cancelled` - Payment was cancelled by user

**Error Responses:**

**400 Bad Request** - Missing required fields:
```json
{
  "error": "Missing required fields: client_id, checkout_id"
}
```

**400 Bad Request** - Invalid UUID format:
```json
{
  "error": "Invalid client_id format"
}
```

**403 Forbidden** - Invalid or inactive client:
```json
{
  "error": "Invalid or inactive client"
}
```

**404 Not Found** - Payment not found:
```json
{
  "error": "Payment not found"
}
```

---

## Usage Examples

### Example 1: Using cURL

**Initiate Payment:**
```bash
curl -X POST http://localhost:8080/api/v1/payments/initiate \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "550e8400-e29b-41d4-a716-446655440000",
    "package_id": "660e8400-e29b-41d4-a716-446655440001",
    "phone_number": "+254712345678",
    "description": "Internet package - Professional Plan"
  }'
```

**Check Payment Status:**
```bash
curl -X POST http://localhost:8080/api/v1/payments/status \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "550e8400-e29b-41d4-a716-446655440000",
    "checkout_id": "abc123xyz456"
  }'
```

### Example 2: Using JavaScript/Node.js

```javascript
// Initiate Payment
async function initiatePayment(clientId, packageId, phoneNumber, description) {
  const response = await fetch('http://localhost:8080/api/v1/payments/initiate', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      client_id: clientId,
      package_id: packageId,
      phone_number: phoneNumber,
      description: description
    })
  });
  
  if (!response.ok) {
    throw new Error(`Payment initiation failed: ${response.statusText}`);
  }
  
  return await response.json();
}

// Check Payment Status
async function checkPaymentStatus(clientId, checkoutId) {
  const response = await fetch('http://localhost:8080/api/v1/payments/status', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      client_id: clientId,
      checkout_id: checkoutId
    })
  });
  
  if (!response.ok) {
    throw new Error(`Status check failed: ${response.statusText}`);
  }
  
  return await response.json();
}

// Usage
const clientId = '550e8400-e29b-41d4-a716-446655440000';
const packageId = '660e8400-e29b-41d4-a716-446655440001';
const phoneNumber = '+254712345678';

const paymentResult = await initiatePayment(clientId, packageId, phoneNumber, 'Internet Package Payment');
console.log('Payment initiated:', paymentResult);

// Check status after some time
setTimeout(async () => {
  const status = await checkPaymentStatus(clientId, paymentResult.checkout_id);
  console.log('Payment status:', status);
}, 5000);
```

### Example 3: Using Python

```python
import requests
import json

BASE_URL = 'http://localhost:8080/api/v1'

def initiate_payment(client_id, package_id, phone_number, description):
    """Initiate a payment"""
    url = f'{BASE_URL}/payments/initiate'
    
    payload = {
        'client_id': client_id,
        'package_id': package_id,
        'phone_number': phone_number,
        'description': description
    }
    
    response = requests.post(url, json=payload)
    response.raise_for_status()
    
    return response.json()

def check_payment_status(client_id, checkout_id):
    """Check payment status"""
    url = f'{BASE_URL}/payments/status'
    
    payload = {
        'client_id': client_id,
        'checkout_id': checkout_id
    }
    
    response = requests.post(url, json=payload)
    response.raise_for_status()
    
    return response.json()

# Usage
if __name__ == '__main__':
    client_id = '550e8400-e29b-41d4-a716-446655440000'
    package_id = '660e8400-e29b-41d4-a716-446655440001'
    phone_number = '+254712345678'
    
    # Initiate payment
    payment = initiate_payment(client_id, package_id, phone_number, 'Internet Package Payment')
    print('Payment initiated:', json.dumps(payment, indent=2))
    
    # Check status
    import time
    time.sleep(5)
    
    status = check_payment_status(client_id, payment['checkout_id'])
    print('Payment status:', json.dumps(status, indent=2))
```

---

## Important Notes

### Phone Number
- Must be provided in the request payload
- Required format: `+254xxxxxxxxx` (Kenya format)
- This is the phone number that will receive the M-Pesa STK prompt
- System will validate and format the phone number automatically

### Payment Status Query
- Status is queried **directly from Safaricom** (not from local database)
- Provides real-time payment status confirmation
- Local transaction record is updated with Safaricom's result
- Three result codes from Safaricom:
  - `0` - Success (mapped to `completed`)
  - `1032` - Cancelled by user (mapped to `cancelled`)
  - Other codes - Failed (mapped to `failed`)

### Transaction Flow
1. **Initiate** → Client initiates payment with package details
2. **STK Prompt** → M-Pesa STK Push prompt appears on customer's phone
3. **Enter PIN** → Customer enters their M-Pesa PIN
4. **Callback** → M-Pesa sends callback with payment result
5. **Status Check** → Use checkout_id to query payment status

### Rate Limiting
- These endpoints are subject to rate limiting
- Default: 100 requests per minute per client

### CORS
- These endpoints support CORS for cross-origin requests
- Safe to call from web browsers

### Callback Processing
- M-Pesa callbacks are processed asynchronously
- Payment status may take a few seconds to update after M-Pesa callback
- Always check status using the `/payments/status` endpoint

---

## Testing

### Using Postman

1. **Create a new request** → POST
2. **URL:** `http://localhost:8080/api/v1/payments/initiate`
3. **Headers:** `Content-Type: application/json`
4. **Body:**
```json
{
  "client_id": "550e8400-e29b-41d4-a716-446655440000",
  "package_id": "660e8400-e29b-41d4-a716-446655440001",
  "description": "Test Payment"
}
```

---

## Error Handling Best Practices

```javascript
// Always handle errors gracefully
async function initiatePaymentSafely(clientId, packageId, description) {
  try {
    const response = await fetch('http://localhost:8080/api/v1/payments/initiate', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ client_id: clientId, package_id: packageId, description })
    });
    
    const data = await response.json();
    
    if (!response.ok) {
      console.error('Payment initiation error:', data.error);
      // Handle error based on response.status
      return null;
    }
    
    return data;
  } catch (error) {
    console.error('Network error:', error);
    return null;
  }
}
```
