708 lines
16 KiB
Markdown
708 lines
16 KiB
Markdown
# Payment Service API Documentation
|
|
|
|
**Base URL:** `/api/payment` (via gateway) or `http://localhost:8084` (direct)
|
|
|
|
**Service Port:** 8084
|
|
|
|
## Overview
|
|
|
|
The Payment Service handles all financial operations for the Coppertone.tech platform, including:
|
|
|
|
- Invoice management (create, read, update, delete)
|
|
- Payment processing via Stripe
|
|
- Multi-modal payment support (credit card, blockchain)
|
|
- Webhook handling for payment status updates
|
|
- IPFS document integration for invoice storage
|
|
|
|
## Authentication
|
|
|
|
All endpoints (except webhooks) require a valid JWT token:
|
|
|
|
```
|
|
Authorization: Bearer <jwt_token>
|
|
```
|
|
|
|
## Role-Based Access
|
|
|
|
| Role | Invoices | Payments | Payment Intent |
|
|
|------|----------|----------|----------------|
|
|
| `SUPERUSER` | Full access | Full access | Can create for any invoice |
|
|
| `ADMIN` | Full access | Full access | Can create for any invoice |
|
|
| `STAFF` | Full access | Full access | Can create for any invoice |
|
|
| `CLIENT` | Own invoices only | Own payments only | Own invoices only |
|
|
|
|
**Note:** `SUPERUSER` role automatically grants full access to all endpoints.
|
|
|
|
---
|
|
|
|
## Health Check
|
|
|
|
```
|
|
GET /healthz
|
|
```
|
|
|
|
**Response:**
|
|
```
|
|
200 OK
|
|
ok
|
|
```
|
|
|
|
---
|
|
|
|
## Invoices
|
|
|
|
### Invoice Statuses
|
|
|
|
| Status | Description |
|
|
|--------|-------------|
|
|
| `DRAFT` | Invoice created but not yet sent |
|
|
| `SENT` | Invoice sent to client |
|
|
| `PENDING` | Awaiting payment |
|
|
| `PAID` | Payment received |
|
|
| `OVERDUE` | Payment past due date |
|
|
| `CANCELLED` | Invoice cancelled |
|
|
|
|
### List Invoices
|
|
|
|
Retrieve invoices. CLIENTs see only their own invoices.
|
|
|
|
```
|
|
GET /invoices
|
|
GET /invoices?client_id=5
|
|
```
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Required Role:** Any authenticated user
|
|
|
|
**Query Parameters:**
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `client_id` | integer | Optional (STAFF/ADMIN only). Filter by client |
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
[
|
|
{
|
|
"id": 1,
|
|
"invoiceNumber": "INV-2024-001",
|
|
"projectId": 1,
|
|
"clientId": 5,
|
|
"amount": 5000.00,
|
|
"currency": "USD",
|
|
"status": "PENDING",
|
|
"dueDate": "2024-02-15",
|
|
"issuedDate": "2024-01-15",
|
|
"paidDate": null,
|
|
"blockchainTxHash": null,
|
|
"ipfsDocumentCid": "QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco",
|
|
"notes": "Website redesign - Phase 1",
|
|
"createdAt": "2024-01-15T10:00:00Z",
|
|
"updatedAt": "2024-01-15T10:00:00Z"
|
|
}
|
|
]
|
|
```
|
|
|
|
**Authorization Logic:**
|
|
- `STAFF/ADMIN/SUPERUSER`: See all invoices (optionally filtered by client_id)
|
|
- `CLIENT`: See only invoices where `client_id` matches their user ID
|
|
|
|
---
|
|
|
|
### Get Invoice by ID
|
|
|
|
Retrieve a single invoice.
|
|
|
|
```
|
|
GET /invoices/{id}
|
|
```
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Required Role:** Invoice owner (client_id) or `STAFF/ADMIN/SUPERUSER`
|
|
|
|
**Path Parameters:**
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `id` | integer | Invoice ID |
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"id": 1,
|
|
"invoiceNumber": "INV-2024-001",
|
|
"projectId": 1,
|
|
"clientId": 5,
|
|
"amount": 5000.00,
|
|
"currency": "USD",
|
|
"status": "PENDING",
|
|
"dueDate": "2024-02-15",
|
|
"issuedDate": "2024-01-15",
|
|
"paidDate": null,
|
|
"blockchainTxHash": null,
|
|
"ipfsDocumentCid": "QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco",
|
|
"notes": "Website redesign - Phase 1",
|
|
"createdAt": "2024-01-15T10:00:00Z",
|
|
"updatedAt": "2024-01-15T10:00:00Z"
|
|
}
|
|
```
|
|
|
|
**Error Responses:**
|
|
|
|
| Status | Condition | Response |
|
|
|--------|-----------|----------|
|
|
| 400 | Invalid ID | `Invalid invoice ID` |
|
|
| 403 | No access | `Forbidden: you do not have access to this invoice` |
|
|
| 404 | Not found | `Invoice not found` |
|
|
|
|
---
|
|
|
|
### Create Invoice
|
|
|
|
Create a new invoice.
|
|
|
|
```
|
|
POST /invoices
|
|
```
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Required Role:** `STAFF`, `ADMIN`, or `SUPERUSER`
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"invoiceNumber": "INV-2024-002",
|
|
"projectId": 1,
|
|
"clientId": 5,
|
|
"amount": 2500.00,
|
|
"currency": "USD",
|
|
"status": "DRAFT",
|
|
"dueDate": "2024-03-01",
|
|
"issuedDate": "2024-02-01",
|
|
"ipfsDocumentCid": "QmNewCid...",
|
|
"notes": "Monthly retainer - February 2024"
|
|
}
|
|
```
|
|
|
|
| Field | Type | Required | Default | Description |
|
|
|-------|------|----------|---------|-------------|
|
|
| `invoiceNumber` | string | Yes | - | Unique invoice number |
|
|
| `clientId` | integer | Yes | - | Client user ID |
|
|
| `amount` | float | Yes | - | Invoice amount (must be > 0) |
|
|
| `projectId` | integer | No | - | Associated project ID |
|
|
| `currency` | string | No | `USD` | Currency code (ISO 4217) |
|
|
| `status` | string | No | `DRAFT` | Invoice status |
|
|
| `dueDate` | string | No | - | Due date (YYYY-MM-DD) |
|
|
| `issuedDate` | string | No | - | Issue date (YYYY-MM-DD) |
|
|
| `ipfsDocumentCid` | string | No | - | IPFS CID for invoice document |
|
|
| `notes` | string | No | - | Additional notes |
|
|
|
|
**Response (201 Created):**
|
|
```json
|
|
{
|
|
"id": 2,
|
|
"invoiceNumber": "INV-2024-002",
|
|
"clientId": 5,
|
|
"amount": 2500.00,
|
|
"currency": "USD",
|
|
"status": "DRAFT",
|
|
...
|
|
}
|
|
```
|
|
|
|
**Error Responses:**
|
|
|
|
| Status | Condition | Response |
|
|
|--------|-----------|----------|
|
|
| 400 | Missing fields | `Invoice number, client_id, and amount are required` |
|
|
| 403 | Insufficient role | `Insufficient permissions` |
|
|
| 409 | Duplicate number | `Invoice number already exists` |
|
|
|
|
---
|
|
|
|
### Update Invoice
|
|
|
|
Update an existing invoice.
|
|
|
|
```
|
|
PUT /invoices/{id}
|
|
```
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Required Role:** `STAFF`, `ADMIN`, or `SUPERUSER`
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"invoiceNumber": "INV-2024-002",
|
|
"projectId": 1,
|
|
"clientId": 5,
|
|
"amount": 2750.00,
|
|
"currency": "USD",
|
|
"status": "SENT",
|
|
"dueDate": "2024-03-01",
|
|
"issuedDate": "2024-02-01",
|
|
"paidDate": null,
|
|
"blockchainTxHash": null,
|
|
"ipfsDocumentCid": "QmUpdatedCid...",
|
|
"notes": "Updated notes"
|
|
}
|
|
```
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"id": 2,
|
|
"invoiceNumber": "INV-2024-002",
|
|
...
|
|
}
|
|
```
|
|
|
|
**Error Responses:**
|
|
|
|
| Status | Condition | Response |
|
|
|--------|-----------|----------|
|
|
| 403 | Insufficient role | `Insufficient permissions` |
|
|
| 403 | Paid invoice | `Cannot modify a paid invoice` |
|
|
| 404 | Not found | `Invoice not found` |
|
|
|
|
**Business Rules:**
|
|
- **PAID invoices cannot be modified** - This protects financial audit trails
|
|
- Changes to paid invoices require issuing a credit note or new invoice
|
|
|
|
---
|
|
|
|
### Delete Invoice
|
|
|
|
Delete an invoice.
|
|
|
|
```
|
|
DELETE /invoices/{id}
|
|
```
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Required Role:** `STAFF`, `ADMIN`, or `SUPERUSER`
|
|
|
|
**Response (204 No Content)**
|
|
|
|
**Error Responses:**
|
|
|
|
| Status | Condition | Response |
|
|
|--------|-----------|----------|
|
|
| 403 | Insufficient role | `Insufficient permissions` |
|
|
| 403 | Paid invoice | `Cannot delete a paid invoice - it must be kept for audit purposes` |
|
|
| 404 | Not found | `Invoice not found` |
|
|
|
|
**Business Rules:**
|
|
- **PAID invoices cannot be deleted** - Required for financial audit compliance
|
|
- To "cancel" a paid invoice, issue a credit note instead
|
|
|
|
---
|
|
|
|
## Payments
|
|
|
|
### Payment Statuses
|
|
|
|
| Status | Description |
|
|
|--------|-------------|
|
|
| `PENDING` | Payment initiated, awaiting processing |
|
|
| `PROCESSING` | Payment being processed by gateway |
|
|
| `COMPLETED` | Payment successfully completed |
|
|
| `FAILED` | Payment failed |
|
|
| `REFUNDED` | Payment refunded |
|
|
| `CANCELLED` | Payment cancelled |
|
|
|
|
### Payment Methods
|
|
|
|
| Method | Description |
|
|
|--------|-------------|
|
|
| `CREDIT_CARD` | Credit/debit card via Stripe |
|
|
| `BANK_TRANSFER` | Direct bank transfer |
|
|
| `CRYPTO_ETH` | Ethereum cryptocurrency |
|
|
| `CRYPTO_BTC` | Bitcoin cryptocurrency |
|
|
| `CRYPTO_USDC` | USDC stablecoin |
|
|
|
|
### List Payments
|
|
|
|
Retrieve payments. CLIENTs see only payments for their invoices.
|
|
|
|
```
|
|
GET /payments
|
|
GET /payments?invoice_id=1
|
|
```
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Required Role:** Any authenticated user
|
|
|
|
**Query Parameters:**
|
|
| Parameter | Type | Description |
|
|
|-----------|------|-------------|
|
|
| `invoice_id` | integer | Optional. Filter by invoice |
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
[
|
|
{
|
|
"id": 1,
|
|
"invoiceId": 1,
|
|
"amount": 5000.00,
|
|
"currency": "USD",
|
|
"paymentMethod": "CREDIT_CARD",
|
|
"status": "COMPLETED",
|
|
"transactionId": "pi_3PGV9nLkdIwHu7ix0abcdefg",
|
|
"blockchainTxHash": null,
|
|
"blockchainNetwork": null,
|
|
"paymentProcessor": "stripe",
|
|
"processorFee": 145.00,
|
|
"processedAt": "2024-01-20T14:30:00Z",
|
|
"createdAt": "2024-01-20T14:25:00Z",
|
|
"updatedAt": "2024-01-20T14:30:00Z"
|
|
}
|
|
]
|
|
```
|
|
|
|
**Authorization Logic:**
|
|
- `STAFF/ADMIN/SUPERUSER`: See all payments (optionally filtered)
|
|
- `CLIENT`: See only payments for invoices where they are the client
|
|
|
|
---
|
|
|
|
### Get Payment by ID
|
|
|
|
Retrieve a single payment.
|
|
|
|
```
|
|
GET /payments/{id}
|
|
```
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Required Role:** Invoice owner or `STAFF/ADMIN/SUPERUSER`
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"id": 1,
|
|
"invoiceId": 1,
|
|
"amount": 5000.00,
|
|
"currency": "USD",
|
|
"paymentMethod": "CREDIT_CARD",
|
|
"status": "COMPLETED",
|
|
"transactionId": "pi_3PGV9nLkdIwHu7ix0abcdefg",
|
|
"blockchainTxHash": null,
|
|
"blockchainNetwork": null,
|
|
"paymentProcessor": "stripe",
|
|
"processorFee": 145.00,
|
|
"processedAt": "2024-01-20T14:30:00Z",
|
|
"createdAt": "2024-01-20T14:25:00Z",
|
|
"updatedAt": "2024-01-20T14:30:00Z"
|
|
}
|
|
```
|
|
|
|
**Error Responses:**
|
|
|
|
| Status | Condition | Response |
|
|
|--------|-----------|----------|
|
|
| 403 | No access | `Forbidden: you do not have access to this payment` |
|
|
| 404 | Not found | `Payment not found` |
|
|
|
|
---
|
|
|
|
### Create Payment (Manual)
|
|
|
|
Create a manual payment record (for bank transfers, crypto, etc.).
|
|
|
|
```
|
|
POST /payments
|
|
```
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Required Role:** `STAFF`, `ADMIN`, or `SUPERUSER`
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"invoiceId": 1,
|
|
"amount": 5000.00,
|
|
"currency": "USD",
|
|
"paymentMethod": "BANK_TRANSFER",
|
|
"status": "PENDING",
|
|
"transactionId": "WIRE-2024-001",
|
|
"blockchainTxHash": null,
|
|
"blockchainNetwork": null,
|
|
"paymentProcessor": null,
|
|
"processorFee": null
|
|
}
|
|
```
|
|
|
|
| Field | Type | Required | Default | Description |
|
|
|-------|------|----------|---------|-------------|
|
|
| `invoiceId` | integer | Yes | - | Invoice being paid |
|
|
| `amount` | float | Yes | - | Payment amount (must be > 0) |
|
|
| `currency` | string | No | `USD` | Currency code |
|
|
| `paymentMethod` | string | No | - | Payment method |
|
|
| `status` | string | No | `PENDING` | Payment status |
|
|
| `transactionId` | string | No | - | External transaction ID |
|
|
| `blockchainTxHash` | string | No | - | Blockchain transaction hash |
|
|
| `blockchainNetwork` | string | No | - | Blockchain network (ethereum, bitcoin, etc.) |
|
|
| `paymentProcessor` | string | No | - | Payment processor name |
|
|
| `processorFee` | float | No | - | Processor fee amount |
|
|
|
|
**Response (201 Created):**
|
|
```json
|
|
{
|
|
"id": 2,
|
|
"invoiceId": 1,
|
|
"amount": 5000.00,
|
|
...
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Stripe Integration
|
|
|
|
### Create Payment Intent
|
|
|
|
Create a Stripe payment intent for card payments.
|
|
|
|
```
|
|
POST /invoices/create-payment-intent
|
|
```
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
**Required Role:** Invoice owner (client_id) or `STAFF/ADMIN/SUPERUSER`
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"invoiceId": 1
|
|
}
|
|
```
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| `invoiceId` | integer | Yes | Invoice to pay |
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"clientSecret": "pi_3PGV9nLkdIwHu7ix_secret_abc123...",
|
|
"paymentId": 3
|
|
}
|
|
```
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `clientSecret` | string | Stripe client secret for frontend payment form |
|
|
| `paymentId` | integer | Database payment record ID |
|
|
|
|
**Error Responses:**
|
|
|
|
| Status | Condition | Response |
|
|
|--------|-----------|----------|
|
|
| 403 | Not invoice owner | `Forbidden: you can only pay your own invoices` |
|
|
| 404 | Invoice not found | `Invoice not found` |
|
|
| 503 | Stripe not configured | `Stripe not configured` |
|
|
|
|
**Frontend Integration:**
|
|
|
|
Use the `clientSecret` with Stripe.js to complete the payment:
|
|
|
|
```javascript
|
|
// Initialize Stripe
|
|
const stripe = Stripe('pk_test_...');
|
|
|
|
// Create payment element
|
|
const elements = stripe.elements({ clientSecret });
|
|
const paymentElement = elements.create('payment');
|
|
paymentElement.mount('#payment-element');
|
|
|
|
// Submit payment
|
|
const { error } = await stripe.confirmPayment({
|
|
elements,
|
|
confirmParams: {
|
|
return_url: 'https://coppertone.tech/payment/success',
|
|
},
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Webhooks
|
|
|
|
### Stripe Webhook
|
|
|
|
Receive Stripe webhook events for payment status updates.
|
|
|
|
```
|
|
POST /webhooks/stripe
|
|
```
|
|
|
|
**Authentication:** Stripe webhook signature verification (not JWT)
|
|
|
|
**Headers:**
|
|
```
|
|
Stripe-Signature: t=...,v1=...,v0=...
|
|
```
|
|
|
|
**Handled Events:**
|
|
|
|
| Event | Action |
|
|
|-------|--------|
|
|
| `payment_intent.succeeded` | Update payment status to `COMPLETED`, update invoice to `PAID` |
|
|
| `payment_intent.payment_failed` | Update payment status to `FAILED` |
|
|
| `charge.refunded` | Update payment status to `REFUNDED` |
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"received": true
|
|
}
|
|
```
|
|
|
|
**Error Responses:**
|
|
|
|
| Status | Condition | Response |
|
|
|--------|-----------|----------|
|
|
| 400 | Invalid signature | `Invalid webhook signature` |
|
|
| 503 | Webhook secret not configured | `Service configuration error` |
|
|
|
|
**Webhook Configuration:**
|
|
|
|
1. In Stripe Dashboard, create a webhook endpoint pointing to:
|
|
```
|
|
https://your-domain.com/api/payment/webhooks/stripe
|
|
```
|
|
|
|
2. Select events:
|
|
- `payment_intent.succeeded`
|
|
- `payment_intent.payment_failed`
|
|
- `charge.refunded`
|
|
|
|
3. Copy the webhook signing secret and set as environment variable:
|
|
```
|
|
STRIPE_WEBHOOK_SECRET=whsec_...
|
|
```
|
|
|
|
---
|
|
|
|
## CORS Configuration
|
|
|
|
Same as other services:
|
|
|
|
| Header | Value |
|
|
|--------|-------|
|
|
| `Access-Control-Allow-Origin` | Configured via `CORS_ALLOW_ORIGIN` env var |
|
|
| `Access-Control-Allow-Methods` | `GET, POST, PUT, DELETE, OPTIONS` |
|
|
| `Access-Control-Allow-Headers` | `Content-Type, Authorization` |
|
|
| `Access-Control-Allow-Credentials` | `true` |
|
|
|
|
---
|
|
|
|
## Environment Variables
|
|
|
|
| Variable | Required | Default | Description |
|
|
|----------|----------|---------|-------------|
|
|
| `JWT_SECRET` | Yes | - | Secret key for JWT validation |
|
|
| `DB_HOST` | Yes | - | PostgreSQL host |
|
|
| `DB_USER` | Yes | - | PostgreSQL user |
|
|
| `DB_PASSWORD` | Yes | - | PostgreSQL password |
|
|
| `DB_NAME` | Yes | - | PostgreSQL database name |
|
|
| `DB_SSL_MODE` | No | `require` | SSL mode |
|
|
| `DB_SCHEMA` | No | `public` | Schema: `dev`, `testing`, `prod` |
|
|
| `STRIPE_SECRET_KEY` | No | - | Stripe secret API key (sk_...) |
|
|
| `STRIPE_WEBHOOK_SECRET` | No | - | Stripe webhook signing secret (whsec_...) |
|
|
| `CORS_ALLOW_ORIGIN` | No | `http://localhost:8090` | Allowed CORS origin |
|
|
|
|
**Note:** If `STRIPE_SECRET_KEY` is not set, Stripe functionality will be disabled.
|
|
|
|
---
|
|
|
|
## Database Tables
|
|
|
|
### invoices
|
|
```sql
|
|
CREATE TABLE invoices (
|
|
id SERIAL PRIMARY KEY,
|
|
invoice_number VARCHAR(50) NOT NULL UNIQUE,
|
|
project_id INTEGER REFERENCES projects(id),
|
|
client_id INTEGER NOT NULL REFERENCES users(id),
|
|
amount DECIMAL(12,2) NOT NULL,
|
|
currency VARCHAR(3) DEFAULT 'USD',
|
|
status VARCHAR(20) DEFAULT 'DRAFT',
|
|
due_date DATE,
|
|
issued_date DATE,
|
|
paid_date DATE,
|
|
blockchain_tx_hash VARCHAR(100),
|
|
ipfs_document_cid VARCHAR(100),
|
|
notes TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
```
|
|
|
|
### payments
|
|
```sql
|
|
CREATE TABLE payments (
|
|
id SERIAL PRIMARY KEY,
|
|
invoice_id INTEGER NOT NULL REFERENCES invoices(id),
|
|
amount DECIMAL(12,2) NOT NULL,
|
|
currency VARCHAR(3) DEFAULT 'USD',
|
|
payment_method VARCHAR(50),
|
|
status VARCHAR(20) DEFAULT 'PENDING',
|
|
transaction_id VARCHAR(100),
|
|
blockchain_tx_hash VARCHAR(100),
|
|
blockchain_network VARCHAR(50),
|
|
payment_processor VARCHAR(50),
|
|
processor_fee DECIMAL(10,2),
|
|
processed_at TIMESTAMP,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Security Considerations
|
|
|
|
1. **Stripe Webhook Verification**: All webhook requests are verified using Stripe's signature verification
|
|
2. **PAID Invoice Protection**: Paid invoices cannot be modified or deleted (audit trail)
|
|
3. **Authorization Checks**: All endpoints verify user ownership or elevated role
|
|
4. **SQL Injection Prevention**: All queries use parameterized statements
|
|
5. **PCI Compliance**: Card data never touches our servers - handled entirely by Stripe
|
|
6. **Amount Validation**: All amounts must be positive numbers
|