Skip to main content

Coalition Integration

The coalition system lets LoyaltyOS programs operate dual points programs: your own proprietary loyalty points alongside external coalition points (e.g., Puntos Apprecio).

Key Concepts​

ConceptDescription
CoalitionAdapterPluggable interface connecting to an external points provider
CoalitionServiceOrchestrator — manages adapters, enforces two-phase commit, retries, and circuit breaking
CoalitionConfigPer-program database row: which provider, endpoint, encrypted credentials, and feature flags
CoalitionAccountLinks a LoyaltyOS member to their external account (e.g., email or RUT in Apprecio)
CoalitionTransactionImmutable audit log of every external operation with status tracking

Architecture​

┌─────────────────────────────────────────────────────────┐
│ REST API Layer │
│ POST /coalition/accumulate redeem convert reverse │
│ GET /members/:id/coalition/balance history │
│ Admin: config link transactions reconciliation │
└───────────────────────┬─────────────────────────────────┘
│
┌───────────────────────▼─────────────────────────────────┐
│ CoalitionService │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ Idempotency │ │ Two-Phase │ │ Compensation │ │
│ │ (txRef-based) │ │ Commit │ │ (reverse on │ │
│ │ │ │ PENDING→OK │ │ core failure)│ │
│ └──────────────┘ └──────────────┘ └───────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ Retry Logic │ │ Circuit │ │ Credential │ │
│ │ (exp backoff │ │ Breaker │ │ Encryption │ │
│ │ max 3) │ │ (opossum) │ │ (AES-256-GCM) │ │
│ └──────────────┘ └──────────────┘ └───────────────┘ │
│ adapters: Map<string, CoalitionAdapter> │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Apprecio │ │ Generic │ │ Custom │ │
│ │Adapter │ │(template)│ │Adapter │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────┘

Two-Phase Commit Flow​

1. Validate config & capabilities
2. Check idempotency (txRef) → already processed? → return cached result
3. Load or create CoalitionAccount
4. Create CoalitionTransaction (status=PENDING)
5. Call adapter via circuit breaker + retry wrapper
├── Success → UPDATE tx CONFIRMED, update account balance
└── Failure → UPDATE tx FAILED, store error, throw
6. For "convert": after adapter success, call pointsService.earn/redeem
└── If core fails → compensation via adapter.reverseTransaction()

Error Classification​

Error TypeHTTP StatusRetry?Example
CoalitionConfigNotFoundError404NoNo config for the given program
CoalitionAccountNotLinkedError404NoMember not linked to external account
CoalitionBusinessError422NoInsufficient balance, min conversion not met
CoalitionTransientError502Yes (3x)Network timeout, 5xx from provider
CoalitionCircuitOpenError503NoCircuit breaker open — adapter is down
CoalitionUnsupportedError501NoAdapter does not support this operation

Built-in Adapters​

Apprecio​

Connects to Puntos Apprecio across Mexico, Chile, Colombia, Peru, and Ecuador. Uses MD5 form-data authentication.

CountryAPI Base URL
Mexicohttps://apiv2.dcanje.mx/api
Chilehttps://api.apprecio.cl/api
Colombiahttps://apiv2.apprecio.com.co/api
Peruhttps://apiv2.apprecio.pe/api
Ecuadorhttps://apiv2.ec.dcanje.com/api

How to Add a New Adapter​

Implement the CoalitionAdapter interface:

interface CoalitionAdapter {
readonly name: string;
readonly capabilities: AdapterCapabilities;
healthcheck(): Promise<{ ok: boolean; latencyMs?: number }>;
getBalance(externalMemberRef: string): Promise<number>;
accumulate(
externalMemberRef: string,
points: number,
txRef: string,
metadata?: object,
): Promise<TxResult>;
redeem(
externalMemberRef: string,
points: number,
txRef: string,
metadata?: object,
): Promise<TxResult>;
convert(externalMemberRef: string, ownPoints: number, txRef: string): Promise<TxResult>;
reverseTransaction(txRef: string, reason: string): Promise<void>;
queryHistory?(externalMemberRef: string, from: Date, to: Date): Promise<unknown[]>;
}

Then register it:

const coalition = new CoalitionService(prisma);
coalition.registerAdapter(new MyProviderAdapter(config));

Credential Encryption​

Coalition provider credentials are encrypted at rest using AES-256-GCM:

import { encrypt, decrypt, getMasterKey } from "@loyaltyos/coalition";

const masterKey = getMasterKey(); // KMS_MASTER_KEY env var
const encrypted = encrypt(JSON.stringify({ apiKey: "sk-...", apiSecret: "..." }), masterKey);
const decrypted = decrypt(encrypted, masterKey);

Set KMS_MASTER_KEY to a 32-byte hex string in production.