Internal Architecture Doc

Payment Architecture
MotoPartPicker

Two revenue streams, zero payment processing by MotoPartPicker. Affiliate commissions are the primary driver — Stripe handles retailer subscriptions. Riders always ride free.

Affiliate Commissions
Stripe Subscriptions
Riders Free Forever
SAQ A PCI Scope
5–7%
Affiliate Commission
$299
Verified Partner / mo
$499
Data Partner / mo
$0
Rider Cost — Always
01

Revenue Model Summary

Stream 1 — Primary
Affiliate Commissions

Riders click "Buy at [Retailer]" buttons. MotoPartPicker appends affiliate tracking parameters to the retailer URL. When the rider completes a purchase, the affiliate network pays MotoPartPicker a commission. No payment processing on our side — we never touch rider payment data.

Rate 1–7% of purchase value
Networks CJ, Impact, ShareASale, Amazon
Payout Monthly
PCI scope None — zero card data
Stream 2 — Secondary
Retailer Subscriptions

Retailers pay a monthly or annual fee for premium placement, enhanced analytics, API access, and partner benefits. Billing is managed entirely by Stripe — checkout, invoicing, dunning, the customer portal. MotoPartPicker syncs state from Stripe webhooks.

Entry tier Free (Affiliate Partner)
Mid tier $299 / mo (Verified Partner)
Top tier $499 / mo (Data Partner)
Processor Stripe Billing
🏍️
Riders pay nothing — ever. MotoPartPicker is free to use for every rider. Revenue is funded by retailers, not the community. This is a core product principle.
02

Payment Flow Architecture

Affiliate Commission Flow
Rider Clicks "Buy" click MotoPartPicker tags affiliate URL redirect Retailer processes payment sale reported Affiliate Network CJ / ShareASale / Impact tracks & attributes Monthly payout to MotoPartPicker affiliate_clicks internal analytics log
Retailer Subscription Flow
Retailer visits pricing page select tier Stripe Checkout hosted, PCI-compliant no card data touches us payment Stripe Billing recurring charges, dunning, invoices webhook MotoPartPicker processes webhook events grant retailer_sub scriptions access granted
Critical architecture note

MotoPartPicker never handles rider payment information. Affiliate tracking is entirely URL-parameter based — we log the outbound click for analytics, not any payment data. For retailer subscriptions, Stripe Checkout is the hosted page; our server only receives webhook events after the fact. This keeps us at the lowest possible PCI scope (SAQ A).

03

Affiliate Commission Details

Retailer Network Commission Cookie Window Payout
RevZilla CJ Affiliate 7% Monthly
Cycle Gear CJ Affiliate 7% Monthly
Rocky Mountain ATV/MC Impact 5% Monthly
Amazon Amazon Associates 1–3% Monthly
Partzilla ShareASale 5% Monthly
Tracking Implementation

Every "Buy at [Retailer]" button constructs a URL by appending the affiliate network's required parameters to the retailer's product URL. MotoPartPicker logs the outbound click in the affiliate_clicks table for internal analytics (part ID, retailer, timestamp, user session). Revenue attribution is via each affiliate network's reporting dashboard — we are not the source of truth for commission amounts.

Cookie windows vary significantly — Amazon's 24-hour window is the most limiting. This influences how we prioritize retailer placement: longer cookie windows mean better attribution for high-consideration purchases.

FTC Compliance Requirement Every page displaying affiliate links must include the following disclosure: "MotoPartPicker earns a commission when you purchase through our links. This never affects our compatibility data." This disclosure must be visible without scrolling and must not be hidden in footnotes. Non-negotiable — failure to disclose is an FTC violation.
04

Retailer Subscription Tiers

Affiliate Partner
Free / mo
No commitment

  • Listed in price comparisons
  • Standard affiliate tracking
  • Basic click analytics
  • No Stripe setup required
Data Partner
$499 / mo
$399/mo billed annually — save 20%

  • Full API access (read + write)
  • White-label compatibility widget
  • Manufacturer portal access
  • Quarterly data review call
  • All Verified Partner features
Annual billing discount: 20% off. Verified Partner drops to $239/mo — Data Partner to $399/mo. Both billed as a single annual charge. Annual cancellations receive a prorated refund if canceled within the first 30 days.
05

