Implementation Guides

Collecting Customer Card Payments

caution

Duffel Payments is not available as a public feature yet. This guide is available as a preview of what Duffel Payments will look like and what integration work it will require. This guide may change before the public release. If you are interested in getting access to Duffel Payments please contact help@duffel.com.

Overview

Duffel Payments provides a PCI-compliant way for you to work with card payments for online bookings from your customers.
This guide will walk you through how to use Duffel Payments to collect payments and book flights using your customers' cards. Duffel Payments adds two new steps to the normal search, select and create instant orders workflow:
  • Search for Offers

  • Select Offer

  • Create PaymentIntent

  • Collect Payment

  • Confirm PaymentIntent

  • Create Order

  • Update Order Metadata (Optional)

  • Inspect Collected Payments and Markups (Optional)

note

Although this guide is focused on the flow of booking instant orders, Duffel Payments can also be used to book pay later orders and order changes.

Requirements

Duffel Payments currently only works with Duffel Content Services and is built specifically for applications that run in a web browser, have their frontend written in JavaScript, and are backed by a backend server. If these two requirements are not met you won't be able to use Duffel Payments for now.
This guide assumes that you already have a working integration with the Duffel API. Only the basics of searching and booking are required for this guide. If you could use a refresher, please head over to our Quick Start Guide.

Create PaymentIntent

A PaymentIntent is a resource that will be used to represent and record the process of collecting a payment from your customer. We recommend that you create exactly one PaymentIntent for each offer your customer wants to book.
Once your customer has searched for and selected an offer to book, the first step to use Duffel Payments is to create a PaymentIntent. You should use the Create a PaymentIntent endpoint to do this in your backend server.

caution

It's very important that you do this in your backend server. It will prevent malicious users from changing the payment amount and will prevent you from exposing your Duffel API token to the public internet.

Request

Shell

curl -X POST --compressed "https://api.duffel.com/payments/payment_intents" \
-H "Accept-Encoding: gzip" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Duffel-Version: beta" \
-H "Authorization: Bearer
$YOUR_ACCESS_TOKEN
" \
-d '{
"data": {
"amount": "120.00",
"currency": "GBP"
}
}'
In the request above we're creating a generic PaymentIntent resource with an amount of £120. The amount you specify here is the amount that we're going to charge your customer. It should be enough to cover the total_amount of the offer plus the total_amount of any services your customer wants to book. You can also provide an amount higher than this sum, in which case the remainder will be considered as your markup.
It's worth calling out, that at this point, the PaymentIntent being created is not tied to the offer being booked. It's just a resource created to represent and record the collection of a payment.

Response

{
"data": {
"id": "pit_00009htYpSCXrwaB9DnUm2",
"amount": "120.00",
"currency": "GBP",
"client_secret": "c2hramgzaGVsbG8gd29ybGQgIyMgZ2lyYWY=",
"created_at": "2020-04-11T15:48:11.642Z"
}
}
The response to the request above will contain a PaymentIntent resource. The two fields worth highlighting in this response are the id and the client_secret. The client_secret will be used in the next step to collect the payment from your customer in the frontend. The id will be used in the "Create order" step to pay for the order, so we recommend you store this information for later use.

Collect payment

Once you have a PaymentIntent created by your backend the next step is to use it in your frontend to actually collect the payment.
The first step is to make the PaymentIntent created in the previous step available to the frontend. We recommend that you create an endpoint in your backend server that the frontend can use to fetch this information.

caution

The Duffel Payments JS SDK is not yet available publicly.

Then you need to import our Duffel Payments JS SDK:
<head>
<script src="https://assets.duffel.com/payments.min.js"></script>
</head>
Create a form to collect payment details:
<form id="payment-form">
<div id="card-element">
<!-- The Duffel Payments JS SDK will create input elements here -->
</div>
<!-- The Duffel Payments JS SDK will put the error messages in this element -->
<div id="card-errors" role="alert"></div>
<button id="submit">Submit Payment</button>
</form>
And finally initialise the form using the Duffel Payments JS SDK:
duffelPayments
.confirmCardPayment('{PAYMENT_INTENT_CLIENT_SECRET}', {
payment_method: {
card: ...,
billing_details: {...},
},
})
.then(function(result) {
// Handle result.error or result.paymentIntent
});
Once payment authorisation is successful then you are ready to confirm the PaymentIntent and have your Balance topped up.

Confirm PaymentIntent

When you confirm the PaymentIntent that succeeded, in the previous step, we'll top-up your Balance with the specified amount—minus the Duffel Payments fees. Once topped-up, it'll be ready to use to create an Order.

Request

Shell

curl -X POST --compressed "https://api.duffel.com/payments/payment_intents/pit_00009htYpSCXrwaB9DnUm2/actions/confirm" \
-H "Accept-Encoding: gzip" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Duffel-Version: beta" \
-H "Authorization: Bearer
$YOUR_ACCESS_TOKEN
" \
}'

Response

{
"data": {
"id": "pit_00009htYpSCXrwaB9DnUm2",
"amount": "120.00",
"currency": "GBP",
"created_at": "2020-04-11T15:48:11.642Z
}
}

Create Order

The last key step is to actually book the order with the funds collected from the customer in the previous step. This uses the Create an order endpoint.

Request

Shell

curl -X POST --compressed "https://api.duffel.com/air/orders" \
-H "Accept-Encoding: gzip" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Duffel-Version: beta" \
-H "Authorization: Bearer
$YOUR_ACCESS_TOKEN
" \
-d '{
"data": {
"type": "instant",
"selected_offers": [
"off_00009htYpSCXrwaB9DnUm0"
],
"payments": [
{
"type": "balance",
"amount": "120.00",
"currency": "GBP"
}
],
"passengers": [...]
}
}'
You'll notice that there's no changes to this request. You'll use balance since it will have been topped-up with the payment from your customer.

Response

{
"data": {
"id": "ord_00009htYpSCXrwaB9DnUm1",
// ...
"payments": [
{
"id": "pay_00009hthhsUZ8W4LxQgkjo",
"amount": "120.00",
"currency": "GBP",
"type": "balance",
"created_at": "2020-04-11T15:48:11.642Z"
}
]
}
}

Update Order Metadata (Optional)

You can optionally store the Payment Intent's id in your new Order's metadata field, to help with things like reconciliation. This uses the Update an order endpoint.

Shell

curl -X PATCH --compressed "https://api.duffel.com/air/orders/ord_00009htYpSCXrwaB9DnUm1" \
-H "Accept-Encoding: gzip" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Duffel-Version: beta" \
-H "Authorization: Bearer
$YOUR_ACCESS_TOKEN
" \
-d '{
"data": {
"metadata": {
"payment_intent_id": "pit_00009htYpSCXrwaB9DnUm2"
}
}
}'

Response

{
"data": {
"id": "ord_00009htYpSCXrwaB9DnUm1",
// ...
"metadata": {
"payment_intent_id": "pit_00009htYpSCXrwaB9DnUm2"
}
}
}

Inspect Collected Payments and Markups (Optional)

After you've collected payments from your customer, you can use the Duffel dashboard to inspect these payments and the associated markups that will be paid out to you.