Stripe Table Data Architecture
Overview
The FinMatch Admin Dashboard displays Stripe customer data in a table that allows you to view, search, and link Stripe billing profiles to FinMatch merchants. This document explains how the data is fetched, displayed, and managed.
Data Fetching
Current Implementation
The Stripe table fetches data fresh from Stripe's API every time the page loads.
When you navigate to the Stripe page in the admin dashboard:
- Frontend loads the live list: paginated
GET /api/customerson stripe-api (Cloud Run servicefinmatch-stripe-api,europe-west2). - The table shows every live customer (not the newest 100). While the tab stays open it polls about every 60 seconds.
- Stripe webhooks upsert one customer so a billing change can appear on the next poll without Create Snapshot.
- Create Snapshot (
POST /api/snapshots/create) is optional archive only. It is not how the table loads.
Do not hard-code a Cloud Run revision host. The service name is
finmatch-stripe-api.
Data Flow
Admin Dashboard (Frontend)
↓
Stripe API Service (Cloud Run)
↓
Stripe API (External)
↓
Data Transformation
↓
Table Display
Refresh Capability
The Stripe tab polls GET /api/customers about every 60 seconds while
it is open. Reload the tab if you need the list immediately. Create
Snapshot is not a refresh control.
Optional snapshot archive
The Stripe table is the live GET /api/customers list. Create
Snapshot writes an optional GCS archive (POST /api/snapshots/create).
Do not click that button to see billed customers. Snapshots live on
stripe-api / GCS, not in the admin static site.
Table Columns Explained
Customer ID
Stripe Field: customer.id
Display: Full Stripe customer ID (e.g., cus_R5CyAAFzShIf5Q)
This is the unique identifier for the Stripe customer record. It's used to link the Stripe billing profile to a FinMatch merchant.
Name
Stripe Field: customer.name or customer.description
Display: Customer name as stored in Stripe
Important Notes:
- The Name field in FinMatch Admin can include either individual names or company names
- Stripe allows customers to be individuals or businesses
- If a customer is a business, the name will be the company name
- If a customer is an individual, the name will be the person's name
- The field may also contain a description if no name is set
Example values:
"John Smith"(individual)"Acme Corporation Ltd"(company)"John Smith - Acme Corp"(combined)
Email
Stripe Field: customer.email
Display: Customer email address
The primary email address associated with the Stripe customer account.
Subscription Plan
Stripe Field: subscription.items.data[0].price.id
Display: Stripe Price ID (e.g., price_1SnL3oRrNO9w4XvzhYpLvknw)
What it shows:
- The Price ID of the active subscription plan
- This is Stripe's unique identifier for the pricing tier
Understanding the values:
- If a price ID is shown: The customer has an active subscription with billing
- If "N/A" is shown: The customer does not have an active subscription
- This could mean:
- No subscription was ever created
- The subscription was canceled
- The subscription expired
- The subscription is in a trial period that ended
- This could mean:
Note: The Subscription Plan column is designed to show whether a Stripe billing record has an active subscription. If it shows "N/A", it means there is no active subscription plan associated with this customer.
Status
Stripe Field: subscription.status
Display: Color-coded badge showing subscription status
Status Options:
| Status | Badge Color | Meaning |
|---|---|---|
| Active | Green | Subscription is active and billing normally |
| Trialing | Blue | Customer is in trial period |
| Past Due | Red | Payment failed, subscription is past due |
| Canceled | Gray | Subscription has been canceled |
| None | Gray | No subscription exists for this customer |
How it's determined:
- The system looks for an active subscription (
status === 'active') - If no active subscription exists, it checks for any subscription and uses its status
- If no subscriptions exist at all, status is
'none'
Status Badge Colors:
- Active: Green (
#10b981) - Past Due: Red (
#ef4444) - Canceled: Gray (
#6b7280) - None: Light Gray (
#9ca3af)
Next Billing
Stripe Field: subscription.current_period_end
Display: Formatted date (e.g., "12/31/2024")
What it shows:
- The date when the current billing period ends
- Calculated from Stripe's
current_period_endtimestamp (Unix epoch seconds)
Display:
- If an active subscription exists: Shows the next billing date
- If no active subscription: Shows "N/A"
Note: This column may have been removed in recent updates. Check the current table structure.
Created
Stripe Field: customer.created
Display: Formatted date (e.g., "01/15/2024")
What it shows:
- The date when the Stripe customer account was created
- Calculated from Stripe's
createdtimestamp (Unix epoch seconds)
Note: This column may have been removed in recent updates. Check the current table structure.
FinMatch Merchant
Source: merchants.json → stripeCustomerId field
Display: Merchant name (if linked) or "Not linked"
What it shows:
- If linked: The name of the FinMatch merchant linked to this Stripe customer
- Clickable link that navigates to the merchant detail page
- Shows merchant name or company name
- If not linked: Displays "Not linked" in gray italic text
How linking works:
- When a merchant is linked,
merchants.jsonstores thestripeCustomerIdin the merchant's profile - The table finds the linked merchant by matching
merchant.stripeCustomerId === customer.id - Only one merchant can be linked to a Stripe customer at a time
Last Link to Merchant Date
Source: merchants.json → lastUpdated field (when stripeCustomerId is set)
Display: ISO timestamp or formatted date
What it shows:
- The last time the merchant was linked to this Stripe customer
- This is the
lastUpdatedtimestamp from the merchant profile
Important Notes:
- Only captures the last linking date - if a link is added and removed multiple times, only the most recent linking date is stored
- No history is maintained - previous linking/unlinking events are not tracked
- The timestamp is updated whenever:
- A merchant is linked to a Stripe customer
- A merchant is unlinked from a Stripe customer (set to null)
Note: This column may be a future addition. Currently, the lastUpdated field tracks when the merchant profile was last modified, which includes Stripe linking changes.
Linking Merchants to Stripe Customers
How It Works
- Click "Link" button in the Action column for a Stripe customer
- Modal opens showing all available FinMatch merchants
- Select a merchant from the dropdown
- Click "Link" to create the connection
Data Refresh During Linking
Current behavior:
- When you open the link modal, it uses already loaded Stripe data
- It does not fetch fresh data from Stripe for the specific customer
- It does not refresh the entire Stripe customer list
What happens:
- Stripe data is loaded when the page first loads (
loadStripeCustomers()) - The link modal displays the current Stripe customer data
- When you link a merchant, it:
- Updates
merchants.jsonin GCS with thestripeCustomerId - Refreshes the merchant data display
- Updates the table to show the linked merchant name
- Updates
Future enhancement:
- Option to fetch fresh Stripe data for the specific customer being linked
- Option to refresh entire Stripe customer list before linking
Refreshing the View
Manual Refresh:
- Use the refresh button in the dashboard settings
- Calls
loadStripeCustomers()which fetches all Stripe customers fresh from the API - Updates the entire table with latest data
Automatic Refresh:
- Currently, data is only refreshed on page load
- No automatic polling or background refresh exists
Data Mapping Reference
Stripe API → FinMatch Admin Table
| FinMatch Column | Stripe Field Path | Transformation |
|---|---|---|
| Customer ID | customer.id | Direct mapping |
| Name | customer.name or customer.description | Fallback to description if name missing |
customer.email | Direct mapping | |
| Subscription Plan | subscription.items.data[0].price.id | Extract price ID from first subscription item |
| Status | subscription.status | Map to display labels (Active, Past Due, etc.) |
| Next Billing | subscription.current_period_end | Convert Unix timestamp to date |
| Created | customer.created | Convert Unix timestamp to date |
| FinMatch Merchant | merchants.json[merchantId].stripeCustomerId | Match by customer ID |
| Last Link Date | merchants.json[merchantId].lastUpdated | When stripeCustomerId is set |
Status Mapping
{
'active': 'Active',
'trialing': 'Trialing',
'past_due': 'Past Due',
'canceled': 'Canceled',
'none': 'None' // No subscription exists
}
Technical Details
API Endpoint
Stripe API Service:
- Service:
finmatch-stripe-api(Cloud Run,europe-west2) - Read path:
GET /api/snapshots/latest(today), elsePOST /api/snapshots/create, elseGET /api/customers - Method: GET (snapshot/customers) / POST (create)
- Customers response:
{ success: true, customers: [...] }
Stripe API Call: stripe-api pages through customers.list (Stripe’s
page size is 100) until the live list is complete, expanding
data.subscriptions on each page. The dashboard Link Stripe picker and
the Stripe table use that full list, not the newest 100 only.
Data Transformation
The frontend transforms Stripe customer objects:
{
id: customer.id,
name: customer.name || customer.description || 'N/A',
email: customer.email || 'N/A',
subscriptionPlan: price.id || 'N/A',
subscriptionPlanId: price.id,
subscriptionStatus: subscription.status || 'none',
nextBillingDate: formatDate(subscription.current_period_end),
created: customer.created
}
Performance Considerations
- API Rate Limits: Stripe has rate limits on API calls
- Page size: Stripe still returns at most 100 customers per API call
- Pagination: Implemented in stripe-api.
GET /api/customerswalks every page so older billed merchants (for example Access Training,cus_ST325wcTyLcFPv) appear on the Stripe tab without Create Snapshot. There is a 10,000-customer safety cap. - Caching: stripe-api keeps a short-lived in-memory list (about 60s) and webhook upserts. The admin table polls that live list. Do not treat Create Snapshot as the refresh control.
Best Practices
- Reload the tab if the list looks stale: the Stripe tab polls about every 60 seconds. Do not press Create Snapshot to refresh.
- Check subscription status: Verify the Status column shows "Active" before linking to ensure valid billing
- Verify subscription plan: Ensure Subscription Plan is not "N/A" if you expect an active subscription
- Link one merchant per customer: Each Stripe customer should link to only one FinMatch merchant
Troubleshooting
"N/A" in Subscription Plan
- Cause: No active subscription exists
- Solution: Check Stripe dashboard to verify subscription status
- Action: May need to create or reactivate subscription in Stripe
"Not linked" in FinMatch Merchant
- Cause: No merchant has
stripeCustomerIdmatching this customer ID - Solution: Use the "Link" button to connect a merchant
- Action: Select merchant from dropdown and confirm link
Stale Data
- Cause: Data was loaded earlier and Stripe has been updated
- Solution: Click refresh button to fetch latest data from Stripe
- Action: Refresh will update entire customer list