Accept recurring payments

Enroll a customer into a fixed-amount recurring subscription with Fintoc's Checkout Session API.

Build a subscription flow that enrolls a payment method and charges the customer automatically on a fixed schedule.

To accept recurring payments with Fintoc, you complete three steps:

  1. On your backend, create a Checkout Session with flow: subscription.
  2. Redirect the customer to complete the enrollment at the Fintoc-hosted checkout page.
  3. Handle post-enrollment and recurring payment events (webhooks).

The following diagram shows how Fintoc interacts with both your backend and your frontend:

fintoc-recurring-payment-diagram

Create a Checkout Session

The Checkout Session object represents your intent to enroll a payment method for recurring charges, and to create a subscription with a fixed amount and periodicity.

Using your Secret Key, create a Checkout Session on your backend with flow set to subscription.

Server

curl --request POST "https://api.fintoc.com/v2/checkout_sessions" \
  --header "Authorization: YOUR_TEST_SECRET_API_KEY" \
  --header "Content-Type: application/json" \
  --data-raw '{
    "flow": "subscription",
    "amount": 350000,
    "currency": "CLP",
    "success_url": "https://merchant.com/success",
    "cancel_url": "https://merchant.com/cancel",
    "payment_method_types": [
      "pac"
    ],
    "customer_data": {
      "tax_id": {
        "type": "cl_rut",
        "value": "11.111.111-1"
      },
      "name": "Felipe Castro",
      "email": "[email protected]",
      "metadata": {}
    },
    "line_items": [
      {
        "price_data": {
          "currency": "CLP",
          "unit_amount": 350000,
          "product_data": {
            "name": "Plan 1"
          },
          "recurring": {
            "interval": "month",
            "interval_count": 1
          }
        },
        "quantity": 1
      }
    ],
    "metadata": {
      "subscription_external_id": "sub_987654321"
    }
  }'
const { Fintoc } = require('fintoc');

const fintoc = new Fintoc('YOUR_TEST_SECRET_API_KEY');

const checkoutSession = await fintoc.checkoutSessions.create({
  flow: 'subscription',
  amount: 350000,
  currency: 'CLP',
  success_url: 'https://merchant.com/success',
  cancel_url: 'https://merchant.com/cancel',
  payment_method_types: ['pac'],
  customer_data: {
    tax_id: {
      type: 'cl_rut',
      value: '11.111.111-1'
    },
    name: 'Felipe Castro',
    email: '[email protected]',
    metadata: {}
  },
  line_items: [
    {
      price_data: {
        currency: 'CLP',
        unit_amount: 350000,
        product_data: {
          name: 'Plan 1'
        },
        recurring: {
          interval: 'month',
          interval_count: 1
        }
      },
      quantity: 1
    }
  ],
  metadata: {
    subscription_external_id: 'sub_987654321'
  }
});
from fintoc import Fintoc

client = Fintoc('YOUR_TEST_SECRET_API_KEY')

checkout_session = client.checkout_sessions.create(
    flow='subscription',
    amount=350000,
    currency='CLP',
    success_url='https://merchant.com/success',
    cancel_url='https://merchant.com/cancel',
    payment_method_types=['pac'],
    customer_data={
        'tax_id': {
            'type': 'cl_rut',
            'value': '11.111.111-1',
        },
        'name': 'Felipe Castro',
        'email': '[email protected]',
        'metadata': {},
    },
    line_items=[
        {
            'price_data': {
                'currency': 'CLP',
                'unit_amount': 350000,
                'product_data': {
                    'name': 'Plan 1',
                },
                'recurring': {
                    'interval': 'month',
                    'interval_count': 1,
                },
            },
            'quantity': 1,
        }
    ],
    metadata={
        'subscription_external_id': 'sub_987654321',
    },
)

Fintoc responds with the Checkout Session object. Store its id and redirect_url to continue the flow:

{
  "id": "cs_li5531onlFDi235",
  "object": "checkout_session",
  "mode": "test",
  "flow": "subscription",
  "status": "created",
  "amount": 350000,
  "currency": "CLP",
  "payment_method_types": ["pac"],
  "customer": {
    "id": "cus_NffrFeUfNV2Hib",
    "object": "customer",
    "name": "Felipe Castro",
    "email": "[email protected]",
    "metadata": {},
    "tax_id": {
      "type": "cl_rut",
      "value": "11.111.111-1"
    }
  },
  "line_items": [
    {
      "price": {
        "product": {
          "name": "Plan 1",
          "description": "Fixed-amount monthly plan"
        },
        "currency": "CLP",
        "unit_amount": 350000,
        "recurring": {
          "interval": "month",
          "interval_count": 1
        }
      },
      "quantity": 1
    }
  ],
  "metadata": {
    "subscription_external_id": "sub_987654321"
  },
  "success_url": "https://merchant.com/success",
  "cancel_url": "https://merchant.com/cancel",
  "redirect_url": "https://pay.fintoc.com/checkout/cs_li5531onlFDi235"
}

The response includes a redirect_url attribute. In the next step, you redirect the customer to this location to complete the subscription.

The following table describes the parameters you send when creating a Checkout Session:

ParameterExampleDescription
amount350000Required. A positive integer representing the value to charge, in the smallest currency unit (for example, 1000 for $1000 CLP, since CLP has no minor unit, or 2476 for $24.76 MXN, since MXN has a minor unit). See the currencies page for details.
currencyCLPRequired. Three-letter ISO 4217 currency code for the recurring payments. One of CLP or MXN.
flowsubscriptionRequired. Flow type for the session. One of payment, setup, or subscription.
success_urlhttps://merchant.com/successRequired. URL to redirect the customer to after a successful enrollment.
cancel_urlhttps://merchant.com/cancelRequired. URL to redirect the customer to if they cancel the enrollment and return to your website.
customercus_3B2bODrQFje7ZVkT69xyaTSDwXQRequired if no customer_data. ID of an existing Customer.
customer_data(object)

Required if no customer. Data for inline customer creation.

If a Customer with the same tax_id already exists, the request returns a 409 Conflict error. To reuse an existing Customer, send its id in customer instead of customer_data.

payment_method_types["pac"]List of allowed payment methods during enrollment. One or more of pac (charges on bank accounts in Chile), direct_debit (charges on bank accounts in Mexico), and card.
line_items(array)Required. Array of items the customer subscribes to. Each item contains quantity and either price or price_data.
metadata{"subscription_external_id": "sub_987654321"}Set of key-value pairs you can attach to an object. Useful for storing additional information about the object in a structured format.

Include customer data (required for subscriptions)

When creating a Checkout Session with flow: subscription, you must include customer information. You can do this either by referencing an existing customer ID (customer) or by sending customer_data to create one inline:

AttributeTypeDescription
tax_idobject

Required if no email. Object that identifies the customer at a fiscal or regulatory level. The type is cl_rut for a Chilean tax ID (RUT) or mx_rfc for a Mexican tax ID (RFC), and value is the tax identifier as a string. See the Customer object.

If a Customer with the same tax_id already exists, the request returns a 409 Conflict error. To reuse an existing Customer, send its id in customer instead of customer_data.

namestringFull name of the customer.
emailstringRequired if no tax_id. Email address of the customer.
metadataobjectCustom data that stores additional information about the customer, for example internal IDs, CRM references, or tags.

Include an items list (required for subscriptions)

When creating a Checkout Session with flow: subscription, you must include the items the customer subscribes to. This information lets Fintoc display the items on the checkout page and show only the payment methods available for specific products.

AttributeTypeDescription
quantityintegerRequired. Number of units of this item being purchased.
price_dataobjectRequired. Data used to generate a new recurring price inline.

Each item in line_items must include price_data.

price_data object

AttributeTypeDescription
product_dataobjectRequired. Data used to generate a new Product object inline.
currencystringThree-letter ISO 4217 currency code for the subscription. One of CLP or MXN.
unit_amountintegerRequired. A positive integer representing the price per unit, in the smallest currency unit (for example, 1000 for $1000 CLP, since CLP has no minor unit, or 2476 for $24.76 MXN, since MXN has a minor unit).
recurringobjectRequired. Recurring configuration. interval is one of day, week, month, or year, and interval_count is the number of intervals between charges (for example, 1 for monthly when interval is month).

product_data object

