Skip to main content

Scenario

Analyze charges with expanded payment methods to extract billing countries. Aggregate by region and create Mavera personas for each market, enriched with language preferences and cultural context.

Architecture

Code

import os, time, stripe, requests
from collections import defaultdict

stripe.api_key = os.environ["STRIPE_API_KEY"]
MAVERA_API_KEY = os.environ["MAVERA_API_KEY"]
MAVERA_BASE = "https://app.mavera.io/api/v1"

REGION_MAP = {"US": "North America", "CA": "North America", "MX": "North America",
    "GB": "Europe", "DE": "Europe", "FR": "Europe", "NL": "Europe",
    "JP": "Asia-Pacific", "AU": "Asia-Pacific", "SG": "Asia-Pacific",
    "BR": "Latin America", "AR": "Latin America", "CO": "Latin America"}

def aggregate_by_region():
    regions = defaultdict(lambda: {"count": 0, "revenue": 0, "countries": defaultdict(int)})
    for ch in stripe.Charge.list(limit=100, expand=["data.payment_method"]).auto_paging_iter():
        country = (ch.payment_method_details or {}).get("card", {}).get("country") or \
                  ((ch.billing_details or {}).get("address") or {}).get("country")
        if not country:
            continue
        region = REGION_MAP.get(country, "Other")
        regions[region]["count"] += 1
        regions[region]["revenue"] += ch.amount / 100
        regions[region]["countries"][country] += 1
    return dict(regions)

def create_market_persona(region, stats):
    top = sorted(stats["countries"].items(), key=lambda x: -x[1])[:5]
    resp = requests.post(f"{MAVERA_BASE}/personas", json={
        "name": f"{region} Market Persona",
        "description": f"{region}: {stats['count']} charges, ${stats['revenue']:,.0f} revenue. Top: {', '.join(c for c, _ in top)}.",
        "attributes": {"region": region, "total_charges": stats["count"],
                        "total_revenue": round(stats["revenue"], 2), "country_breakdown": dict(top)},
    }, headers={"Authorization": f"Bearer {MAVERA_API_KEY}"})
    resp.raise_for_status()
    return resp.json()

for region, stats in aggregate_by_region().items():
    if stats["count"] >= 10:
        r = create_market_persona(region, stats)
        print(f"Created: {r['id']}{region} ({stats['count']} charges)")
        time.sleep(0.2)
const STRIPE_API_KEY = process.env.STRIPE_API_KEY;
const MAVERA_API_KEY = process.env.MAVERA_API_KEY;
const MAVERA_BASE = "https://app.mavera.io/api/v1";

const REGION_MAP = { US: "North America", CA: "North America", MX: "North America",
  GB: "Europe", DE: "Europe", FR: "Europe", NL: "Europe",
  JP: "Asia-Pacific", AU: "Asia-Pacific", SG: "Asia-Pacific",
  BR: "Latin America", AR: "Latin America", CO: "Latin America" };

async function stripeGet(path, params = {}) {
  const url = new URL(`https://api.stripe.com/v1/${path}`);
  Object.entries(params).forEach(([k, v]) => url.searchParams.append(k, v));
  const res = await fetch(url, { headers: { Authorization: `Bearer ${STRIPE_API_KEY}` } });
  if (!res.ok) throw new Error(`Stripe ${res.status}: ${await res.text()}`);
  return res.json();
}

async function aggregateByRegion() {
  const regions = {};
  const data = await stripeGet("charges", { limit: "100", "expand[]": "data.payment_method" });
  for (const ch of data.data) {
    const country = ch.payment_method_details?.card?.country || ch.billing_details?.address?.country;
    if (!country) continue;
    const region = REGION_MAP[country] || "Other";
    if (!regions[region]) regions[region] = { count: 0, revenue: 0, countries: {} };
    regions[region].count++;
    regions[region].revenue += ch.amount / 100;
    regions[region].countries[country] = (regions[region].countries[country] || 0) + 1;
  }
  return regions;
}

async function createMarketPersona(region, stats) {
  const top = Object.entries(stats.countries).sort((a, b) => b[1] - a[1]).slice(0, 5);
  const res = await fetch(`${MAVERA_BASE}/personas`, {
    method: "POST",
    headers: { Authorization: `Bearer ${MAVERA_API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({
      name: `${region} Market Persona`,
      description: `${region}: ${stats.count} charges, $${stats.revenue.toFixed(0)} total. Top: ${top.map(([c]) => c).join(", ")}.`,
      attributes: { region, total_charges: stats.count, total_revenue: Math.round(stats.revenue * 100) / 100,
        country_breakdown: Object.fromEntries(top) },
    }),
  });
  if (!res.ok) throw new Error(`Mavera ${res.status}: ${await res.text()}`);
  return res.json();
}

(async () => {
  const regions = await aggregateByRegion();
  for (const [region, stats] of Object.entries(regions)) {
    if (stats.count >= 10) {
      const r = await createMarketPersona(region, stats);
      console.log(`Created: ${r.id}${region} (${stats.count} charges)`);
    }
  }
})();

Example Output

{
  "id": "per_g7h8i9j0",
  "name": "Europe Market Persona",
  "description": "Customers from Europe: 312 charges, $87,430 total revenue. Top: GB, DE, FR, NL, ES.",
  "attributes": { "region": "Europe", "total_charges": 312, "total_revenue": 87430.0,
    "country_breakdown": { "GB": 124, "DE": 89, "FR": 52, "NL": 31, "ES": 16 } },
  "created_at": "2026-03-17T14:35:12Z"
}

Error Handling

Legacy charges may lack payment_method_details. Fall back to billing_details.address.country and skip charges with no geographic signal.
Use PUT to update existing personas, or append a timestamp for versioned snapshots (e.g., "Europe Market Persona — 2026-03").