Payment connector - How to develop your connector

Payment connector - How to develop your connector

Read our introduction to payment connector extensibility and how to configure a payment connector before diving into this article

1. Introduction

OneStock supports leading Payment Service Providers (PSPs) such as Adyen, Stripe, and HiPay through native connectors. The Payment Connector framework lets partners and clients build custom integrations with any PSP, covering the full payment lifecycle across all sales channels.

With the Payment Connector, you can implement:

  • Direct capture: Immediate payment capture at checkout (Order In Store).

  • Deferred payment: Authorize payment at checkout and capture funds upon dispatch (Order In Store).

  • Pay By Links: Generate secure payment links displayed in the Order In Store checkout (Order In Store only).

  • Capture and refund for any order, from any sales channel: Pass transaction data when creating an order — web, marketplace, Order In Store, or any other channel — so that OneStock automatically triggers captures and refunds in your connector at the right moment in the order lifecycle (dispatch, cancellation, return). See Pass transaction data to enable capture & refund.

This guide will walk you through the steps to successfully build, test, and deploy your custom payment integration with OneStock.

2. Documentation

To integrate a custom payment solution, the partner or client must implement the provided interface, detailed in the accompanying OpenAPI specification file :

The OpenAPI is separated in two sections: Routes to implement and OneStock API to call to update the payment states.

image-20260623-162658.png

 

image-20260623-162711.png

 

3. Guide

3.1. Direct Capture

This scenario demonstrates how to initiate a payment transaction that authorizes and captures the amount to be paid in a single flow.

In the Direct Capture process, OneStock initiates the transaction by sending a request to the PSP Connector. The connector acknowledges receipt of the transaction and forwards the capture request to the designated Payment Service Provider. Once the payment provider processes the transaction, the result is communicated back to the PSP Connector, which then updates OneStock with the final status of the payment.

The following sequence diagram illustrates the steps involved in the direct capture process.

image-20260626-124045.png

 

3.2. Deferred payment

This scenario outlines the process for initiating a payment authorization without an immediate capture. The Deferred Payment method allows for payment authorization at checkout, with the actual capture occurring later, typically after the goods have been dispatched.

In this flow, OneStock initiates the transaction by sending an authorization request to the PSP Connector. The connector acknowledges receipt and forwards the authorization to the Payment Service Provider. Upon receiving the authorization result, the connector updates OneStock with the transaction status.

Once the items are shipped, OneStock will send a capture request for the authorized amount (or a partial amount). The PSP Connector must immediately acknowledge this request and subsequently update OneStock with the capture result.

image-20260626-124103.png

 

3.3. Pay By Link

The Pay By Link scenario allows customers to complete their payment transactions from their own devices. This is available for Order In Store only.

In this process, OneStock initiates the transaction by requesting a payment link from the PSP Connector. The connector generates the link and sends it back to OneStock, which then shares it with the customer.

The Payment Connector allows you to send a url to display as a QR code in the Order In Store. The link can also be sent to the customer with a text and/or email notification.

The customer completes the payment through the provided link, and the PSP processes the transaction.

Depending on the payment type, this transaction may result in either a direct capture or a deferred payment (authorization only). OneStock expects to receive updates on the transaction status accordingly.

image-20260626-124118.png

 

3.4. Refunds

If refunds are configured in the workflows with a payment execution of type refund, OneStock will call the connector in different use cases:

3.4.1. Deferred payments where capture wasn't done

  • OneStock will call the connector on the POST /cancel_authorisations

  • The connector will update the authorisation cancel on the POST /external_payments/authorisation_update

3.4.2. Capture is already taken

  • OneStock will call the connector on the POST /refunds route

  • The connector will update the refund state on the POST /external_payments/refund_update

4. Develop your integration

4.1. Security - check the OneStock signature and sign your calls

All API calls between OneStock and the connectors must be signed.

Each API call includes a signature in the header with the following format: timestamp + "," + signature.

During the connector setup a hash key is generated by OneStock. This key, referred to as h0, is used to encrypt the signature. OneStock retains the three most recent hash keys (h0, h1, h2) to support ongoing signature verification.

The signature format is:
t=timestamp,h0=h[0],h1=h[1],h2=h[2], where:

  • timestamp: The current timestamp in seconds

  • h[0]: The signature encrypted using the latest hash key.

  • h[1]: The signature encrypted using the previous hash key.

  • h[2]: The signature encrypted using the oldest hash key.

Encryption: the encryption is a HMAC in SHA256 of the computed string: timestamp and the requestBody separated by a . character.

Example of signature and verification code

Current timestamp = 1704092400

Encryption of h0 for 1704092400 = 1234 (encryption of the timestamp with the new key)

Encryption of h1 for 1704092400 = 9876 (encryption of the timestamp with the previous key)

The signature will be the following: t=1704092400,h0=1234,h1=9876