AttributeTypeDescription
namestringRequired. Name of the product or service being purchased.
image_urlstringImage URL for the product. Must be an HTTPS URL. Recommended aspect ratio: 9:4.

Redirect the customer to complete the enrollment

Next, redirect the customer to the Fintoc-hosted checkout page using the redirect_url. After the customer completes the enrollment, Fintoc redirects the customer back to your site: to the success_url on success, or to the cancel_url if they cancel.

Client

window.location.assign(REDIRECT_URL_FROM_YOUR_BACKEND);

Handle post-session events

Once a Checkout Session finishes, you handle the result in your frontend and complete the subscription in your backend. For your backend, you use the events that Fintoc sends through webhooks.

Complete the subscription on your backend

Fintoc sends a checkout_session.finished event when the session completes.

In a subscription flow, this event includes information about the session and references to the subscription and payment_method created during enrollment.

{
  "id": "evt_a4xK32BanKWYn",
  "object": "event",
  "type": "checkout_session.finished",
  "data": {
    "id": "cs_li5531onlFDi235",
    "flow": "subscription",
    "customer": {
      "id": "cus_NffrFeUfNV2Hib",
      "object": "customer",
      "name": "Felipe Castro",
      "email": "[email protected]",
      "metadata": {},
      "tax_id": {
        "type": "cl_rut",
        "value": "11.111.111-1"
      }
    },
    "payment_method_types": ["pac"],
    "status": "finished",
    "payment_status": "succeeded",
    "subscription": "sub_NffrFeUfNV2Hib",
    "payment_method": "pm_NffrFeUfNV2Hib"
  }
}

You should handle the following post-session events:

EventDescriptionAction
checkout_session.finishedSent when a subscription Checkout Session reaches a final state.Activate the subscription on your side based on the final status, and store the created ids (subscription, payment_method, customer).
checkout_session.expiredSent when a session expires.Offer the customer another attempt to subscribe.
payment_intent.succeededSent when a payment intent succeeds, like a charge on a bank account or card.Confirm to your customer that the subscription charge succeeded.
payment_intent.failedSent when a payment intent fails.Offer the customer another attempt to pay the subscription.

Test your integration

To confirm that your integration works correctly, you can simulate subscriptions and scheduled recurring payments without moving any money.

1) Create a subscription Checkout Session using test user credentials

Using your test mode Secret Key, create a Checkout Session with flow: subscription on your backend. Then complete the enrollment on the Fintoc-hosted checkout page with the following credentials:

Test credentials

  • Username (RUT): 11.111.111-1
  • Password: jonsnow

2) Handle simulated scheduled payments of the subscription

In test mode, the subscription's scheduled payments are simulated so you can verify how your integration handles success and failure without moving any money. You handle them through the same invoice.* and payment_intent.* events described in the Manage invoices section below: a successful charge emits invoice.payment_succeeded and payment_intent.succeeded, and a failed one emits invoice.payment_failed and payment_intent.failed.

📘

Test mode is not yet available for recurring payments in Mexico.

Manage invoices

When a subscription is created after a successful checkout enrollment, Fintoc automatically generates an Invoice for each billing cycle. An invoice represents the amount the customer owes for a given period. Fintoc attempts to collect payment for the invoice using the enrolled payment method.

For full details on invoices, see the Invoice Object.

Invoices in the subscription flow

After the checkout_session.finished event, the subscription becomes active and Fintoc creates the first invoice. From that point on, you should handle the following invoice-related events alongside the post-session events described above:

EventDescriptionAction
invoice.createdSent when Fintoc generates a new invoice for a billing cycle.Log the invoice and update your records.
invoice.finalizedSent when Fintoc finalizes the invoice and it becomes ready for payment.Update your records accordingly.
invoice.payment_succeededSent when Fintoc collects the invoice payment.Confirm the payment to your customer and extend access.
invoice.payment_failedSent when a payment attempt for the invoice fails.Notify your customer and offer an alternative payment method.

Month 1: Right after the subscription is created, Fintoc generates the first invoice and attempts payment immediately. You'll receive invoice.created, followed by invoice.finalized, then invoice.payment_succeeded and payment_intent.succeeded on success.

