Public beta

Payments

Zippex collects payment for every delivery created through the API. Your customer is charged once for items plus the delivery fee; Zippex remits your share to your Stripe account, keeps a volume-tiered service fee, and handles the dispatch and fulfillment of the delivery on its own side. There is a single payment flow: create a checkout → collect payment → create the delivery.

Universal: just open a URL

Every successful POST /v1/checkouts returns a checkout_url. Open it in a browser, WebView, Safari View Controller, or Chrome Custom Tab — Zippex hosts a Stripe-powered payment page that works identically on web, iOS, and Android. Pass success_url / cancel_url and the customer is redirected back to your app afterwards. No Stripe SDK, no PaymentSheet wiring, no platform-specific code.

Prefer a native sheet inside your app? The same response also returns client_secret, customer, ephemeral_key, and publishable_key, so you can drop straight into Stripe's SDK — see Advanced: native SDK.

How money flows

Your customer is charged items_subtotal + delivery_fee. At delivery completion the funds settle automatically:

  • You — your items subtotal, minus the Zippex service fee, transferred to your connected Stripe account (your “net share”).
  • Zippex — keeps its own cut of the delivery fee and the service fee. Driver payout is handled internally by Zippex.

The service fee is taken from your share, not added on top of what the customer pays.

Service fee

The service fee is a percentage of your items subtotal and decreases as your monthly delivery volume grows. Higher volume, lower fee.

Deliveries / 30 daysService fee
0 – 9910%
100 – 4998%
500 – 1,9996%
2,000+4%

High-volume accounts can negotiate a custom rate, contact support@zippex.com.

Prerequisite: connect Stripe

You must complete Stripe Connect onboarding before you can collect payments, this is the account your net share is paid out to.

  1. Go to Dashboard → Payouts → Connect Stripe.
  2. Complete the Stripe Express onboarding flow.

Connect required

Checkout creation returns 403 stripe_not_connected until onboarding is complete.

Step 1 — Create a checkout

POST/v1/checkouts

Server-side: creates a Stripe PaymentIntent for items + delivery and returns the credentials your client app needs to display Stripe's payment sheet.

What this endpoint actually does

This is a server-to-server call. It creates a Stripe PaymentIntent on Zippex's platform Stripe account and returns:
  • client_secret — used by Stripe's SDK on your client to render the payment UI
  • customer + ephemeral_key — required by mobile Stripe PaymentSheet
  • publishable_key — the public Stripe key your client SDK initializes with
  • checkout_id — pass this to /v1/deliveries after the customer pays

Zippex does not host a payment page — you render Stripe's sheet inside your own app using these values in Step 2.

Parameters

quote_idstringrequired

A valid, unused quote ID from POST /v1/quotes. The quote provides the authoritative delivery fee.

items_subtotalintegeroptional

Goods price in cents (e.g. 4500 = $45.00). Defaults to 0 for delivery-only. Your net payout is this amount minus the service fee.

success_urlstringoptional

Absolute http(s) URL the hosted checkout redirects to after a successful payment. Recommended — gives you a clean handoff back to your app.

cancel_urlstringoptional

Absolute http(s) URL the hosted checkout sends the customer to if they cancel.

customer.namestringoptional

Customer name for the payment receipt.

customer.emailstringoptional

Customer email for the receipt. Reuses an existing Stripe Customer when matched.

customer.phonestringoptional

Customer phone for the receipt.

stripe_versionstringoptional

Advanced — only needed if you render a native Stripe PaymentSheet yourself. Pass your mobile SDK apiVersion to receive an ephemeral_key.

curl -X POST https:"color:#6a9955">//developer.zippex.app/api/v1/checkouts \
  -H "Authorization: Bearer zx_live_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "quote_id": "qt_dr_4f8a2b1c0d3e6f",
    "items_subtotal": 4500,
    "success_url": "https://yourapp.com/orders/123/confirmed",
    "cancel_url":  "https://yourapp.com/cart",
    "customer": { "name": "Jane Smith", "email": "jane@example.com" }
  }'
