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:
- Your backend obtains a PaymentSetup id (
setup_pm_req_...) — see the two ways below. - Your frontend mounts the SDK
CardElementwithsetupIdinstead ofpaymentId. - The customer enters their card; the SDK tokenizes it, runs 3D Secure if needed, and saves it — no charge happens.
- Your app receives the result in
onComplete, and your backend getspayment_setup.completedvia webhooks.
paymentIdandsetupIdare mutually exclusive onCardElement— 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 regularpaymentobject — passpayment.idto the SDK aspaymentIdas 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
↘ failedTrack it from your backend:
| What | How |
|---|---|
| List all setups | GET /payment-setups |
| Get one setup | GET /payment-setups/{paymentSetupId} |
| Per-customer view | GET /api/customer/{customerEmail}/payment-methods/setup-requests |
| Push notifications | Webhooks payment_setup.created, payment_setup.completed, payment_setup.failed — see Webhooks |
Prefer webhooks over polling:
payment_setup.completedis the reliable signal that the card is saved (and, for trials, that the subscription is active).