# 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 ``` ## 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 ``` **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 ``` **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 ``` **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 ``` **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 ``` **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 ``` **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 ``` **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 ``` **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 ``` **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