Resulting the header: Onestock-Signature=t=1704092400,h0=1234,h1=9876

<?php function checkWebhookSignatureWithMultipleKeys($request, $keys) { foreach ($keys as $key) { $isValid = checkWebhookSignature($request, $key); if ($isValid) { return true; } } return false; } function checkWebhookSignature($request, $secretKey) { $signatureHeader = $request->headers->get('Onestock-Signature'); $body = file_get_contents('php://input'); $signatureParts = explode(',', $signatureHeader); if (count($signatureParts) < 2) { return false; } $timestamp = substr($signatureParts[0], 2); if (time() - intval($timestamp) > 60 * 60 * 6) { return false; } $payload = $timestamp . '.' . $body; $expectedSignature = hash_hmac('sha256', $payload, $secretKey); for ($i = 1; $i < count($signatureParts); $i++) { $h = substr($signatureParts[$i], strlen("h{$i}=")); if ($h === $expectedSignature) { return true; } } return false; } ?>
import hmac import hashlib import time def check_webhook_signature_with_previous_keys(request, previous_keys): for key in previous_keys: if check_webhook_signature(request, key): return True return False def check_webhook_signature(request, secret_key): signature_header = request.headers.get('Onestock-Signature') body = request.data signature_parts = signature_header.split(',') if len(signature_parts) < 2: return False timestamp = signature_parts[0].removeprefix("t=") if time.time() - int(timestamp) > 60 * 60 * 6: return False payload = f'{timestamp}.{body}' expected_signature = compute_hash(secret_key, payload) for i in range(1, len(signature_parts)): h = signature_parts[i].removeprefix(f'h{i}=') if hmac.compare_digest(h, expected_signature): return True return False def compute_hash(secret_key, payload): return hmac.new(bytes(secret_key, 'utf-8'), msg=bytes(payload, 'utf-8'), digestmod=hashlib.sha256).hexdigest()
const crypto = require('crypto'); function verifyWithPreviousKeys(req, previousKeys) { for (const key of previousKeys) { if (checkWebhookSignature(req, key)) { return true; } } return false; } function checkWebhookSignature(req, secretKey) { const signatureHeader = req.headers['Onestock-Signature']; const body = req.body; const signatureParts = signatureHeader.split(','); if (signatureParts.length < 2) { return false; } const timestamp = signatureParts[0].replace('t=', ''); if (Date.now() / 1000 - parseInt(timestamp) > 60 * 60 * 6) { return false; } const payload = `${timestamp}.${body}`; const expectedSignature = crypto.createHmac('sha256', secretKey).update(payload).digest('hex'); for (let i = 1; i < signatureParts.length; i++) { const h = signatureParts[i].replace(`h${i-1}=`, ''); if (h === expectedSignature) { return true; } } return false; }

API response best practice

To avoid unnecessary processing it is crucial to focus on the signature validation first.

For asynchronous content validation (if applicable): any additional checks should be performed asynchronously after acknowledging the message.

Signing a call to our APIs

To update a transaction, you will have to call either:

  • POST /external_payments/authorisation_update

  • POST /external_payments/capture_update

  • POST /external_payments/refund_update

You will have to add the Onestock-signature header to those calls. Find below a pre-request script for Postman to create your signature using the CryptoJS library, assuming that you have saved the secret hash in an environment variable hashkey.

const CryptoJS = require("crypto-js"); let hash = pm.environment.get("hashkey"); let requestBody = pm.request.body.raw; let time = parseInt(Date.now() / 1000); let payload = `${time}.${requestBody}`; let hmac = CryptoJS.enc.Hex.stringify(CryptoJS.HmacSHA256(payload, hash)) let header = `Onestock-Signature: t=${time},h0=${hmac}`; pm.request.addHeader(header);

4.2 Adding payment terminals in store

Once the connector is setup, the payment terminals can be added for every store directly from your OMC. It is up to the client to correctly setup those terminals depending on the connector specifications.

For a connector implementing direct captures or deferred captures, a POID of the terminal will be required for every store.

For a connector implementing a Pay By Link, you will be creating a virtual terminal.

5. FAQ

5.1. Debug API calls

You can find all requests to your payment connector in the order history logs in the backoffice.

See https://onestock.atlassian.net/wiki/spaces/DOCUMENTAT/pages/2280095751

5.2. Use case: Take payment from a POS

A client might want to use our Order In Store for queue busting but doesn't want to contract with our PSP partners.

They can implement the payment connector to bridge the payment to their POS if their POS allows it.

  • Creating a payment connector

  • Implement the Pay By Link workflow above

  • Pay By Link can answer a data that we display in our checkout screen in a barcode format that the POS can scan to take the payment

5.3. Resources

You can find a markdown file with the sequence diagrams that have to be implemented to be a starting point for your project specification to add specific requirements for every connection you wish to build.