Scenario
Pull disputes, categorize by reason code (product_not_received, duplicate, fraudulent, etc.), and use Mavera’s structured-output chat to rewrite product descriptions and checkout copy. JSON output feeds directly into a CMS or A/B test.
Architecture
Code
import os, json, 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"
def fetch_disputes():
cats = defaultdict(lambda: {"count": 0, "examples": []})
for d in stripe.Dispute.list(limit=100).auto_paging_iter():
cats[d.reason]["count"] += 1
if len(cats[d.reason]["examples"]) < 3:
cats[d.reason]["examples"].append({"dispute_id": d.id, "amount": d.amount / 100,
"product_description": (d.evidence or {}).get("product_description", "")})
return dict(cats)
def rewrite_copy_for_reason(reason, data):
schema = {"type": "object", "properties": {
"reason_code": {"type": "string"},
"original_issues": {"type": "array", "items": {"type": "string"}},
"revised_product_description": {"type": "string"},
"revised_checkout_message": {"type": "string"},
"revised_confirmation_email_snippet": {"type": "string"},
}, "required": ["reason_code", "original_issues", "revised_product_description",
"revised_checkout_message", "revised_confirmation_email_snippet"]}
resp = requests.post(f"{MAVERA_BASE}/responses", json={
"input": [
{"role": "system", "content": "You are a conversion copywriter reducing chargebacks."},
{"role": "user", "content": f"{data['count']} disputes for '{reason}':\n"
f"{json.dumps(data['examples'], indent=2)}\n\n"
"Rewrite the product description, checkout message, and confirmation email."},
],
"response_format": {"type": "json_schema", "json_schema": {"name": "dispute_fix", "schema": schema}},
"max_tokens": 1500,
}, headers={"Authorization": f"Bearer {MAVERA_API_KEY}"})
resp.raise_for_status()
return json.loads(resp.json()["choices"][0]["message"]["content"])
for reason, data in fetch_disputes().items():
if data["count"] >= 2:
print(f"\n=== {reason} ({data['count']}) ===")
print(json.dumps(rewrite_copy_for_reason(reason, data), indent=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";
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 fetchDisputes() {
const categories = {};
const data = await stripeGet("disputes", { limit: "100" });
for (const d of data.data) {
if (!categories[d.reason]) categories[d.reason] = { count: 0, examples: [] };
categories[d.reason].count++;
if (categories[d.reason].examples.length < 3)
categories[d.reason].examples.push({
dispute_id: d.id, amount: d.amount / 100,
product_description: d.evidence?.product_description || "",
});
}
return categories;
}
async function rewriteCopyForReason(reason, data) {
const fields = ["reason_code", "original_issues", "revised_product_description",
"revised_checkout_message", "revised_confirmation_email_snippet"];
const schema = { type: "object", required: fields, properties: Object.fromEntries(
fields.map((f) => [f, f === "original_issues" ? { type: "array", items: { type: "string" } } : { type: "string" }])
) };
const res = await fetch(`${MAVERA_BASE}/responses`, {
method: "POST",
headers: { Authorization: `Bearer ${MAVERA_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
input: [
{ role: "system", content: "You are a conversion copywriter reducing chargebacks." },
{ role: "user", content: `${data.count} disputes for '${reason}':\n${JSON.stringify(data.examples, null, 2)}\n\nRewrite the product description, checkout message, and confirmation email.` },
],
response_format: { type: "json_schema", json_schema: { name: "dispute_fix", schema } },
max_tokens: 1500,
}),
});
if (!res.ok) throw new Error(`Mavera ${res.status}: ${await res.text()}`);
return JSON.parse((await res.json()).output[0].content[0].text);
}
(async () => {
const categories = await fetchDisputes();
for (const [reason, data] of Object.entries(categories)) {
if (data.count >= 2) {
const fix = await rewriteCopyForReason(reason, data);
console.log(`\n=== ${reason} (${data.count}) ===\n${JSON.stringify(fix, null, 2)}`);
}
}
})();
Example Output
{
"reason_code": "product_not_received",
"original_issues": [
"Says 'instant delivery' but fulfillment takes 2-4 hours",
"No confirmation page after purchase"
],
"revised_product_description": "Digital access within 4 hours. Email with credentials and dashboard link.",
"revised_checkout_message": "Credentials arrive via email within 2-4 hours. Confirmation page appears immediately.",
"revised_confirmation_email_snippet": "Access provisioned — login details within 4 hours. Order #{{order_id}}."
}
Error Handling
Stripe: Dispute evidence object empty
Stripe: Dispute evidence object empty
Older disputes may have sparse
evidence. Skip empty product_description examples — reason code and amount alone produce useful recommendations.Mavera: JSON schema validation failure (400)
Mavera: JSON schema validation failure (400)
Simplify the schema and retry. Set
"strict": true inside json_schema to force adherence (may increase latency).