Month 2 onwards: At each billing cycle renewal (based on the subscription's billing_cycle_anchor), Fintoc creates a new invoice in draft status. After 1 hour, Fintoc attempts payment automatically. On success you receive invoice.payment_succeeded. On failure, invoice.payment_failed.

Test invoice creation with status draft

To test an invoice that is created in draft status, create a subscription with a line item using the product name sandbox_draft:

Server

curl --request POST "https://api.fintoc.com/v2/checkout_sessions" \
  --header "Authorization: YOUR_TEST_SECRET_API_KEY" \
  --header "Content-Type: application/json" \
  --data-raw '{
    "flow": "subscription",
    "amount": 350000,
    "currency": "CLP",
    "success_url": "https://merchant.com/success",
    "cancel_url": "https://merchant.com/cancel",
    "payment_method_types": ["pac"],
    "customer_data": {
      "tax_id": {
        "type": "cl_rut",
        "value": "11.111.111-1"
      },
      "name": "Felipe Castro",
      "email": "[email protected]"
    },
    "line_items": [
      {
        "price_data": {
          "currency": "CLP",
          "unit_amount": 350000,
          "product_data": {
            "name": "sandbox_draft"
          },
          "recurring": {
            "interval": "month",
            "interval_count": 1
          }
        },
        "quantity": 1
      }
    ]
  }'
const { Fintoc } = require('fintoc');

const fintoc = new Fintoc('YOUR_TEST_SECRET_API_KEY');

const checkoutSession = await fintoc.checkoutSessions.create({
  flow: 'subscription',
  amount: 350000,
  currency: 'CLP',
  success_url: 'https://merchant.com/success',
  cancel_url: 'https://merchant.com/cancel',
  payment_method_types: ['pac'],
  customer_data: {
    tax_id: {
      type: 'cl_rut',
      value: '11.111.111-1'
    },
    name: 'Felipe Castro',
    email: '[email protected]'
  },
  line_items: [
    {
      price_data: {
        currency: 'CLP',
        unit_amount: 350000,
        product_data: {
          name: 'sandbox_draft'
        },
        recurring: {
          interval: 'month',
          interval_count: 1
        }
      },
      quantity: 1
    }
  ]
});
from fintoc import Fintoc

client = Fintoc('YOUR_TEST_SECRET_API_KEY')

checkout_session = client.checkout_sessions.create(
    flow='subscription',
    amount=350000,
    currency='CLP',
    success_url='https://merchant.com/success',
    cancel_url='https://merchant.com/cancel',
    payment_method_types=['pac'],
    customer_data={
        'tax_id': {
            'type': 'cl_rut',
            'value': '11.111.111-1',
        },
        'name': 'Felipe Castro',
        'email': '[email protected]',
    },
    line_items=[
        {
            'price_data': {
                'currency': 'CLP',
                'unit_amount': 350000,
                'product_data': {
                    'name': 'sandbox_draft',
                },
                'recurring': {
                    'interval': 'month',
                    'interval_count': 1,
                },
            },
            'quantity': 1,
        }
    ],
)

Fintoc creates the Checkout Session:

{
  "id": "cs_li5531onlFDi235",
  "object": "checkout_session",
  "mode": "test",
  "flow": "subscription",
  "status": "created",
  "amount": 350000,
  "currency": "CLP",
  "payment_method_types": ["pac"],
  "customer": {
    "id": "cus_NffrFeUfNV2Hib",
    "object": "customer",
    "name": "Felipe Castro",
    "email": "[email protected]",
    "metadata": {},
    "tax_id": {
      "type": "cl_rut",
      "value": "11.111.111-1"
    }
  },
  "line_items": [
    {
      "price": {
        "product": {
          "name": "sandbox_draft",
          "description": "Fixed-amount monthly plan"
        },
        "currency": "CLP",
        "unit_amount": 350000,
        "recurring": {
          "interval": "month",
          "interval_count": 1
        }
      },
      "quantity": 1
    }
  ],
  "success_url": "https://merchant.com/success",
  "cancel_url": "https://merchant.com/cancel",
  "redirect_url": "https://pay.fintoc.com/checkout/cs_li5531onlFDi235"
}

Fintoc creates the invoice in draft status, so you can edit its items with the Add Lines endpoint before the invoice transitions to the next status.