Card Payments

Take card payments through the API
To use this capability add octo/cardPayments to your Octo-Capabilities header.
This capability allows you to accept card payments via the API. It is possible to support multiple gateways, we document 4 in this spec: VivaWallet Adyen Stripe and External
If you want us to add support for any other gateway please contact us at d[email protected]

VivaWallet Gateway

We add a cardPayment key to the booking object, but if you're using the octo/cart capability then we add the same key to that as well. The value will look like this:
// ..rest of the booking or order object
"cardPayment": {
"gateway": "vivawallet",
"vivawallet": {
"orderCode": 1272214778972604
}
}
In the response we provide orderCode value which you can use to initiate the VivaWallet Smart Checkout as described in the documentation here:
Smart Checkout
VivaWallet Smart Checkout
As described in the docs, in production you just redirect the guest to a URL like so:
https://www.vivapayments.com/web2?ref=1272214778972604
Where the value for ref is the orderCode returned in the card payment object. The documentation also describes various ways to customise the checkout including using custom colors and payment methods.
Once the payment is complete, the customer will be redirected back to the Success URL or Failure URL as setup in your gateway account. If directed to the Success URL you will also receive a query parameter t which is a UUID (example t=2b4c6b5b-49ff-4e46-adc5-f53740212361), you must then send this to Ventrata along with the original orderCode using either of the confirmation endpoints (depending if you're using the octo/cart capability):
POST /bookings/:uuid/confirm POST /orders/:orderId/confirm
{
"cardPayment": {
"gateway": "vivawallet",
"vivawallet": {
"orderCode": 1272214778972604,
"transactionId": "2b4c6b5b-49ff-4e46-adc5-f53740212361"
}
}
}
Ventrata will then verify the payment has actually been approved and if so will confirm the order.

Bridgepay Gateway

We add a cardPayment key to the booking object, but if you're using the octo/cart capability then we add the same key to that as well. The value will look like this:
// ..rest of the booking or order object
"cardPayment": {
"gateway": "bridgepay",
"bridgepay": {
"publicKey": "tokenpay8228api20202615022644676"
}
}
In the response we provide publicKey value which you can use with Bridgepay's TokenPay.js SDK described in the documentation here:
Documentation
Navigate to TokenPay.js -> Integration Guide -> Client Side
On the client side, the payment page should look something like this:
<form action="/payment" method="post" id="form">
<div id="card"></div>
<div id="error"></div>
<input type="hidden" id="token">
<input type="submit" value="Pay Now">
</form>
<script src="https://bridgepaynetsecuretx.com/WebSecurity/TokenPay/plain-js/tokenPay.js"></script>
<script>
var tokenpay = TokenPay('tokenpay8228api20202615022644676');
tokenpay.initialize({
dataElement: 'card',
errorElement: 'error',
useACH: false,
useStyles: false,
disableZip: false,
disableCvv: false
});
var form = document.getElementById('form');
form.addEventListener('submit', function(event) {
event.preventDefault();
tokenpay.createToken(function (result) {
var input = document.getElementById('token');
input.setAttribute('value', result.token);
form.submit()
}, function(result) {
var error = document.getElementById('error');
error.innerText = result
})
})
</script>
The value of result.token which in this example we set on the hidden input field in the form. You must then send this to Ventrata using either of the confirmation endpoints (depending if you're using the octo/cart capability):
POST /bookings/:uuid/confirm POST /orders/:orderId/confirm
{
"cardPayment": {
"gateway": "bridgepay",
"bridgepay": {
"token": "11a3272a-7d16-402f-84a5-1ea29c3a2656"
}
}
}
Ventrata will then use the card token to authorise and capture the payment amount on the order and confirm the booking.

Stripe Gateway

We add a cardPayment key to the booking object, but if you're using the octo/cart capability then we add the same key to that as well. The value will look like this:
// ..rest of the booking or order object
"cardPayment": {
"gateway": "stripe",
"stripe": {
"version": "latest",
"paymentIntent": {
"id": "pi_1GG1qyCgIN4MRzgjHrJygury",
"publishableKey": "pk_test_f2FFE5An5Z0oMyxjlYgFRtdO",
"clientSecret": "pi_1GG1qyCgIN4MRzgjHrJygury_secret_PW4yD18qsNpU1Gz8etftPCZDM",
"amount": 9130,
"currency": "eur"
}
}
}
In the response we give some parameters under cardPayment which includes a stripe payment intent. You should follow the Stripe documentation from this section:
Check the Stripe documentation for more complete examples, but here is an example:
// Change this value cardPayment.stripe.paymentIntent.publishableKey
var stripe = Stripe('pk_test_f2FFE5An5Z0oMyxjlYgFRtdO');
// Change this value cardPayment.stripe.paymentIntent.clientSecret
var elements = stripe.elements({ clientSecret: "pi_1GG1qyCgIN4MRzgjHrJygury_secret_PW4yD18qsNpU1Gz8etftPCZDM" });
var payment = elements.create("payment");
payment.mount("#payment-element");
var form = document.getElementById('payment-form');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const {error} = await stripe.confirmPayment({
//`Elements` instance that was used to create the Payment Element
elements,
confirmParams: {
return_url: 'https://my-site.com/order/123/complete',
},
});
if (error) {
// This point will only be reached if there is an immediate error when
// confirming the payment. Show error to your customer (e.g., payment
// details incomplete)
const messageContainer = document.querySelector('#error-message');
messageContainer.textContent = error.message;
} else {
// Send this payment intent back to OCTO Cloud!
}
});
Once the payment is confirmed you can perform either:
POST /bookings/:uuid/confirm POST /orders/:orderId/confirm
The second endpoint is if you're using the octo/cart capability. You then must include the payment intent id in the request body. If you prefer to use a Stripe payment method id instead that's also possible. Both examples are given below:
Payment Intent
Payment Method
{
"cardPayment": {
"gateway": "stripe",
"amount": 9130,
"currency": "EUR",
"stripe": {
"paymentIntent": "pi_1GG0bjCgIN4MRzgjMPBIM7SU"
}
}
}
{
"cardPayment": {
"gateway": "stripe",
"amount": 9130,
"currency": "EUR",
"stripe": {
"paymentMethod": "pm_card_us"
}
}
}

