Files
web-hosts/domains/coppertone.tech/docs/api/payment-service.md
2025-12-26 13:38:04 +01:00

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