Skip to main content

Payments API Reference

Overview

The Payments API provides comprehensive payment processing capabilities supporting multiple payment providers, mixed tender payments (voucher + external payment), refund processing, and no-show fee management.

Supported Payment Providers:

  • Stripe - Credit/debit card payments (Authorize & Capture pattern)
  • Paytrail - Finnish payment methods (bank transfers, mobile payments)
  • Vouchers - Gift cards and promotional vouchers
  • Payment on Arrival (PoA) - Pay at venue after arrival confirmation

Key Features:

  • Multi-provider payment orchestration
  • Mixed tender support (partial voucher + external payment)
  • Webhook-based payment confirmation
  • Signature verification and replay protection
  • Automatic refund processing
  • No-show fee assessment and charging

Authentication

All payment endpoints require proper authentication:

  • Webhooks: Use provider-specific signature verification (no Bearer token required)
  • Admin endpoints: Require Authorization: Bearer <token> header with admin permissions
  • Customer endpoints: Require valid authentication token

All requests must include the x-tenant-id header for multi-tenant isolation.


Payment Providers

Credential Resolution (site → tenant → environment)

Every charge, refund, and provider adapter is built against credentials resolved from the booking's context in a fixed precedence:

  1. Site — the booking's site, if it has its own gated payment credentials (site_payment_settings with enable_custom_payments = true)
  2. Tenant — the tenant's configured gateway credentials
  3. Environment — the platform's credentials

Provider-specific rules:

  • Stripe resolves site → tenant → not-configured. It never falls back to the platform environment key for customer charges — an unconfigured site/tenant yields a not-configured result rather than charging into the platform account.
  • Paytrail resolves site → tenant → environment. The PaytrailAdapterFactory exposes forSite(tenantId, siteId) (site-aware) alongside the existing forTenant(tenantId); adapters are cached by a hash of the resolved merchantId:secretKey, so site and tenant credentials occupy distinct cache entries.

The resolved source is tracked as site, tenant, or env. The same precedence is applied to charges, refunds, group / mixed-tender settlement, and the site-aware Paytrail stale-payment poller, so a payment and its refund always settle against the same account. See Per-Site Payment Credentials for the admin configuration side.

1. Stripe Integration

Authorize & Capture Pattern

Stripe payments use a two-phase authorization and capture flow:

  1. Authorization: Hold funds on customer's card during booking hold
  2. Capture: Charge the authorized amount when booking is confirmed

Benefits:

  • Funds are reserved but not charged until confirmation
  • Prevents double charges if customer abandons booking
  • Supports partial captures (charge less than authorized amount)

Stripe Payment Flow

Create Booking Hold

Authorize Payment (Stripe PaymentIntent)

Customer confirms booking

Capture Payment

Webhook confirms payment

Booking status → PAID

Webhook Endpoint

Endpoint: POST /payments/stripe/webhook

Purpose: Receives payment event notifications from Stripe

Headers:

stripe-signature: t=1234567890,v1=signature_hash
content-type: application/json

Security Features:

  • Signature Verification: Validates webhook authenticity using Stripe signing secret
  • Idempotency: Prevents duplicate processing of same event
  • Replay Protection: Detects and rejects tampered signatures
  • Automatic Retry: Stripe retries failed webhooks automatically

Common Event Types:

  • payment_intent.succeeded - Payment completed successfully
  • payment_intent.payment_failed - Payment failed
  • charge.refunded - Refund processed
  • charge.dispute.created - Chargeback initiated

Response:

{
"received": true,
"processed": true,
"eventId": "evt_1234567890",
"message": "Event processed successfully"
}

