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.
Revenue Model Summary
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.
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.
Payment Flow Architecture
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).
Affiliate Commission Details
| Retailer | Network | Commission | Cookie Window | Payout |
|---|---|---|---|---|
| RevZilla | CJ Affiliate | 7% | 14 days | Monthly |
| Cycle Gear | CJ Affiliate | 7% | 14 days | Monthly |
| Rocky Mountain ATV/MC | Impact | 5% | 30 days | Monthly |
| Amazon | Amazon Associates | 1–3% | 24 hours | Monthly |
| Partzilla | ShareASale | 5% | 30 days | Monthly |
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.
Retailer Subscription Tiers
- ✓ Listed in price comparisons
- ✓ Standard affiliate tracking
- ✓ Basic click analytics
- ✓ No Stripe setup required
- ✓ Priority placement in results
- ✓ Fitment Intelligence Reports
- ✓ API access (read)
- ✓ Verified Partner badge
- ✓ 30-day free trial
- ✓ All Affiliate Partner features
- ✓ Full API access (read + write)
- ✓ White-label compatibility widget
- ✓ Manufacturer portal access
- ✓ Quarterly data review call
- ✓ All Verified Partner features
Stripe Billing Implementation
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'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.
.completed
retailer_subscriptions record. Set status to trialing or active. Store Stripe customer ID and subscription ID.active. Record payment in billing history. Confirm access is enabled for the billing period._failed
past_due. Send dunning email to retailer. Stripe handles retry schedule (Smart Retries)..updated
.deleted
canceled. Access continues until current_period_end. Do not revoke immediately.
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.
Billing Lifecycle State Machine
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.
Refund & Cancellation Policy
- 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.
- 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.
Tax & Compliance
- 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.
- 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.
- 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.
- 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.
Financial Reporting
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.
Implementation Approach
- 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_clickslogging on every outbound click. - Add FTC disclosure to all pages with affiliate links — required before launch.
- No Stripe setup needed in this phase.
- 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_subscriptionstable. Implement access-gating middleware. - 30-day free trial for Verified Partner — set at Stripe Price level.
- 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; }