201 Created
{
  "checkout_id": "chk_dr_abc123def456",
  "checkout_url": "https://developer.zippex.app/checkout/chk_dr_abc123def456",
  "client_secret": "pi_xxx_secret_yyy",
  "payment_intent_id": "pi_xxx",
  "customer": "cus_xxx",
  "ephemeral_key": null,
  "publishable_key": "pk_live_xxx",
  "quote_id": "qt_dr_4f8a2b1c0d3e6f",
  "currency": "cad",
  "amount": 5749,
  "delivery_fee": 1249,
  "items_subtotal": 4500,
  "expires_at": "2026-05-21T14:30:00Z"
}

Step 2 — Open the hosted checkout

The checkout_url from Step 1 is a Zippex-hosted page that renders Stripe's Payment Element — cards, Apple Pay, Google Pay, Link, and every other method you've enabled. It works on any platform that can open a URL. Send the customer there, they pay, Stripe redirects them to your success_url. Done.

Web

"color:#6a9955">// Same-tab redirect
window.location.href = checkout.checkout_url;

"color:#6a9955">// Or a new tab
window.open(checkout.checkout_url, '_blank');

iOS — SFSafariViewController

import SafariServices

"color:#6a9955">// The recommended way to open URLs on iOS — full Stripe UI, Apple Pay,
"color:#6a9955">// and your success_url is delivered back to your app universal link.
let vc = SFSafariViewController(url: URL(string: checkout.checkoutUrl)!)
present(vc, animated: true)

Android — Chrome Custom Tabs

import androidx.browser.customtabs.CustomTabsIntent

"color:#6a9955">// Renders the checkout in an in-app browser tab with shared cookies,
"color:#6a9955">// Google Pay support, and a smooth return to your app via the success_url
"color:#6a9955">// (use an app link / deep link for the cleanest handoff).
CustomTabsIntent.Builder().build()
    .launchUrl(context, Uri.parse(checkout.checkoutUrl))

React Native / Expo

import * as WebBrowser from 'expo-web-browser';

"color:#6a9955">// openAuthSessionAsync auto-closes the browser once it hits success_url,
"color:#6a9955">// then returns control to your app. Make success_url a deep link / universal
"color:#6a9955">// link into your app (e.g. yourapp://orders/123/confirmed).
const result = await WebBrowser.openAuthSessionAsync(
  checkout.checkout_url,
  'yourapp://orders/123/confirmed', // matches success_url
);
if (result.type === 'success') {
  "color:#6a9955">// Customer paid — call POST /v1/deliveries with checkout.checkout_id
}

What the customer sees

A page branded as “Payment to Your Company via Zippex”, with the order total, the Stripe Payment Element, and your Cancel link if you provided cancel_url. The page already handles 3-D Secure, Link, wallet sheets, error states, and expiry. There is nothing else to build.

Advanced — Render Stripe's sheet natively

You only need this if you want the sheet inside your own app shell without ever leaving it (typical for high-volume merchants who care about every conversion percent). The same /v1/checkouts response gives you client_secret, customer, ephemeral_key, and publishable_key. Hand those to Stripe's official SDK on each platform.

Common reasons the native sheet doesn't appear

  1. You called initPaymentSheet but never called presentPaymentSheet() / confirmPayment().
  2. You passed payment_intent_id instead of client_secret.
  3. You forgot to send stripe_version in the checkout body, so ephemeral_key came back null and the init failed silently.
  4. You initialized Stripe with your own publishable key instead of the publishable_key returned by Zippex (the PaymentIntent lives on the Zippex platform account).

Web — Stripe.js + PaymentElement

