Payment Processing Domain Architecture
Payment Processing Domain Architecture
Part 1: Stripe + Subscriptions (Session 4)
Key Takeaways
- Stripe is the sole payment processor — All payments (one-time and recurring) flow through Stripe. No other payment gateway is used for billing. Stripe SDK 27.1.0.
- Virtual currency (“coins”) system — The platform has an in-app currency with exchange rates, discount codes, and promo codes. Fans buy coins via Stripe, then spend coins on content/events via the wallet service.
- Purchase-request-bpm orchestrates purchase fulfillment — CIB Seven 2.0 (not Camunda 7) runs a BPMN process that handles the full purchase lifecycle: payment → entitlements → wallet debit/credit → notifications → refunds.
- Stripe product sync is message-driven — When subscriptions, courses, journeys, or office hours are created, RabbitMQ messages trigger Stripe product creation. Stripe product IDs are stored back in inventory.
- Subscriptions service is a product catalog, not billing — It manages subscription definitions (name, price, recurrence, entitlements) but delegates all payment to Stripe and all fulfillment to the BPM engine.
Migration Decision Question
What is the payment infrastructure migration path?
Migration Verdict
Verdict: Upgrade Complexity: L Key Constraint: CIB Seven BPM engine (EOL risk) orchestrates purchase fulfillment; Stripe webhook URLs and product catalog sync must be preserved Dependencies: Stripe API keys, CIB Seven Keycloak plugin, wallet service (Session 5), inventory service, email service
Services Inventory
| Service | Gen | Deployed? | Stack | Purpose |
|---|---|---|---|---|
| stripe | 2 | Yes (4/4 tenants) | Java 21 / SB 3.5.4 | Stripe payment processing, webhooks, checkout |
| subscriptions | 2 | Yes (4/4 tenants) | Java 21 / SB 3.5.4 | Subscription product catalog, series |
| purchase-request-bpm | 2 | Yes (4/4 tenants) | Java 21 / SB 3.5.4 / CIB Seven 2.0 | Purchase fulfillment orchestration |
Database Repos
| Repo | Migrations | Tables | Purpose |
|---|---|---|---|
| peeq-stripe-db | 13 | 7 | Exchange rates, discount codes, Stripe customers, checkout data |
| peeq-subscriptions-db | 14 | 4 | Subscriptions, series, tags, notification log |
API Surface
Stripe Service (11 GraphQL + 1 REST)
GraphQL Mutations (4): -
createPaymentIntent — Creates Stripe PaymentIntent, returns
client secret - createSetupIntent — Creates Stripe
SetupIntent for card registration -
cancelActiveStripeSubscription — Cancels active Stripe
subscription - createDiscountCode /
updateDiscountCode — Discount code CRUD (financial
role)
GraphQL Queries (7): -
getStripePortalSession — Creates Stripe customer portal
session URL - getStripeCheckoutSession — Creates checkout
session for purchases - queryActiveStripeSubscriptions —
Gets active subscriptions for user -
queryStripeSubscriptions — Gets subscriptions by state
(ACTIVE, CANCELED, ALL) - getAllDiscountCodes — List
discount codes (admin) -
validateDiscountCodeForCoinPurchase — Validates discount
code - getExchangeRates /
getExchangeRatesCount /
getDeletedExchangeRates — Coin exchange rate CRUD
GraphQL Mutations (Exchange Rate, 3): -
createExchangeRate / updateExchangeRate /
deleteExchangeRate — Admin coin rate management
REST (1): - POST /api/stripe/callback —
Stripe webhook handler (signature-verified)
Webhook Events Handled: 1.
checkout.session.completed 2.
payment_intent.succeeded 3.
setup_intent.succeeded 4.
invoice.payment_succeeded 5.
invoice.payment_failed 6. invoice.created 7.
charge.refunded
Subscriptions Service (8 GraphQL + 1 REST)
GraphQL Mutations (7): -
createSubscription — Create subscription product definition
- updateSubscription — Update subscription product -
createSeries / updateSeries — Content series
CRUD - batchUpdateSubscriptionDisplayOrder — Reorder
subscriptions in UI - setSubscriptionPairing /
unsetSubscriptionPairing — Link/unlink subscription tiers
(admin)
GraphQL Queries (4): -
searchSubscriptions — Multi-criteria search with pagination
and tag filtering - readSeriesById — Read series by ID list
- searchSeries — Search series by name/creator
REST (1): - GET /series-share/{id} —
Series social media share page
Purchase-Request-BPM (Event-Driven Only)
No HTTP endpoints for business logic. All
interaction is via RabbitMQ messages: - Process start:
InventoryItemPurchaseRequested - Process correlation:
PurchasesFinalized, IssueRefunds - Task
completion: PurchaseStateUpdated,
TransactionCreated, UserEntitlementsCreated -
Process stop: StopRecurringPayments
CIB Seven Admin UI: Available at
/purchase-request-bpm for process monitoring.
Who Calls This Domain? Who Does It Call?
graph LR
subgraph "Payment Domain"
STR[Stripe Service]
SUB[Subscriptions]
BPM[Purchase-Request BPM]
end
subgraph "Callers"
FE1[peeq-mono]
FE2[frontends]
STRIPE_API[Stripe Webhooks]
end
subgraph "Called Services"
INV[Inventory]
WAL[Wallet]
EMAIL[Email]
KC[Keycloak]
TAGS[Tags]
ORG[Org Manager]
end
FE1 -->|GraphQL| STR & SUB
FE2 -->|GraphQL| STR & SUB
STRIPE_API -->|Webhook| STR
STR -->|GraphQL| INV
STR -->|GraphQL| ORG
STR -->|Admin API| KC
SUB -->|GraphQL| INV
SUB -->|Admin API| KC
BPM -->|Admin API| KC
BPM -.->|RabbitMQ| WAL
BPM -.->|RabbitMQ| INV
BPM -.->|RabbitMQ| EMAIL
STR -.->|RabbitMQ| BPM
SUB -.->|RabbitMQ| STR
SUB -.->|RabbitMQ| INV
API Backward Compatibility Constraints
- Stripe webhook URL registered in Stripe dashboard — must update if service URL changes
- GraphQL schemas for stripe and subscriptions called by both frontends — schema changes break both
- Stripe customer IDs mapped to Keycloak UUIDs in
stripe_customerstable — must preserve mapping - Stripe product IDs stored in inventory metadata — bidirectional sync via RabbitMQ
- Checkout session redirect URLs point to frontend domains — must update if frontend URLs change
- Exchange rate IDs referenced in coin purchase flows — cannot change without frontend coordination
Data Model
Stripe Schema (13 Flyway migrations — 7 tables)
erDiagram
exchange_rates {
uuid id PK
string name
int price "Price in cents"
int amount "Coins received"
string[] keywords
string meta_data
boolean visible
boolean active
date created_on
uuid created_by
date deleted_on
}
discount_codes {
uuid id PK
string code UK
string name
int value
boolean percentage
int limit
boolean active
date expires_on
string associated_coin_packages
date created_on
}
discount_codes_usage {
uuid id PK
uuid fan_id
uuid code_id FK
date created_on
}
stripe_customers {
uuid peeq_user_id PK
string stripe_customer_id
date created_on
}
checkout_purchase_products {
uuid id PK
uuid stripe_checkout_data_id
string stripe_product_id
uuid inventory_id
int quantity
int inventory_price
date created_on
}
checkout_purchase_recipients {
uuid id PK
uuid stripe_checkout_data_id
string first_name
string last_name
string email
uuid keycloak_id
string organization
string phone_number
string linked_in_address
date created_on
}
promo_code_usage {
uuid id PK
uuid purchaser_id
uuid inventory_item_id
string promo_code
date created_on
date updated_on
}
one_off_subscriptions_to_cancel {
uuid id PK
string stripe_subscription_id
boolean is_cancelled
date cancelled_on
date created_on
}
discount_codes ||--o{ discount_codes_usage : tracks
Subscriptions Schema (13 Flyway migrations — 4 tables)
erDiagram
subscriptions {
uuid id PK
string name
string description
enum recurring_payment "DAILY|WEEKLY|MONTHLY|ANNUALLY|NONE"
int auto_expires
date created_on
string created_by_id
text inventory_info "JSON blob"
uuid group_id
int display_weight
uuid paired_subscription_id
int version "Optimistic lock"
enum inventory_state "HIDDEN|AVAILABLE|NOT_AVAILABLE|COMPLETE|PRE_ORDER"
}
subscription_associated_tags {
uuid id PK
string tag_id
}
series {
uuid id PK
string name
string description
date created_on
string created_by_id
text inventory_info "JSON blob"
uuid group_id
int display_weight
}
series_published_notification_log {
uuid publisher_id PK
date publish_time
}
subscriptions ||--o{ subscription_associated_tags : tagged
Purchase-Request-BPM (No Custom Tables)
Uses CIB Seven engine tables (ACT_*) with process
variables: - purchaseId (business key),
fanUserId, itemId, itemType -
price, quantity, isRefundable,
autoExpires, recurringPayment -
subscriptionEndDate, debitId,
debitWasSuccessful
Flyway migrations V1–V6 handle CIB Seven schema upgrades (7.17 → 7.23).
Data Migration Specifics
- Schema transformation: Stripe and subscriptions schemas are additive. No breaking changes across migrations.
- Foreign key dependencies:
stripe_customers.peeq_user_id→ Keycloak UUID.discount_codes_usage→discount_codes.checkout_purchase_products.inventory_id→ inventory service. - Data volume: Unknown (no DB access). 13 migrations each suggests moderate evolution.
- External ID coupling: Stripe customer IDs, Stripe product IDs, Stripe subscription IDs, Stripe checkout session IDs — all external references that must be preserved.
- CIB Seven engine DB: Contains active process instances. Flyway migrations track CIB Seven schema versions (7.17–7.23). Running instances must complete or be migrated.
In-Flight State
- Active purchase processes: CIB Seven has running process instances for in-progress purchases. Cannot stop engine without losing purchase state.
- Subscription timers: BPM process uses timer events for subscription end dates. Active timers must fire or be migrated.
- Stripe webhooks in transit: Webhook events may be queued at Stripe. Must ensure webhook endpoint remains available during migration.
- One-off subscription cancellation scheduler: Runs hourly to cancel scheduled subscriptions. Must complete pending cancellations before migration.
- RabbitMQ messages: Multiple message types flow between stripe, subscriptions, BPM, inventory, wallet, and email. Queue drain needed.
External Integrations
| Integration | Service | Purpose | Migration Impact |
|---|---|---|---|
| Stripe | stripe | Payment processing, webhooks, customer portal | API key + webhook URL migration |
| Stripe Products | stripe ← subscriptions | Stripe product catalog sync | Bidirectional via RabbitMQ |
| CIB Seven | purchase-request-bpm | Purchase fulfillment orchestration | BPM engine version + running instances |
| Keycloak | All three | JWT validation + user info | Standard JWT issuer config |
| GCS | subscriptions | Asset storage (thumbnails) | Bucket access |
Inter-Service Communication
Synchronous (REST/GraphQL)
| From | To | Protocol | Purpose |
|---|---|---|---|
| Stripe | Inventory | GraphQL | Fetch membership discounts |
| Stripe | Org Manager | GraphQL | Fetch user max membership discount |
| Stripe | Keycloak | Admin API | User info |
| Subscriptions | Inventory | GraphQL | Search inventory items |
| Subscriptions | Keycloak | Admin API | User info |
| BPM | Keycloak | Admin API | User display name for notifications |
Asynchronous (RabbitMQ)
| Publisher | Message | Consumer | Purpose |
|---|---|---|---|
| Subscriptions | SubscriptionCreated | Stripe | Create Stripe product |
| Subscriptions | SubscriptionUpdated | Stripe | Update Stripe product |
| Subscriptions | AddInventoryItem | Inventory | Register in catalog |
| Subscriptions | UpdateInventoryItem | Inventory | Sync changes |
| Subscriptions | SeriesCreated | Notifications? | Notify subscribers |
| Stripe | StripeProductCreated | Subscriptions, Inventory | Link Stripe product ID |
| Stripe | PaymentSucceeded | BPM? | Payment confirmation |
| Stripe | PaymentFailed | BPM? | Payment failure |
| Stripe | SubscriptionCanceled | Subscriptions? | Cancellation sync |
| Stripe (← external) | JourneyCreated | Stripe | Create Stripe product for journey |
| Stripe (← external) | SectionCreated | Stripe | Create Stripe product for course section |
| Stripe (← external) | OfficeHourTimeSlotCreated | Stripe | Create Stripe product for office hours |
| BPM | UpdatePurchaseState | Inventory | State transitions |
| BPM | CreateTransaction | Wallet | Debit/credit wallet |
| BPM | CreateUserEntitlements | Inventory? | Grant entitlements |
| BPM | SendEmail | Purchase notifications |
Notable: Stripe service consumes 8 inbound message types from 4 different services (subscriptions, courses, journeys, office hours). It’s a message hub for product catalog sync.
Purchase Flow (End-to-End)
sequenceDiagram
participant Fan
participant FE as Frontend
participant STR as Stripe Service
participant STRIPE as Stripe API
participant BPM as Purchase BPM
participant WAL as Wallet
participant INV as Inventory
participant EMAIL as Email
Fan->>FE: Click "Purchase"
FE->>STR: getStripeCheckoutSession()
STR->>STRIPE: Create Checkout Session
STRIPE-->>STR: Session URL
STR-->>FE: Redirect URL
FE->>STRIPE: Fan completes payment
STRIPE->>STR: Webhook: checkout.session.completed
STR-->>BPM: InventoryItemPurchaseRequested (RabbitMQ)
alt Subscription Item
BPM->>INV: UpdatePurchaseState → PaidFor
BPM->>INV: CreateUserEntitlements
Note over BPM: Timer: wait for subscription end
BPM->>INV: UpdatePurchaseState → PaidForAndFinalized
else Regular Item (Wallet)
BPM->>WAL: CreateTransaction (debit)
WAL-->>BPM: TransactionCreated
alt Sufficient Funds
BPM->>INV: UpdatePurchaseState → PaidFor
BPM->>EMAIL: SendEmail (charge notification)
alt Refundable
Note over BPM: Wait for PurchasesFinalized
BPM->>INV: UpdatePurchaseState → PaidForAndFinalized
else Non-Refundable
BPM->>INV: UpdatePurchaseState → PaidForAndFinalized
end
else Insufficient Funds
BPM->>INV: UpdatePurchaseState → InsufficientFunds
BPM->>EMAIL: SendEmail (insufficient funds)
end
end
Gen 1 → Gen 2 Comparison
| Aspect | Gen 1 | Gen 2 | Gap |
|---|---|---|---|
| Payment gateway | Stripe (assumed) | Stripe SDK 27.1.0 | Gen 2 adds checkout sessions, customer portal, invoice handling |
| Subscription model | Basic | Rich (pairing, entitlements, tags, display ordering, inventory state) | Gen 2 significantly expanded |
| Purchase fulfillment | Unknown | CIB Seven BPM with full state machine | Gen 2 adds formal workflow orchestration |
| Discount codes | Unknown | Full system (codes, usage tracking, promo codes) | Gen 2 adds complete discount infrastructure |
| Exchange rates | Unknown | Coin-based virtual currency with admin CRUD | Gen 2 adds in-app currency |
| BPM engine | Camunda 7 | CIB Seven 2.0 | Fork migration completed |
BPM Migration Risk Assessment
High Risk
- CIB Seven EOL — CIB Seven is a Camunda 7 fork. Camunda 7 CE ends October 2025. CIB Seven’s long-term support timeline is unclear. The purchase fulfillment process is critical — if the BPM engine becomes unsupported, this is a blocker.
- Running process instances — Active purchases in the BPM engine cannot be interrupted. Migration requires either completing all instances or migrating process state.
Medium Risk
- Keycloak identity plugin — CIB Seven uses a custom Keycloak plugin for identity. If CIB Seven is replaced, this plugin coupling is removed, but identity integration must be rebuilt.
- Timer events — Subscription end date timers in the BPM engine are persistent. Migrating these requires careful handling.
Low Risk
- Message-based integration — All BPM interaction is via RabbitMQ. The BPM engine is well-isolated from other services. Replacing it with a service-based saga would require reimplementing the state machine but not changing the message contracts.
- No custom JPA entities — BPM service has no custom database tables beyond CIB Seven’s engine tables. Data migration is limited to process state.
Modernization Implications
BPM Strategy Options
- Keep CIB Seven, upgrade — Lowest risk. CIB Seven 2.0 is current. Monitor EOL timeline. Replace Keycloak plugin when needed.
- Replace with saga pattern — Reimplement purchase flow as a service with state machine (no BPM engine). The flow is well-documented (~10 states, clear transitions). Removes BPM engine dependency entirely.
- Migrate to Camunda 8 — Move to Camunda’s cloud-native platform. Requires BPMN rework (Zeebe has different execution model). Higher effort, but vendor-supported.
Recommended: Option 2 (saga pattern) for Gen 3. The purchase flow is simple enough that a BPM engine is over-engineering. A state machine service with RabbitMQ integration would be simpler and have no external engine dependency.
Stripe Service Consolidation
The Stripe service handles three distinct concerns: 1. Payment processing (checkout, PaymentIntent, webhooks) 2. Virtual currency (exchange rates, discount codes, promo codes) 3. Stripe product catalog sync (message handlers)
Recommended: Keep as a single service in Gen 3. These concerns are tightly coupled through Stripe’s API. Splitting would create unnecessary inter-service communication.
Subscriptions Service
The subscriptions service is essentially a product catalog with rich metadata (entitlements, pairing, tags, display ordering). It has no direct Stripe SDK dependency — all Stripe interaction is via messages.
Recommended: Consider consolidating with inventory service in Gen 3. Subscriptions are a type of inventory item, and the two services already communicate heavily via RabbitMQ messages.
PCI Considerations (H9)
- Stripe handles all card data — the platform never touches PAN/CVV
- Stripe Checkout and Elements handle PCI-scoped UI components
- Customer portal is Stripe-hosted
- Assessment: Platform is likely SAQ-A (merchant that outsources all payment processing). No PCI DSS scope changes needed for migration.
- WLNK Warning: This is L0 conjecture. Need Stripe dashboard access to verify actual PCI scope and confirm no stored payment data.
Part 2: Wallet + Transactions + Dwolla (Session 5)
Key Takeaways (Part 2)
- Wallet is a simple balance ledger — Integer-based “Peeq” virtual currency. No payment gateway integration. Credits come from admin GraphQL mutations; debits come from RabbitMQ messages (purchase-request-bpm). Pessimistic locking for concurrency.
- Transaction service is a payment reporting tool —
Records payments made to celebrities/groups. Single table, no
double-entry bookkeeping. Financial role required. Publishes
TransactionPaidOutevents. - Dwolla is confirmed inactive (H2 → L2 Verified) — The Gen 2 wallet service has zero Dwolla references. peeq-dwolla is a Gen 1 service (Java 11/SB 2.6.8) last committed January 2023, not deployed to production. Dwolla was replaced by Stripe for all active payment flows.
- Wallet and Transaction are distinct services — Wallet handles fan coin balances (virtual currency). Transaction records celebrity/group payment disbursement records. They serve different audiences and different data flows.
Services Inventory (Part 2)
| Service | Gen | Deployed? | Stack | Purpose |
|---|---|---|---|---|
| wallet | 2 | Yes (4/4 tenants) | Java 21 / SB 3.5.4 | Fan coin balance ledger |
| transaction | 2 | Yes (4/4 tenants) | Java 21 / SB 3.5.4 | Celebrity/group payment records |
| peeq-dwolla | 1 | No | Java 11 / SB 2.6.8 | Dwolla ACH payments (inactive) |
Database Repos (Part 2)
| Repo | Migrations | Tables | Purpose |
|---|---|---|---|
| peeq-wallet-db | 3 | 3 | Balance tracking, transaction ledger |
| peeq-transaction-db | 6 | 1 | Celebrity/group payment records |
| peeq-dwolla-db | 3 | 3 | Vault secrets, webhook events (inactive) |
API Surface (Part 2)
Wallet Service (1 GraphQL Mutation + 4 Queries)
GraphQL Mutations (1): - addToWallet —
Admin-only: adds coins to user’s wallet
GraphQL Queries (4): - getBalance —
Returns coin balance for current or specified user -
getBalanceAllUsers — Admin-only: total platform coin
balance - getWalletHistory — Paginated credit/debit
transaction history - microservice — Service metadata
No REST endpoints. All interaction is GraphQL or RabbitMQ.
Transaction Service (6 Queries + 1 Mutation)
GraphQL Mutations (1): - markPayment —
Financial role: records payment to celebrity/group, validates via
inventory service
GraphQL Queries (6): -
getPaidDetailsByCurrentCeleb — Celebrity’s own transaction
summary - getPaidDetailsByCeleb — Financial role: any
celebrity’s transactions - getPaidDetailsByGroup —
Financial role: group transactions - getPaidItems —
Deprecated: legacy query - findPaidItemsByCeleb — Financial
role: paginated celebrity search - findPaidItemsByGroup —
Financial role: paginated group search
No REST endpoints. Publish-only for RabbitMQ (no consumers).
peeq-dwolla (Gen 1 — Inactive)
GraphQL: Customer management, transfers, funding
sources, W9 tax forms REST: Plaid bank linking
(/plaid/link-token, /plaid/public-token,
/plaid/processor-token), Dwolla webhooks
Stack: Dwolla Kotlin SDK 0.1.2, Plaid Java SDK 13.0.0,
vault-service 0.0.12, w9-service 0.0.8 Last commit:
January 2023 (3 years inactive)
Who Calls Part 2 Services?
graph LR
subgraph "Wallet & Transaction Domain"
WAL[Wallet Service]
TXN[Transaction Service]
end
subgraph "Callers"
FE1[peeq-mono]
FE2[frontends]
BPM[Purchase-Request BPM]
end
subgraph "Called Services"
INV[Inventory]
SSE[SSE Service]
KC[Keycloak]
end
FE1 -->|GraphQL| WAL & TXN
FE2 -->|GraphQL| WAL & TXN
BPM -.->|RabbitMQ: CreateTransaction| WAL
WAL -.->|RabbitMQ: TransactionCreated| BPM
WAL -.->|RabbitMQ: SendSseMessageToUser| SSE
TXN -->|GraphQL| INV
TXN -->|Admin API| KC
TXN -.->|RabbitMQ: TransactionPaidOut| ???
Data Model (Part 2)
Wallet Schema (3 Flyway migrations — 3 tables)
erDiagram
balance_transaction {
uuid id PK
string user_id
int balance "Current coin balance"
}
create_transaction {
uuid id PK
string user_id
string item_id
boolean is_debit
int amount
string item_type
string description
string meta_data
date created_on
}
create_transaction_related_transactions {
uuid create_transaction_id FK
string related_transaction_id
}
balance_transaction ||--o{ create_transaction : "tracks"
create_transaction ||--o{ create_transaction_related_transactions : "links"
Transaction Schema (6 Flyway migrations — 1 table)
erDiagram
transactions {
uuid id PK
uuid user_id "Celebrity/talent (nullable)"
uuid group_id "Group (nullable)"
string display_name
text purchases "JSON array of InventoryInfo"
int items_number
int price "Total price"
int quantity "Total quantity"
uuid created_by_id
date created_on
string comment
}
peeq-dwolla Schema (3 Flyway migrations — 3 tables, inactive)
erDiagram
vault_namespaces {
string namespace_id PK
text public_key
text private_key
int version
date created_on
}
vault_secrets {
uuid secret_id PK
string namespace_id FK
string key
text value "Encrypted"
date created_on
}
dwolla_webhook_events {
string webhook_event_id PK
string resource_id
string topic
date created
text body
string status
}
vault_namespaces ||--o{ vault_secrets : contains
Data Migration Specifics (Part 2)
- Wallet schema: Simple — 3 tables, UUID keys, integer balances. Migrated from bigint to UUID in V2.
- Transaction schema: Single table with JSON
purchasescolumn. Evolved from item-level to aggregate records. - No foreign keys to other domains: Wallet uses
string
userId(not UUID FK). Transaction uses UUIDuser_id/group_idbut no FK constraints. - Data volume: Unknown. Wallet likely has moderate volume (one balance row per user, many transaction rows). Transaction is append-only.
- Dwolla data: 3 tables with vault secrets and webhook events. If Dwolla is retired, this data can be archived.
In-Flight State (Part 2)
- Wallet pessimistic locks: Balance reads use
PESSIMISTIC_READlock. During migration, concurrent transactions must be handled. - RabbitMQ messages: Wallet receives
CreateTransactionfrom BPM and publishesTransactionCreatedback. Queue drain needed during migration. - SSE notifications: Wallet publishes
SendSseMessageToUserfor real-time balance updates. SSE clients would lose notifications during migration. - No scheduled jobs: Neither wallet nor transaction has schedulers.
External Integrations (Part 2)
| Integration | Service | Purpose | Migration Impact |
|---|---|---|---|
| Keycloak | Both | JWT validation | Standard config |
| Inventory | Transaction | Purchase validation | GraphQL dependency |
| SSE | Wallet | Real-time balance notifications | RabbitMQ dependency |
| Dwolla | peeq-dwolla (inactive) | ACH bank transfers | Retire — not in production |
| Plaid | peeq-dwolla (inactive) | Bank account linking | Retire — not in production |
Inter-Service Communication (Part 2)
Synchronous
| From | To | Protocol | Purpose |
|---|---|---|---|
| Transaction | Inventory | GraphQL | Validate purchase IDs, fetch pricing |
| Transaction | Keycloak | Admin API | User display names |
Asynchronous (RabbitMQ)
| Publisher | Message | Consumer | Purpose |
|---|---|---|---|
| Purchase-Request-BPM | CreateTransaction | Wallet | Debit/credit fan wallet |
| Wallet | TransactionCreated | Purchase-Request-BPM | Confirm transaction (with hasFunds flag) |
| Wallet | SendSseMessageToUser | SSE | Real-time balance update notification |
| Transaction | TransactionPaidOut | Unknown | Notify of celebrity/group payment |
Dwolla Status (H2 Evidence)
Dwolla is confirmed inactive and should be retired.
Evidence chain: 1. ArgoCD: No peeq-dwolla app in any
of 4 production tenants (Session 0) 2. Gen 2 wallet
service: Zero Dwolla references in source code, pom.xml, or
config (Session 5) 3. peeq-dwolla: Gen 1 service (Java
11/SB 2.6.8), last commit January 2023 (3 years ago) 4.
peeq-dwolla-db: Last commit September 2022 (3+ years
ago) 5. Stripe replacement: All active payment flows
use Stripe (Session 4). Stripe handles checkout, subscriptions,
invoices, refunds. 6. Frontend dead code: DwollaService
exists in frontends repo (admin-fe) but calls a backend not deployed to
production. 7. Celebrity message: Celebrity service
publishes CreateDwollaAccount — dead code, no consumer in
production.
H2 promoted to L2 (Verified): Dwolla integration is confirmed inactive. The wallet service is a pure coin ledger with no payment gateway.
Recommendation: Archive peeq-dwolla, peeq-dwolla-db.
Remove DwollaService from frontends. Remove
CreateDwollaAccount message from celebrity service.
Wallet Model Analysis
Currency Model
- Unit: “Peeq” (integer, no decimals)
- Single currency: No multi-currency support
- Balance tracking: One
balance_transactionrow per user, updated in-place with pessimistic locking - Transaction log: Append-only
create_transactionrecords
Flow: Fan Purchases Content
- Fan buys coins via Stripe checkout → coins added to wallet (admin addToWallet)
- Fan spends coins on content/events → BPM sends
CreateTransaction(debit) to wallet - Wallet checks balance, deducts if sufficient, publishes
TransactionCreated - BPM receives confirmation, proceeds with fulfillment or handles insufficient funds
Flow: Celebrity Gets Paid
- Financial admin reviews earnings in transaction service
- Admin calls
markPayment→ validates purchases via inventory service - Transaction record created with aggregated purchase data (JSON)
TransactionPaidOutpublished (downstream handling unclear — may trigger external payout)
Limitation: No Double-Entry Bookkeeping
The transaction service is a simple payment log, not an accounting ledger. No debit/credit pairs, no reconciliation, no audit trail for individual line items. If Gen 3 needs proper financial reporting, this must be redesigned.
Modernization Implications (Part 2)
Wallet + Transaction Consolidation
Wallet and transaction serve different purposes: - Wallet: Fan-facing coin balance (virtual currency) - Transaction: Admin-facing payment records (celebrity disbursements)
Recommended: Keep separate in Gen 3. Different audiences, different access patterns, different data models. Consolidation would mix concerns.
Virtual Currency Simplification
The coin-based virtual currency (exchange rates → wallet → debit) adds complexity. Consider: - Keep coins: If the business needs an intermediary currency for pricing flexibility - Remove coins, charge directly: If all purchases can be priced in USD and charged via Stripe directly
This is a business decision, not a technical one.
Dwolla Retirement
Recommended: Archive all Dwolla-related repos and
code: - Archive: peeq-dwolla, peeq-dwolla-db - Remove: DwollaService
from frontends, CreateDwollaAccount from celebrity service
- Remove: dwolla API gateway from frontend configs
Transaction Service Redesign
If Gen 3 needs proper financial reporting: - Add double-entry bookkeeping (debit/credit pairs) - Add ledger reconciliation - Add proper audit trail - Consider integration with accounting SaaS (QuickBooks, Xero)
Last updated: 2026-01-30 — Sessions 4-5 Review by: 2026-07-30 Staleness risk: Medium — Stripe API versions, CIB Seven support status, and subscription/wallet models evolve