Accept a payment

Learn how to use Fintoc's Payment Initiation API

There are three steps to accept payments using Fintoc:

  1. On your backend, create a PaymentIntent using your Secret Key
  2. Open the widget on your frontend using your Public Key
  3. Handle post-payments events

Create a payment

The Payment Intent object represents your intent to collect a payment from a customer and tracks state changes throughout the payment process.

Before creating a payment, you must decide how to operate with Fintoc. The recommended way β€”and the defaultβ€” is that Fintoc collects your payments and then makes payouts to your bank account based on a payout schedule. If you want the money to be sent directly to a specific bank account, see the setup direct payments guide.

Using your Secret Key, create a PaymentIntent on your server with an amount and currency.

curl --request POST "https://api.fintoc.com/v1/payment_intents" \
-- header 'Authorization: sk_live_0000000000000000' \
-- header 'Content-Type: application/json' \
--data-raw '{
  "amount": 2476,
  "currency": "CLP",
  "customer_email":"[email protected]"
}'
const fetch = require('node-fetch');

const payment_intent = {
  amount: 1000,
  currency: 'clp'
}

fetch('https://api.fintoc.com/v1/payment_intents', {
  	method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'sk_live_000000000000'
    },
    body: JSON.stringify(payment_intent),
  },
)
import requests

payment_intent = {
  'amount': 1000,
  'currency': 'clp'

headers = {
  'Accept': 'application/json', 'Authorization': 'sk_live_000000000000'
}

r = requests.post(
  'https://api.fintoc.com/v1/payment_intents',
  json=payment_intent,
  headers=headers
)
require 'net/http'
require 'uri'
require 'json'

payment_intent = {
  amount: 1000,
  currency: 'clp'
}

uri = URI("https://api.fintoc.com/v1/payment_intents")

header = {
  Accept: 'application/json', Authorization: 'sk_live_000000000000'
}

http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.request_uri, header)
request.body = payment_intent.to_json

response = http.request(request)

If you want to create a Payment Intent for Mexico, change the currency to MXN.

🚧

Currencies are represented as integers

The Fintoc API represents currencies in its smallest possible units with no decimals (as an integer). That means that an amount of MXN 10.29 gets represented by Fintoc as 1029. You can read more about currencies here.

ParameterExampleExplanation
amount2476Amount of money that needs to be paid. It's represented as integer with no decimals in the smallest possible unit of the currency you are using.

If your payment uses Chilean peso, an amount of CLP 2476 is represented as 2476.

If your payment uses Mexican peso, an amount of MXN 24.76 is represented as 2476.

Read here to learn more.
currencyCLPCurrency that is being used for the payment. We currently support CLP and MXN.

πŸ“˜

Always create the PaymentIntent from your server

Always create the PaymentIntent on the server side, a trusted environment, as opposed to the client. This prevents malicious customers from being able to choose their own prices.

Response when creating a Payment Intent

After making the request to create the Payment Intent, Fintoc should respond with something like this:

{
  "id": "pi_BO381oEATXonG6bj",
  "object": "payment_intent",
  "amount": 1000,
  "currency": "CLP",
  "widget_token": "pi_BO381oEATXonG6bj_sec_a4xK32BanKWYn",
  "status": "created",
  "metadata": {},
  "reference_id": null,
  "recipient_account": {
    "holder_id": "183917137",
    "number": "123456",
    "type": "checking_account",
    "institution_id": "cl_banco_de_chile"
  },
  "sender_account": null,
  "created_at": "2021-10-15T15:23:11.474Z",
}

In the response, you should receive the widget_token attribute. This attribute is returned only when the Payment Intent is created. After that, every request to get an existing Payment Intent from Fintoc will return the widget_token attribute empty. In the following step, you'll use this attribute to set up the Fintoc widget.

The values of the attributes reference_id and sender_account will be null. These fields will populate once the payment starts to be processed.

Once the transfer is completed successfully, the reference_id field will correspond to the bank's operation number assigned to the transfer.

πŸ“˜

Widget token expiration

A Payment Intent expires after 10 minutes in Chile and 15 minutes in Mexico. After the mentioned time of the Payment Intent being created, the widget won't accept the widget_token of the expired Payment Intent, and you will have to create a new one.

Open the widget

Once you create the Payment Intent, you need to use the widget_token to setup the widget for a payment.

The Fintoc Widget is the client-side component that your customers will interact with to pay using Fintoc. The Fintoc Widget will handle credential validation, multi-factor authentication, and error handling for each institution that we support.

