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 calls:
GET https://finmatch-stripe-api-3cgkzplynq-nw.a.run.app/api/customers - Stripe API service calls: Stripe's API directly using
stripe.customers.list()with expanded subscriptions - Data is transformed and displayed in the table immediately
No snapshot or cache is currently used - every page load results in a fresh API call to Stripe.
Data Flow
Admin Dashboard (Frontend)
↓
Stripe API Service (Cloud Run)
↓
Stripe API (External)
↓
Data Transformation
↓
Table Display
Refresh Capability
You can manually refresh the Stripe data at any time:
- Refresh Button: Available in the dashboard settings or Stripe page
- What it does: Calls
loadStripeCustomers()which fetches fresh data from the Stripe API - Scope: Refreshes all Stripe customers, not just individual records
Future: Daily Snapshot System
Note: A daily snapshot system is planned but not yet implemented. The proposed system would:
- Create daily snapshots of Stripe customer data stored in GCS
- Allow manual snapshot updates via admin action
- Provide faster page loads by serving from snapshot instead of live API calls
- Maintain data history for auditing and analysis
When implemented, the system will:
- Run a scheduled job (daily) to create snapshots
- Store snapshots in
gs://finmatch-shared/stripe-snapshots/ - Allow admins to trigger manual snapshot updates
- Fall back to live API if snapshot is unavailable
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:
- URL:
https://finmatch-stripe-api-3cgkzplynq-nw.a.run.app/api/customers - Method: GET
- Response:
{ success: true, customers: [...] }
Stripe API Call:
stripe.customers.list({
limit: 100,
expand: ['data.subscriptions']
})
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
- Current Limit: 100 customers per request
- Pagination: Not currently implemented (shows first 100 customers)
- Caching: No caching implemented (fresh fetch every time)
Best Practices
- Refresh before linking: If you need the latest Stripe data, refresh the table before linking
- 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