API Documentation¶
Cloud Functions (Callable)¶
All functions use Firebase Callable Functions (HTTPS-callable).
Authentication¶
All functions require Firebase Authentication. Pass Authorization: Bearer <idToken> header or use Firebase SDK.
issue_giftcard¶
Issue a new giftcard to a user.
Function: issue_giftcard
Role: ADMIN only
Request¶
{
"idempotencyKey": "uuid-...",
"productId": "giftcard-100nok",
"targetUserId": "user-...",
"claimCode": "XXXX-XXXX-XXXX-XXXX",
"reason": "Birthday gift"
}
Response¶
{
"success": true,
"walletItemId": "uuid-...",
"ledgerTxId": "uuid-...",
"claimCodeHash": "sha256(code+pepper)"
}
Behavior¶
- Acquires atomic idempotency lock.
- Validates product exists.
- Creates wallet item.
- Creates ledger entry (ISSUE).
- If claim code provided: creates
giftcard_codes/{codeHash}. - Updates wallet projection.
- Logs admin action to audit log.
Errors¶
unauthenticated: User not authenticated.permission-denied: User is not ADMIN.invalid-argument: Missing required fields.not-found: Product not found.failed-precondition: Idempotency key already being processed.
redeem_value¶
Deduct value from a wallet item.
Function: redeem_value
Role: END_USER (own item) or STAFF/ADMIN (any item)
Request¶
{
"idempotencyKey": "uuid-...",
"walletItemId": "uuid-...",
"amount": 50,
"reason": "Coffee purchase"
}
Response¶
{
"success": true,
"ledgerTxId": "uuid-...",
"remainingBalance": 50
}
Behavior¶
- Acquires idempotency lock.
- Fetches wallet item, verifies ownership.
- Checks status (must be ACTIVE).
- Checks expiry.
- Validates sufficient balance.
- Creates ledger entry (REDEEM).
- Updates wallet item with used amount.
- Creates redemption record.
- Updates wallet projection.
- Returns remaining balance.
Errors¶
unauthenticated: User not authenticated.not-found: Wallet item not found.permission-denied: User does not own the item.failed-precondition: Item not ACTIVE, expired, or insufficient balance.
claim_code¶
Claim a giftcard using a code.
Function: claim_code
Role: END_USER
Request¶
{
"idempotencyKey": "uuid-...",
"code": "XXXX-XXXX-XXXX-XXXX",
"externalAccountId": "membership-123"
}
Response¶
{
"success": true,
"walletItemId": "uuid-...",
"ledgerTxId": "uuid-..."
}
Behavior¶
- Acquires idempotency lock.
- Hashes code and looks up in
giftcard_codes. - Validates code is ACTIVE and not expired.
- Links external account if provided.
- Creates new wallet item (copy of original, linked to claimant).
- Creates ledger entry (CLAIM).
- Marks code as CLAIMED.
- Updates wallet projection.
Errors¶
unauthenticated: User not authenticated.not-found: Code not found.failed-precondition: Code already claimed or blocked.
Idempotency¶
All write operations require idempotencyKey.
- Format: UUID (RFC 4122) or any unique string.
- Lifetime: 7 days (configurable).
- Retry: Send same
idempotencyKey+ payload to get cached result. - Change payload: If you modify the request, behavior is undefined (hash mismatch detected).
Example: Retry Pattern¶
const idempotencyKey = generateUUID();
// First attempt
try {
const result = await issueGiftCard({
idempotencyKey,
productId: "giftcard-100nok",
targetUserId: "user-123"
});
} catch (error) {
if (error.code === "failed-precondition") {
// Network error or server timeout - retry with SAME key
const result = await issueGiftCard({
idempotencyKey, // Same key!
productId: "giftcard-100nok",
targetUserId: "user-123"
});
// Will return cached result from first attempt
}
}
Errors¶
Standard Firebase error format:
{
"code": "string",
"message": "Human-readable error"
}
Common codes:
- unauthenticated: Missing/invalid auth token.
- permission-denied: Insufficient permissions.
- invalid-argument: Invalid request data.
- not-found: Resource doesn't exist.
- failed-precondition: Invalid state (e.g., item already redeemed).
- internal: Server error (see logs).
Rate Limiting¶
No explicit rate limiting implemented. Recommend: - Firebase pricing (pay-per-read/write). - Add Cloud Armor rules if needed. - Monitor logs for abuse.
Pagination¶
Not implemented in v1. Recommend:
- Firestore pagination via startAfter() and limit.
- Implement in client or API layer.
Next Steps¶
- REST wrapper (Express.js or similar).
- GraphQL layer (optional).
- SDK for client apps (web, mobile).