Error Handling:

  • 400 Bad Request - Invalid signature or missing headers (Stripe won't retry)
  • 500 Internal Server Error - Processing failed (Stripe will retry)

2. Paytrail Integration

Payment Creation Flow

Paytrail supports Finnish payment methods including bank transfers, mobile payments, and credit cards.

Flow:

Create Booking Hold

Create Paytrail Payment

Redirect customer to Paytrail payment page

Customer completes payment

Paytrail callback to webhook

Booking status → PAID

Webhook Endpoint

Endpoint: POST /webhooks/paytrail/callback

Purpose: Receives payment completion callbacks from Paytrail

Headers:

signature: calculated_hmac_signature
checkout-account: merchant_account_id
checkout-algorithm: sha256
checkout-method: POST
checkout-timestamp: 2025-11-17T12:00:00Z
checkout-nonce: unique_nonce_value

Request Body:

{
"checkout-stamp": "booking-123-external-1234567890",
"checkout-transaction-id": "paytrail_tx_abc123",
"checkout-status": "ok",
"checkout-amount": "10000",
"checkout-reference": "booking-123",
"checkout-provider": "nordea"
}

Callback Statuses:

  • ok - Payment successful
  • fail - Payment failed
  • pending - Payment pending (bank transfer)
  • delayed - Payment delayed

Security Features:

  • HMAC Signature Verification: Validates callback authenticity
  • Idempotency: Prevents duplicate processing using idempotency keys
  • Replay Protection: Detects and rejects duplicate callbacks

Response:

{
"status": "success",
"message": "Callback processed successfully",
"idempotency_key": "idem_paytrail_abc123xyz"
}

3. Mixed Tender Payments

Mixed tender allows customers to pay with a combination of voucher and external payment (Stripe/Paytrail).

Mixed Tender Flow

Total Amount: €100

Apply Voucher (€30 balance)

Voucher redeemed: €30
Remaining: €70

External Payment (Stripe/Paytrail): €70

Both payments recorded

Booking status → PAID

Payment Orchestration

Process:

  1. Voucher Redemption: Attempt to redeem voucher code first
  2. Balance Calculation: Calculate remaining amount after voucher
  3. External Payment: Process remaining balance via Stripe/Paytrail
  4. Transaction Recording: Record both payments under same booking

Example Request:

{
"booking_id": "booking_abc123",
"customer_id": "cus_xyz789",
"total_amount": 10000,
"currency": "EUR",
"tenant_id": "t_demo",
"voucher_code": "SUMMER2025",
"payment_provider": "stripe",
"payment_method_id": "pm_card_visa",
"success_url": "https://example.com/success",
"cancel_url": "https://example.com/cancel"
}

Example Response:

{
"success": true,
"total_amount_charged": 10000,
"voucher_payment": {
"voucher_code": "SUMMER2025",
"voucher_transaction_id": "vt_abc123",
"amount_redeemed": 3000,
"remaining_voucher_balance": 0
},
"external_payment": {
"provider": "stripe",
"payment_intent_id": "pi_xyz789",
"amount_charged": 7000,
"requires_action": false,
"client_secret": "pi_xyz789_secret_abc"
}
}

Refund Processing

Refund Orchestrator

The platform supports automatic refunds across all payment providers with fee calculation.

Refund Flow

Cancellation Request

Calculate Refund Amount (apply cancellation fee policy)

Create Refund Request

Process Refund via Provider

Update Booking Status

Refund Confirmation

Refund Calculation

Refunds are calculated based on tenant-specific cancellation policies:

  • Flat Fee: Deduct fixed amount (e.g., €10)
  • Percentage Fee: Deduct percentage of total (e.g., 10%)
  • Time-based: Fee depends on time until booking (e.g., no fee if >24h, 50% fee if <24h)

Mixed Payment Refunds

For bookings paid with voucher + external payment:

Refund Priority:

  1. External payment refunded first (Stripe/Paytrail)
  2. Voucher credited back (balance restored)

Example:

Original Payment:
- Voucher: €30
- Stripe: €70
Total: €100

Refund (with 10% fee):
- Fee: €10
- Refund: €90

Refund Distribution:
- Stripe refund: €70 (full external payment)
- Voucher credit: €20 (partial voucher amount)

Refund Response Format

{
"success": true,
"refund_id": "ref_abc123",
"total_refund_amount": 9000,
"fee_deducted": 1000,
"net_refund_amount": 9000,
"refund_breakdown": [
{
"payment_method": "stripe",
"original_amount": 7000,
"refund_amount": 7000,
"provider_refund_id": "re_stripe_xyz"
},
{
"payment_method": "voucher",
"original_amount": 3000,
"refund_amount": 2000,
"voucher_new_balance": 2000
}
]
}

No-Show Fee Processing

Fee Assessment Service

Automatically charges no-show fees when customers fail to arrive for their bookings.

No-Show Flow

Customer misses booking

Admin marks as NO_SHOW

Calculate fee based on policy

Charge fee using original payment method

Update booking status

Fee recorded in ledger

Fee Assessment Request

Endpoint: POST /admin/no-shows/process-fee (admin only)

Request:

{
"booking_id": "booking_abc123",
"tenant_id": "t_demo",
"customer_id": "cus_xyz789",
"assessed_by": "admin_user_123",
"assessment_reason": "Customer did not arrive for scheduled booking",
"override_fee_amount": 2000
}

Response:

{
"success": true,
"fee_assessment_id": "fee_abc123",
"calculated_fee_amount": 2000,
"charged_fee_amount": 2000,
"fee_type": "flat",
"payment_method_used": "stripe",
"charge_response": {
"charge_id": "ch_xyz789",
"charged_amount": 2000,
"provider_fees": 58
},
"metadata": {
"policy_applied": "tenant_no_show_policy",
"calculation_details": {
"base_amount": 2000,
"multiplier": 1.0
},
"payment_attempts": 1,
"assessment_timestamp": "2025-11-17T12:00:00.000Z"
}
}

Payment Security

Webhook Security

All webhook endpoints implement comprehensive security measures:

1. Signature Verification

Stripe:

  • Uses stripe-signature header
  • HMAC signature with timestamp
  • Validates against webhook signing secret

Paytrail:

  • Uses signature header
  • SHA-256 HMAC signature
  • Includes checkout headers in signature calculation

2. Idempotency

Event Deduplication:

  • Each webhook event has unique ID
  • System tracks processed event IDs
  • Duplicate events return cached result without reprocessing

Benefits:

  • Prevents double charges
  • Safe for automatic retries
  • Maintains data consistency

3. Replay Protection

Tampered Signature Detection:

  • Checks for obviously malformed signatures
  • Validates signature format before processing
  • Rejects signatures with suspicious patterns

Timestamp Validation:

  • Verifies event timestamp is recent
  • Rejects events older than threshold (e.g., 5 minutes)
  • Prevents replay of old events

Payment Status Flow

Status Transitions

NOT_STARTED → PENDING → AUTHORIZED → CAPTURED → COMPLETED

FAILED

CAPTURED → REFUND_PENDING → REFUNDED

Status Definitions:

  • NOT_STARTED: No payment initiated
  • PENDING: Payment created, awaiting customer action
  • AUTHORIZED: Funds held (Stripe authorize phase)
  • CAPTURED: Funds charged (Stripe capture phase)
  • COMPLETED: Payment fully processed and confirmed
  • FAILED: Payment failed at any stage
  • REFUND_PENDING: Refund initiated
  • REFUNDED: Refund completed

Error Handling

Common Error Codes

Error CodeHTTP StatusDescriptionRecovery
PAYMENT_FAILED400Payment processing failedRetry with different payment method
INVALID_SIGNATURE403Webhook signature invalidCheck webhook signing secret
DUPLICATE_EVENT200Event already processedNo action needed (idempotent)
INSUFFICIENT_FUNDS400Voucher balance too lowUse different voucher or add external payment
PROVIDER_ERROR502Payment provider errorRetry after delay
REFUND_FAILED500Refund processing failedManual intervention required

Error Response Format

{
"error": {
"code": "PAYMENT_FAILED",
"message": "Payment processing failed: card declined",
"details": {
"provider": "stripe",
"provider_error_code": "card_declined",
"retry_allowed": true,
"suggested_action": "Try different payment method"
}
}
}

Best Practices

1. Webhook Implementation

Do:

  • ✅ Verify signatures before processing
  • ✅ Return 200 OK quickly to acknowledge receipt
  • ✅ Process events asynchronously if needed
  • ✅ Log all webhook attempts for monitoring
  • ✅ Implement idempotency for safe retries

Don't:

  • ❌ Process webhooks without signature verification
  • ❌ Perform long-running operations synchronously
  • ❌ Trust webhook data without validation
  • ❌ Ignore duplicate event detection

2. Payment Processing

Do:

  • ✅ Use idempotency keys for all payment operations
  • ✅ Store correlation IDs for payment tracking
  • ✅ Handle partial payments correctly (mixed tender)
  • ✅ Implement proper error handling and retries
  • ✅ Log all payment attempts for auditing

Don't:

  • ❌ Charge customers before booking confirmation
  • ❌ Skip capture phase for authorized payments
  • ❌ Process payments without tenant context

3. Refund Processing

Do:

  • ✅ Calculate fees based on cancellation policy
  • ✅ Refund external payments before crediting vouchers
  • ✅ Record all refund attempts in audit log
  • ✅ Notify customers of refund status

Don't:

  • ❌ Refund without verifying booking status
  • ❌ Process refunds without fee calculation
  • ❌ Skip refund confirmation notifications

Testing

Stripe Test Mode

Test Card Numbers:

Success: 4242 4242 4242 4242
Decline: 4000 0000 0000 0002
3D Secure: 4000 0025 0000 3155

Test Webhook Events: Use Stripe CLI to send test webhooks:

stripe trigger payment_intent.succeeded

Paytrail Test Mode

Test Configuration:

PAYTRAIL_MERCHANT_ID=test_merchant_id
PAYTRAIL_SECRET_KEY=test_secret_key
PAYTRAIL_WEBHOOK_SECRET=test_webhook_secret

Test Payment Status:

  • Success: Use test callback with checkout-status=ok
  • Failure: Use test callback with checkout-status=fail

Monitoring & Observability

Payment Metrics

Track:

  • Payment success/failure rates
  • Average processing time
  • Webhook delivery latency
  • Refund processing time
  • No-show fee collection rate

Webhook Health Check

Stripe Health Check:

POST /payments/stripe/webhook/health

Paytrail Health Check:

POST /webhooks/paytrail/health

Response:

{
"status": "healthy",
"timestamp": "2025-11-17T12:00:00.000Z",
"stats": {
"total_events_processed": 1250,
"events_last_hour": 42,
"duplicate_events_rejected": 8,
"failed_events": 2
}
}

Payment on Arrival (PoA)

Payment on Arrival allows customers to pay at the venue instead of online. The booking is confirmed without upfront payment and transitions through a PoA-specific lifecycle.

For the full PoA flow including arrival confirmation, payment collection methods, late arrival handling, and no-show fee assessment, see the Payment on Arrival Guide.

Key endpoints for PoA:

  • POST /api/v1/bookings/{id}/confirm with paymentMethod: "poa" — confirm a PoA booking
  • POST /api/v1/bookings/{id}/mark-arrived — record customer arrival and optionally collect payment
  • POST /api/v1/bookings/{id}/mark-paid — record payment received at venue

Support

For payment integration questions or issues:

  • Check webhook logs for detailed error messages
  • Verify webhook signing secrets are correctly configured
  • Monitor payment status in admin dashboard
  • Contact support for provider-specific issues