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 whereclient_idmatches 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:
-
In Stripe Dashboard, create a webhook endpoint pointing to:
https://your-domain.com/api/payment/webhooks/stripe -
Select events:
payment_intent.succeededpayment_intent.payment_failedcharge.refunded
-
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
- Stripe Webhook Verification: All webhook requests are verified using Stripe's signature verification
- PAID Invoice Protection: Paid invoices cannot be modified or deleted (audit trail)
- Authorization Checks: All endpoints verify user ownership or elevated role
- SQL Injection Prevention: All queries use parameterized statements
- PCI Compliance: Card data never touches our servers - handled entirely by Stripe
- Amount Validation: All amounts must be positive numbers