Free Trial & Card Setup

The Concept: PaymentSetup

Sometimes you need the customer's card without charging them — a subscription with a free trial, a waitlist, or simply saving a card for later. Inflow does not create a €0 payment for this. Instead, the API creates a PaymentSetup (id setup_pm_req_...): a no-charge payment method collection that the SDK can complete exactly like a payment.

The flow mirrors the standard two-step flow:

  1. Your backend obtains a PaymentSetup id (setup_pm_req_...) — see the two ways below.
  2. Your frontend mounts the SDK CardElement with setupId instead of paymentId.
  3. The customer enters their card; the SDK tokenizes it, runs 3D Secure if needed, and saves it — no charge happens.
  4. Your app receives the result in onComplete, and your backend gets payment_setup.completed via webhooks.

paymentId and setupId are mutually exclusive on CardElement — pass exactly one.

Use Case 1: Subscription With a Free Trial (Free First Payment)

If your subscription offer has a trial period (trialPeriodDays), initiating it does not charge the customer. Instead of a payment, the API returns a PaymentSetup.

Step 1: Initiate the subscription (backend)

curl -X POST https://api.inflowpay.xyz/subscription/offer/{offerId}/initiate-server \
  -H "X-Inflow-Api-Key: your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "customerEmail": "[email protected]",
    "billingCountry": "FR",
    "purchasingAsBusiness": false,
    "firstName": "John",
    "lastName": "Doe"
  }'

No card field, default base URL — same rules as creating a payment for the SDK.

Step 2: Read the response

For a trial (or waitlist) offer, the response has type: "setup" and a paymentSetup instead of a payment:

{
  "type": "setup",
  "paymentSetup": {
    "id": "setup_pm_req_abc123",
    "status": "pending",
    "currency": "EUR",
    "subscriptionId": "sub_123"
  },
  "subscriptionId": "sub_123"
}

For offers without a trial, the same endpoint returns type: "payment" with a regular payment object — pass payment.id to the SDK as paymentId as usual.

Step 3: Collect the card with the SDK (frontend)

Pass the PaymentSetup id as setupId:

import { InflowPayProvider, CardElement, PaymentResultStatus } from '@inflow_pay/sdk/react';

<InflowPayProvider config={{ publicKey: 'inflow_pub_xxx' }}>
  <CardElement
    setupId="setup_pm_req_abc123"
    onComplete={(result) => {
      if (result.status === PaymentResultStatus.SUCCESS) {
        // Card saved — subscription is now in trial, no charge happened
      }
    }}
  />
</InflowPayProvider>

Vanilla JS works the same way with provider.createCardElement({ setupId, container, onComplete }) — see Vanilla JS Integration.

What happens next

  • The card is saved and the subscription becomes TRIAL.
  • The first charge happens automatically when the trial ends — that's your free first payment.
  • If the offer combines a trial with an entry fee, the entry fee is charged when the trial ends, before regular billing starts.
  • The customer can cancel during the trial without ever being charged.

Use Case 2: Save a Card Without a Subscription

To save a card on file outside of any subscription (e.g. for faster future checkouts), create a setup request directly:

curl -X POST https://api.inflowpay.xyz/api/customer/{customerEmail}/payment-methods \
  -H "X-Inflow-Api-Key: your_private_key" \
  -H "Content-Type: application/json" \
  -d '{ "currency": "EUR" }'

Response:

{
  "id": "setup_pm_req_abc123",
  "customerId": "cus_xyz789",
  "customerEmail": "[email protected]",
  "status": "pending",
  "currency": "EUR"
}

Pass id to the SDK as setupId (Step 3 above). Once completed, the saved card can be charged later with useCustomerPaymentMethod: true on server payments.

Tracking a PaymentSetup

A setup request moves through these statuses:

pending → requires_3ds (only if 3DS is needed) → completed
                                               ↘ failed

Track it from your backend:

WhatHow
List all setupsGET /payment-setups
Get one setupGET /payment-setups/{paymentSetupId}
Per-customer viewGET /api/customer/{customerEmail}/payment-methods/setup-requests
Push notificationsWebhooks payment_setup.created, payment_setup.completed, payment_setup.failed — see Webhooks

Prefer webhooks over polling: payment_setup.completed is the reliable signal that the card is saved (and, for trials, that the subscription is active).