Use your Public Key and the Payment Intent widget token to configure the widget.

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Fintoc Demo</title>
    <script src="https://js.fintoc.com/v1/"></script>
  </head>
  <body>
    <script>
      window.onload() => {
        const widget = Fintoc.create({
          holderType: 'individual',
          widgetToken: 'pi_XXXXXXXX_sec_YYYYYYYY',
          product: 'payments',
          country: 'cl',
          publicKey: 'pk_live_0000000000',
          onSuccess: () => {},
          onExit: () => {},
        });
        widget.open();
      };
      </script>
  </body>
</html>

If you want a Mexican payment, change the country parameter to mx.

You can read more about the widget and its configurations in the Widget guide.

πŸ“˜

Use our Widget Webview if you are building a mobile app

If you are integrating Fintoc into an iOS or Android application, you can use our WebView integration.

Handle post-payments events

Once a Payment Intent finishes, you handle the payment result in your frontend and complete the payment in your backend. For your frontend you will use the widget callback, and for your backend you will use the events sent by webhooks.

πŸ“˜

Use webhooks events to complete payments

Your customer could close the browser window or quit the app before the onSuccess widget callback executes. For this reason, you should always use the payment intent successful event to handle post-payments actions like sending an order confirmation email to your customer, logging the sale in a database, or starting a shipping workflow.

Handle the payment result on your frontend

Once a Payment Intent finishes successfully, the widget executes the onSuccess callback. You need to pass this function to the widget upon creation.

With this callback, you can decide what to do with your user's frontend once the payment is complete, for example:

  • Redirect the user to a post-sale or post-payment view
  • Show the user a success screen.

πŸ“˜

Don't use this callback as a payment confirmation

You shouldn't trust on the onSuccess callback as a confirmation for a successful payment, as the frontend is an insecure realm and a malicious third party may execute a JavaScript function that simulates that the transfer was executed successfully.

For a more comprehensive validation mechanism, we strongly encourage integrating webhooks and subscribing to the payment intent successful event. By implementing webhooks, you can ensure timely and accurate updates on payment statuses, enhancing the overall security and reliability of your payment confirmation process. You can also validate the payment's authenticity by leveraging the Retrieve Payment Intent endpoint, but this can be subject to rate limits and fail.

Handle errors

You don't only need to handle succeeded payments because payments can also fail. For example, your customer doesn't have funds in their bank account to complete the payment.

When a payment fails or is rejected by your customer, the widget executes the onExit callback. With this callback, you can handle errors on your frontend. For example, you can invite your customer to use another payment method.

Complete the payment on your backend

Fintoc sends a payment_intent.succeeded event when the payment completes. Use the follow the webhook guide to receive these events and run actions, such as sending an order confirmation email to your customer, logging the sale in a database, or starting a shipping workflow.

The payment_intent.succeeded event looks like this:

{
  "id": "evt_eNkjd5zjCpM5PaGy",
  "type": "payment_intent.succeeded",
  "mode": "live",
  "created_at": "2024-10-17T18:13:26.750Z",
  "data": {
    "id": "pi_7WG4XwODSJndlDg6",
    "mode": "live",
    "amount": 1000,
    "object": "payment_intent",
    "status": "succeeded",
    "currency": "CLP",
    "metadata": {'order_id':"7n4dFs3z"},
    "created_at": "2024-10-17T18:11:39Z",
    "error_reason": null,
    "reference_id": "173021729",
    "widget_token": null,
    "customer_email": null,
    "sender_account": {
      "type": "checking_account",
      "number": "12345678",
      "holder_id": "123456789",
      "institution_id": "cl_banco_bice"
    },
    "transaction_date": null,
    "recipient_account": {
      "type": "checking_account",
      "number": "1111111",
      "holder_id": "498574628",
      "institution_id": "cl_banco_de_chile"
    }
  },
  "object": "event"
}

You should handle the following events when using our Payment Initiation product:

EventDescriptionAction
checkout_session.finishedSent when a customer has successfully completed a payment.Complete the order based on the status of the related payment
checkout_session.expiredSent when a session expires due to the customer not attempting a payment.Offer the customer another attempt to pay.

❗️

Handle empty fields

Some payments may have null fields if the user exits mid-process. For example, the sender account may be null if the user quits before selecting a bank. Account for nulls appropriately when processing payments and saving payment objects in your database.