Install @stripe/stripe-js (or load https://js.stripe.com/v3/):

npm install @stripe/stripe-js
import { loadStripe } from '@stripe/stripe-js';

"color:#6a9955">// 1. Create the checkout on your server, return values to the browser
const checkout = await fetch('/your-server/create-checkout', { method: 'POST' }).then(r => r.json());

"color:#6a9955">// 2. Initialize Stripe with the publishable key Zippex returned
const stripe = await loadStripe(checkout.publishable_key);

"color:#6a9955">// 3. Mount the Payment Element
const elements = stripe.elements({ clientSecret: checkout.client_secret });
const paymentElement = elements.create('payment');
paymentElement.mount('#payment-element'); // <div id="payment-element"></div>

"color:#6a9955">// 4. Confirm the payment when the customer clicks "Pay"
const { error, paymentIntent } = await stripe.confirmPayment({
  elements,
  confirmParams: { return_url: window.location.origin + '/order-confirmed' },
  redirect: 'if_required',
});

if (!error && paymentIntent.status === 'succeeded') {
  "color:#6a9955">// 5. Now call POST /v1/deliveries with checkout.checkout_id
}

iOS — Stripe iOS SDK (Swift)

Add via Swift Package Manager: https://github.com/stripe/stripe-ios (StripePaymentSheet product).

import StripePaymentSheet

"color:#6a9955">// 1. Create the checkout from your server, passing the iOS SDK apiVersion
"color:#6a9955">//    The current SDK exposes STPAPIClient.STPSDKVersion, but Stripe pins
"color:#6a9955">//    apiVersion to "2020-08-27" for ephemeral keys — pass that.
let checkout = try await yourServer.createCheckout(
    quoteId: quoteId,
    itemsSubtotal: 4500,
    stripeVersion: "2020-08-27"
)

"color:#6a9955">// 2. Configure the PaymentSheet with the Zippex publishable key
STPAPIClient.shared.publishableKey = checkout.publishableKey

var config = PaymentSheet.Configuration()
config.merchantDisplayName = "Your Store"
config.customer = .init(id: checkout.customer, ephemeralKeySecret: checkout.ephemeralKey)
config.applePay = .init(merchantId: "merchant.com.yourapp", merchantCountryCode: "CA")
config.allowsDelayedPaymentMethods = true

let paymentSheet = PaymentSheet(
    paymentIntentClientSecret: checkout.clientSecret,
    configuration: config
)

"color:#6a9955">// 3. Present from your view controller
paymentSheet.present(from: self) { result in
    switch result {
    case .completed:
        "color:#6a9955">// Call POST /v1/deliveries with checkout.checkoutId
    case .canceled: break
    case .failed(let error): print(error)
    }
}

Android — Stripe Android SDK (Kotlin)

Add to build.gradle:

dependencies {
    implementation 'com.stripe:stripe-android:21.+'
}
import com.stripe.android.PaymentConfiguration
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.PaymentSheetResult

class CheckoutActivity : AppCompatActivity() {
    private lateinit var paymentSheet: PaymentSheet

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        paymentSheet = PaymentSheet(this, ::onPaymentResult)

        lifecycleScope.launch {
            "color:#6a9955">// 1. Create checkout via your server, send the Android SDK apiVersion
            val checkout = yourServer.createCheckout(
                quoteId = quoteId,
                itemsSubtotal = 4500,
                stripeVersion = "2020-08-27"
            )

            "color:#6a9955">// 2. Initialize Stripe with the Zippex publishable key
            PaymentConfiguration.init(applicationContext, checkout.publishableKey)

            "color:#6a9955">// 3. Present the sheet
            paymentSheet.presentWithPaymentIntent(
                checkout.clientSecret,
                PaymentSheet.Configuration(
                    merchantDisplayName = "Your Store",
                    customer = PaymentSheet.CustomerConfiguration(
                        id = checkout.customer,
                        ephemeralKeySecret = checkout.ephemeralKey
                    ),
                    googlePay = PaymentSheet.GooglePayConfiguration(
                        environment = PaymentSheet.GooglePayConfiguration.Environment.Production,
                        countryCode = "CA",
                        currencyCode = "CAD"
                    )
                )
            )
        }
    }

    private fun onPaymentResult(result: PaymentSheetResult) {
        when (result) {
            is PaymentSheetResult.Completed -> { /* call POST /v1/deliveries */ }
            is PaymentSheetResult.Canceled -> {}
            is PaymentSheetResult.Failed -> Log.e("Stripe", "Error", result.error)
        }
    }
}

React Native — @stripe/stripe-react-native

Cross-platform sheet that renders the native iOS / Android UI under the hood.

npm install @stripe/stripe-react-native
cd ios && pod install
import { StripeProvider, useStripe } from '@stripe/stripe-react-native';

"color:#6a9955">// Wrap your root component once
export default function App() {
  const [publishableKey, setPublishableKey] = useState('');
  return (
    <StripeProvider publishableKey={publishableKey} merchantIdentifier="merchant.com.yourapp">
      <Checkout onPublishableKey={setPublishableKey} />
    </StripeProvider>
  );
}

