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

16 KiB

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):

[
  {
    "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):

{
  "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:

{
  "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):

{
  "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:

{
  "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):

{
  "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):

[
  {
    "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):

{
  "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:

{
  "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):

{
  "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:

{
  "invoiceId": 1
}
Field Type Required Description
invoiceId integer Yes Invoice to pay

Response (200 OK):

{
  "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:

// 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):

{
  "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

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

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