Stripe Billing Implementation

🔒
Stripe Checkout

Stripe's hosted checkout page handles new subscriptions. Simplest integration — zero card data touches MotoPartPicker's server. Fully PCI-compliant out of the box. We create a Checkout Session server-side with the selected Price ID and redirect the retailer. On success, we receive the checkout.session.completed webhook.

⚙️
Stripe Customer Portal

Stripe's hosted portal handles all self-service billing management: upgrade/downgrade tier, change payment method, view invoice history, and cancel subscription. MotoPartPicker creates a portal session and redirects the retailer. No custom billing UI needed — Stripe maintains it.

WH
Webhook Events to Handle
checkout.session
.completed
Create retailer_subscriptions record. Set status to trialing or active. Store Stripe customer ID and subscription ID.
invoice.paid
Update subscription status to active. Record payment in billing history. Confirm access is enabled for the billing period.
invoice.payment
_failed
Update status to past_due. Send dunning email to retailer. Stripe handles retry schedule (Smart Retries).
customer.subscription
.updated
Handle tier upgrade or downgrade. Update local plan/tier reference. Adjust feature access immediately on upgrade, at period end on downgrade.
customer.subscription
.deleted
Update status to canceled. Access continues until current_period_end. Do not revoke immediately.
retailer_subscriptions
id uuid primary key
retailer_id uuid fk → retailers
stripe_customer_id text cus_xxx
stripe_subscription_id text sub_xxx
plan enum affiliate / verified / data
status enum trialing/active/past_due/canceled
current_period_end timestamp access gate
created_at / updated_at timestamp
affiliate_clicks
id uuid primary key
part_id uuid fk → parts
retailer_id uuid fk → retailers
session_id text anonymous session
affiliate_network enum cj / impact / shareasale / amazon
destination_url text full tagged URL
bike_make / model / year text for analytics segmentation
clicked_at timestamp indexed

Stripe is the source of truth for subscription state. The local retailer_subscriptions table is a read cache for fast access gate checks. Always reconcile against Stripe when in doubt.

06

Billing Lifecycle State Machine

TRIALING 30 days ACTIVE access granted PAST DUE dunning active CANCELED no access S trial ends renewal payment fails retry succeeds retries 1,2,3 exhausted user cancels (access until period end) resubscribes LEGEND Happy path Payment failure Retry success User action Resubscribe

Stripe Smart Retries manages the dunning schedule automatically — it uses ML to pick optimal retry times. MotoPartPicker receives webhook events for each attempt. On three consecutive failures, Stripe marks the subscription canceled. For grace period UX, access remains until current_period_end even when status = past_due.

07

Refund & Cancellation Policy

Monthly Subscriptions
  • Cancel anytime — no cancellation fee, no penalty.
  • Access continues until the end of the current billing period.
  • No prorated refunds for partial months.
  • Cancellation is one-click in the Stripe Customer Portal.
Annual Subscriptions
  • Cancel anytime — access continues to end of annual period.
  • Prorated refund available if canceled within first 30 days.
  • No refund after 30-day window has passed.
  • Refunds processed via Stripe — 5–10 business days.
"We want partners, not hostages. Cancel with one click in the Stripe Customer Portal."
08

Tax & Compliance

Tax Collection
  • Stripe Tax auto-calculates sales tax and VAT for all retailer subscriptions.
  • Tax is applied at checkout based on the retailer's billing address.
  • Stripe remits collected tax where applicable (Stripe Tax Remittance).
  • Stripe auto-generates compliant invoices for every charge.
PCI Compliance
  • MotoPartPicker is SAQ A — the lowest PCI DSS scope.
  • Stripe Checkout handles all cardholder data. Zero card data on our servers.
  • Affiliate flows involve no payment data at all.
  • Annual SAQ A self-assessment questionnaire required.
Money Transmission
  • MotoPartPicker is not a marketplace. No rider payments are processed.
  • No money transmission license required — we are an affiliate referrer.
  • Retailers set their own prices; MotoPartPicker never quotes a price.
  • Affiliate commissions are income to MotoPartPicker, not payouts to others.