function Checkout({ onPublishableKey }) {
  const { initPaymentSheet, presentPaymentSheet } = useStripe();

  async function openSheet() {
    "color:#6a9955">// 1. Create the checkout — send the SDK's apiVersion
    const checkout = await fetch('https:"color:#6a9955">//your-server/create-checkout', {
      method: 'POST',
      body: JSON.stringify({
        quote_id, items_subtotal: 4500,
        stripe_version: '2020-08-27', // matches @stripe/stripe-react-native
      }),
    }).then(r => r.json());

    // 2. Hand Stripe the publishable key from Zippex
    onPublishableKey(checkout.publishable_key);

    // 3. Init + present the sheet (this renders the iOS / Android native UI)
    const init = await initPaymentSheet({
      merchantDisplayName: 'Your Store',
      customerId: checkout.customer,
      customerEphemeralKeySecret: checkout.ephemeral_key,
      paymentIntentClientSecret: checkout.client_secret,
      allowsDelayedPaymentMethods: true,
      applePay: { merchantCountryCode: 'CA' },
      googlePay: { merchantCountryCode: 'CA', testEnv: __DEV__, currencyCode: 'CAD' },
    });
    if (init.error) { console.error(init.error); return; }

    const { error } = await presentPaymentSheet();
    if (!error) {
      "color:#6a9955">// 4. Call POST /v1/deliveries with checkout.checkout_id
    }
  }

  return <Button onPress={openSheet} title="Pay" />;
}

Flutter — flutter_stripe

import 'package:flutter_stripe/flutter_stripe.dart';

Future<void> openSheet() async {
  final checkout = await yourServer.createCheckout(
    quoteId: quoteId, itemsSubtotal: 4500, stripeVersion: '2020-08-27',
  );

  Stripe.publishableKey = checkout.publishableKey;

  await Stripe.instance.initPaymentSheet(
    paymentSheetParameters: SetupPaymentSheetParameters(
      merchantDisplayName: 'Your Store',
      paymentIntentClientSecret: checkout.clientSecret,
      customerId: checkout.customer,
      customerEphemeralKeySecret: checkout.ephemeralKey,
      applePay: const PaymentSheetApplePay(merchantCountryCode: 'CA'),
      googlePay: const PaymentSheetGooglePay(merchantCountryCode: 'CA', currencyCode: 'CAD'),
    ),
  );
  await Stripe.instance.presentPaymentSheet();
  "color:#6a9955">// Then POST /v1/deliveries with checkout.checkoutId
}

Apple Pay & Google Pay

The sheet shows Apple Pay / Google Pay automatically once you configure your platform's wallet credentials (Apple merchant ID in Xcode capabilities, Google Pay merchant ID in your Play console). No extra Zippex setup is needed — wallets are paid into the same PaymentIntent.

Test mode

With a zx_test_ key, /v1/checkouts creates a real test-mode PaymentIntent — the sheet renders normally, but no money moves. Use card 4242 4242 4242 4242 (any future expiry, any CVC). Stripe Connect onboarding is not required in test mode. Pass the resulting checkout_id to a test-mode /v1/deliveries call to exercise the full pay → deliver loop.

Step 3 — Create the delivery

After the customer pays, create the delivery with the checkout_id. Zippex verifies the payment succeeded, then dispatches a driver. See Deliveries for the full reference.

curl -X POST https:"color:#6a9955">//developer.zippex.app/api/v1/deliveries \
  -H "Authorization: Bearer zx_live_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "quote_id": "qt_dr_4f8a2b1c0d3e6f",
    "checkout_id": "chk_dr_abc123def456",
    "customer": { "name": "Jane Smith", "phone": "+16045550456" }
  }'

Pay before you dispatch

Live deliveries require a paid checkout_id. Calling /v1/deliveries without one returns 400 missing_checkout; with an unpaid checkout it returns 402 checkout_not_paid.

Payouts

Your net share is transferred to your connected Stripe Express account when the delivery completes, then paid out on your Stripe schedule (typically 2 business days).

  • View payout history in Dashboard → Payouts.
  • Open your Stripe Dashboard from the Payouts page for detailed transaction reports.