Setup Intent

If you do not want to take a payment, but you do want to save an active card against the order which can be charged in future without the customer being present, you can use the setup intent functionality provided by the Stripe gateway.
In the booking/order response, next to the paymentIntent object will be a new field called setupIntent which will look like this:
// ..rest of the booking or order object
"cardPayment": {
"gateway": "stripe",
"stripe": {
"version": "latest",
"setupIntent": {
"id": "seti_1IsSzkFpFSzCJZwK7FZaEFdT",
"publishableKey": "pk_test_3oDH2WJpi1pHBWwOiR71cmWS",
"clientSecret": "seti_1IsSzkFpFSzCJZwK7FZaEFdT_secret_JVTx18Jwwt7EFZDSzHsmjNiDkeeNfvX"
}
}
}
}
The response object will look very similar to the paymentIntent object except it won't contain the amount and currency fields because unlike a paymentIntent we're not actually charging the card, we're just collecting the details to be used later. This can be helpful for a "Buy Now, Pay Later" scenario.
To collect the card details from the customer, follow Stripe's guide here: https://stripe.com/docs/payments/save-and-reuse
A simple example using Stripe's card element would be:
var stripe = Stripe('pk_test_f2FFE5An5Z0oMyxjlYgFRtdO');
// Change this value cardPayment.stripe.paymentIntent.clientSecret
var elements = stripe.elements({ clientSecret: "pi_1GG1qyCgIN4MRzgjHrJygury_secret_PW4yD18qsNpU1Gz8etftPCZDM" });
var payment = elements.create("payment");
payment.mount("#payment-element");
var form = document.getElementById('payment-form');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const {error} = await stripe.confirmSetup({
//`Elements` instance that was used to create the Payment Element
elements,
confirmParams: {
return_url: 'https://my-site.com/order/123/complete',
},
});
if (error) {
// This point will only be reached if there is an immediate error when
// confirming the payment. Show error to your customer (e.g., payment
// details incomplete)
const messageContainer = document.querySelector('#error-message');
messageContainer.textContent = error.message;
} else {
// Send this payment intent back to OCTO Cloud!
}
});
Once the card setup is confirmed you can perform either:
POST /bookings/:uuid/confirm POST /orders/:orderId/confirm
The second endpoint is if you're using the octo/cart capability. You then must include the setup intent id in the request body. If you prefer to use a Stripe payment method id instead that's also possible. Both examples are given below:
Setup Intent
Payment Method
{
"cardPayment": {
"gateway": "stripe",
"stripe": {
"setupIntent": "seti_1IsSzkFpFSzCJZwK7FZaEFdT"
}
}
}
{
"cardPayment": {
"gateway": "stripe",
"amount": 0,
"stripe": {
"paymentMethod": "pm_card_us"
}
}
}
Note if you use the payment method route we include "amount": 0 in the body. This is critical because it tells the system to use set the payment method up to be used in future rather than charging it right now. This isn't necessary for the Setup Intent method.

External Gateway

The external gateway is the simplest and isn't actually a gateway at all. It allows you to register a virtual card payment which might have been taken on another gateway not supported by OCTO Cloud.
We add a cardPayment key to the booking object, but if you're using the octo/cart capability then we add the same key to that as well. The value will look like this:
// ..rest of the booking or order object
"cardPayment": {
"gateway": "external"
}
Once the payment is confirmed you can perform either:
POST /bookings/:uuid/confirm POST /orders/:orderId/confirm
The second endpoint is if you're using the octo/cart capability. You can then optionally include notes in the external field which will can record an external payment reference which might be useful for backend reconciliation later. For example:
{
"cardPayment": {
"gateway": "external",
"amount": 9130,
"currency": "EUR",
"notes": "Ref#25172328"
}
}