Reporting & 1099s
  • No 1099 obligations — MotoPartPicker receives commissions, does not pay them out.
  • Affiliate networks (CJ, Impact, etc.) issue 1099s to MotoPartPicker if applicable.
  • Stripe generates consolidated financial reports for subscription revenue.
  • Invoicing: Stripe auto-generates and emails invoices to retailers on every charge.
09

Financial Reporting

💰
Affiliate Revenue
Total commissions earned. Broken down by retailer, bike category, and part type.
Monthly
📈
Subscription MRR
Monthly Recurring Revenue from retailer subscriptions, segmented by tier.
Monthly
🎯
Affiliate RPV
Revenue Per Visitor — key efficiency metric for affiliate monetization.
Daily
🔗
Click-Through Rate
Affiliate link CTR by part category and retailer. Identifies optimization opportunities.
Daily
📉
Churn Rate
Monthly churn for retailer subscriptions. Target: keep below 3% monthly.
Monthly
🏍️
Top Parts / Bikes
Highest-revenue parts and bike configurations. Guides content and SEO investment.
Weekly
DB
Admin Dashboard Scope

A lightweight internal admin page (not public-facing) displays: current MRR, affiliate revenue MTD, top 10 performing parts by click volume, top 5 bikes by revenue, and monthly churn. Data sourced from affiliate_clicks (internal) and Stripe Dashboard API (subscription metrics). No third-party analytics platform required at MVP.

10

Implementation Approach

Phase 1 — Affiliate Only
Month 1–6
  • Apply for affiliate accounts: CJ Affiliate (RevZilla, Cycle Gear), Impact (Rocky Mountain), Amazon Associates, ShareASale (Partzilla).
  • Build affiliate link generator utility — appends correct tracking parameters per retailer/network.
  • Implement affiliate_clicks logging on every outbound click.
  • Add FTC disclosure to all pages with affiliate links — required before launch.
  • No Stripe setup needed in this phase.
Phase 2 — Retailer Subscriptions
Month 6–12
  • Set up Stripe account. Create Verified Partner product and Price objects (monthly + annual).
  • Build server-side endpoint: POST /api/billing/checkout → creates Stripe Checkout Session.
  • Build server-side endpoint: POST /api/billing/portal → creates Stripe Customer Portal Session.
  • Implement webhook handler at POST /api/webhooks/stripe — verify signature, handle all 5 events.
  • Create retailer_subscriptions table. Implement access-gating middleware.
  • 30-day free trial for Verified Partner — set at Stripe Price level.
Phase 3 — Scale & Optimization
Month 12+
  • Add Data Partner tier to Stripe — create product, prices (monthly + annual).
  • Enable Stripe Tax — configure in Stripe Dashboard, set product tax codes.
  • Annual billing option — add annual Price objects with 20% discount.
  • Build admin financial dashboard — MRR, affiliate revenue, churn, top parts.
  • White-label widget delivery infrastructure for Data Partner tier.
// Key Stripe API calls (TypeScript / Node.js)

// 1. Create Checkout Session (new subscription)
const session = await stripe.checkout.sessions.create({
  mode:       'subscription',
  customer:   retailer.stripe_customer_id,       // create if new
  line_items: [{ price: PRICE_ID, quantity: 1 }],
  trial_period_days: 30,                          // Verified Partner only
  success_url: `${BASE_URL}/billing/success?session_id={CHECKOUT_SESSION_ID}`,
  cancel_url:  `${BASE_URL}/pricing`,
});

// 2. Create Customer Portal Session (self-service billing)
const portal = await stripe.billingPortal.sessions.create({
  customer:   retailer.stripe_customer_id,
  return_url: `${BASE_URL}/dashboard`,
});

// 3. Webhook handler (verify signature first)
const event = stripe.webhooks.constructEvent(body, sig, WEBHOOK_SECRET);
switch (event.type) {
  case 'checkout.session.completed':  await handleCheckoutComplete(event); break;
  case 'invoice.paid':                 await handleInvoicePaid(event);       break;
  case 'invoice.payment_failed':        await handlePaymentFailed(event);    break;
  case 'customer.subscription.updated': await handleSubUpdated(event);       break;
  case 'customer.subscription.deleted': await handleSubDeleted(event);       break;
}