TokenPay
for developers

Understand more about the innovative functionality of TokenPay and get to know different integration and whitelabeling options.

TokenPay solves fundamental structural problems of blockchain in payment transactions with technical innovations

The innovative TokenPay smart contracts solve known structural problems of decentralized exchange processes with regard to arbitrage and exchange rate risks while making optimal use of the available liquidity. Predominantly based on known liquidity pool schemes, these smart contracts form the core of the TokenPay technology, which is extended by various decentralized systems such as bridges and decentralized exchanges. Existing verified smart contracts are always used for continuous monitoring of the market situation and extended decentralized liquidity management. 

Exchange rate risk

Arbitrage

The exchange rate risk when using the blockchain arises through the use of an intermediary.

TokenPay technically manages to completely remove the exchange rate risk from the transaction process. The technical solution involves new smart contracts and a special way of linking transactions.

Cross-hierarchy smart contracts

TokenPay's new smart contracts allow a different recipient address to be specified alternately in a cross-hierarchy liquidity pool transaction. This means that the result of the liquidity pool swap, the stablecoins, are sent directly to the merchant's wallet address.

Bundling of transactions

The new liquidity pool implementation and the special broadcasting of transactions to the nodes make it possible to execute several transactions directly one after the other within a block, so that the exchange rate of the intermediary used cannot technically change between the two transactions.

Arbitrage when using the blockchain arises due to the lack of equalization of the cash flow

TokenPay uses technical measures to remove arbitrage from the transaction process. Utility tokens, which measure the circulation of different assets more efficiently, and decentralized liquidity pools with integrated market-making algorithms are combined for this purpose.

Decentralized market making pools

TokenPay's innovative multi-level pool structure enables the respective currencies in a pool to be balanced against each other via a higher-level pool. This balancing takes place on the basis of the transactions that have taken place and thus simulates the function of a central bank in traditional currency markets. In addition, oracles, bridging and DEX protocols are included in order to use the available liquidity as efficiently as possible.

Circulation measurement tokens

Variables cannot simply be stored on the blockchain. That is why we have created various utility tokens in the background that enable the individual smart contracts to read a circulating balance and a change in the intermediary over time in a decentralized manner using the account status of the respective utility tokens. Based on this, the cash flow of different currencies and stablecoins can be balanced without an external authority and the associated integrity risks.

TokenPay smart contracts are decentralized and have no influence on transactions

Money

TokenPay does not have access to third-party funds at any time, either during, before or after a transaction, nor does it receive any special transaction fees.

Data

TokenPay does not know any personal transaction data, no purchase history and only refers to the public data of the blockchain, as well as customer data provided by the customer.

Influence

TokenPay can neither prevent transactions nor reverse transactions. In principle, TokenPay has no direct influence on transactions, senders or recipients and the currencies used.

Integrate TokenPay into your application in just a few steps

Integrate TokenPay e-commerce via plug-in

Prerequisites

Aktuell stehen für TokenPay E-Commerce Plug-Ins für die Online-Shop Baukastensysteme WordPress/WooCommerce und Shopware zur Verfügung. Currently, for TokenPay E-Commerce plug-ins for the shop-systems WordPress/WooCommerce and Shopware are available. In addition, a plug-in for the Shopify online store modular system is currently under development. For integration, you need access to the administration interface of the respective modular system. Please request the required plug-in via the contact form.
Zur Integration benötigst Du Zugriff auf die Administrations-Oberfläche des jeweiligen Baukastensystems. 
Bitte frage das jeweilig benötigte Plug-In über das Kontaktformular an. 

 

Similar to other payment solutions, TokenPay requires a connection to your online store and the corporate customer dashboard. During the payment process, the end customer is redirected from the checkout page to a unique payment page hosted on https://checkout.usetokenpay.com. After finalizing the payment, the end customer is redirected to a customizable page.

For WordPress:

Make sure that WooCommerce is installed and set up on your WordPress site. Upload the ZIP source code file that you received from our support team as a new plug-in. Confirm the installation and activate the plug-in. You can then activate the payment method under WooCommerce / Settings / Payments / TokenPay and customize the title and description text displayed on your checkout page. Save your changes.

Also copy the displayed webhook. This must then be linked to the corporate customer dashboard.

For Shopware:

Open the Shopware admin panel. To access it, it may be necessary to open a separate browser window and clear your cache. Upload the ZIP source code file you received from our support team as a new plug-in. Confirm the installation and activate the plug-in. 

You will then find the “Payments and shipping” section under the “Storefront” settings. Activate the TokenPay payment gateway here. 

Then navigate to Settings / Payment method and activate the gateway as well. Here you can also view the current webhook under “Technical details”. This must then be linked to the corporate customer dashboard. Save your changes.

The access key is a unique key that is required to generate checkout sessions. This is uniquely assigned to your corporate customer account. 

Ask an authorized representative or a person authorized by the representative to create a corporate customer account for TokenPay. The publicly visible settings for configuring the checkout session must then be completed under “Settings” (top right). It is mandatory to upload the complete imprint information as well as a company logo and a background color to individualize the checkout sessions. In addition, the redirect URLs for a successful and a failed payment can be defined here. To redirect to the automatically generated “Thank You” pages of the modular systems, it is usually sufficient to enter the basic URL of the online store. In the last step, paste the copied endpoint URL under “Success URL” and “Error URL” - this is identical for both fields. Then save your data. You can now copy your access key.

Now enter the access key on the corresponding settings pages of your modular system. From now on it is possible to accept e-commerce payments with TokenPay.

Integrate TokenPay e-commerce via API

Prerequisites

A RESTful API is available for integrating TokenPay into individual online stores.

 

Similar to other payment solutions, TokenPay requires a connection to your online store and the corporate customer dashboard. During the payment process, the end customer is redirected from the checkout page to a unique payment page hosted on https://checkout.usetokenpay.com. After finalizing the payment, the end customer is redirected to a customizable page.

The API provided offers a detailed mechanism for accepting crypto payments, verifying them and responding to different scenarios. One of the main features of this payment infrastructure is the use of webhooks to forward the transaction status to the online store's server.

When a webhook is triggered, it sends a POST request to the configured endpoint with a JSON response.

				
					/*
Webhook Types:

payment_success:
Triggered when a payment has been successfully verified.
payment_failed: 
Triggered when a payment verification fails, or when there's an issue with the payment.

Events:
Payment Success:
type: Always`payment_success`
customId: Custom identifier for the session or order
Payment Failed:
type: Always `payment_failed`
customId: Custom identifier for the session or order
*/
				
			

Best practices for setting up webhooks:

  • The webhooks will always return a “200 OK” response, even if the event is “payment_failed”.
  • Log all received webhooks. This helps for possible troubleshooting and ensures that all events are processed.
  • Set up redundant webhook listeners: If a primary listener fails, a backup can save the day.
  • Monitor: Check at regular intervals that your webhook listeners are working. Warning mechanisms can be invaluable.
  • Performance: Design the webhook handler so that it confirms receipt immediately and only processes the user data afterwards. This ensures that the sending service does not have to wait and reduces the risk of redundant deliveries.

The access key is a unique key that is required to generate checkout sessions. This is uniquely assigned to your corporate customer account. 

Ask an authorised representative or a person authorised by them to create a corporate customer account for TokenPay. The publicly visible settings for configuring the checkout session must then be completed under ‘Settings’ (top right). It is mandatory to upload the complete imprint information as well as a company logo and a background colour to customise the checkout sessions. The redirect URLs for a successful and a failed payment can also be defined here. In the last step, add the created webhook endpoint URLs under ‘Success URL’ and ‘Error URL’ - it is also possible to specify the same endpoint for both event types. Then save your data. You can now copy your access key / API endpoint.

The ‘createCheckoutSession’ function serves as the end point for creating one-off checkout sessions.

 

				
					/*
Parameters:

- req (Object): The incoming request object.
    - req.query.accessKey (String): A UUID4 key to access the checkout configuration.
    - req.body.items (Array): An array of items for the checkout session. This is mandatory and must adhere to the specified format.
    - req.body.customId (String): An optional custom identifier for the checkout session.
    - req.body.successUrl (String): An optional URL where the user is redirected upon successful checkout.
- res (Object): The response object used to send responses back to the caller.
- next (Function): Express middleware function to pass control to the next handler.

Items Format:

Items provided in the req.body.items should be an array of objects with the following format:

items: [
  {
    name: "Special Item",   // Item name
    price: 0.001,           // Item price
    quantity: 1             // Quantity of the item
  },
  {
    name: "Another Item",
    price: 0.001,
    quantity: 1
  }
]

Response Format

The successful response will contain:

- url (String): A URL redirecting to the frontend checkout page with the session's ID.
- session (Object): An object containing:
    - id (String): The session's ID.
    - total (Number): The total amount for the checkout session.
    - items (Array): The items for the checkout session.
    - paymentStatus (String): The payment status for the checkout session.
    - customId (String): An optional custom identifier for the checkout session.
    - successUrl (String): The URL where the user is redirected upon a successful checkout.

Errors and Status Codes

The function can return various HTTP status codes and errors:

- 400: For invalid access keys, invalid items format or count, items missing required fields, invalid `successUrl` formats.
- 404: When the provided `accessKey` does not match any existing checkout configurations.
- 500: If any internal server errors occur.

Example Usage

Initiate a checkout session by sending a `POST` request to the endpoint where this function is mounted, with the required parameters in the body and query string.

Example Request:

POST https://BASE_URL/checkoutSession/V1/createCheckoutSession?accessKey=YOUR_ACCESS_KEY
{
  "items": [
    {
      "name": "Special Item",
      "price": 0.001,
      "quantity": 1
    },
    {
      "name": "Another Item",
      "price": 0.001,
      "quantity": 1
    }
  ],
  "customId": "YOUR_CUSTOM_ID",
  "successUrl": "http://example.com/success"
}

Example Response:

{
  "url": "https://BASE_URL/SESSION_ID",
  "session": {
    "id": "SESSION_ID",
    "total": 0.002,
    "items": [...],
    "paymentStatus": "pending",
    "customId": "YOUR_CUSTOM_ID",
    "successUrl": "http://example.com/success"
  }
}
*/
				
			

Integrate TokenPay Point of Sale via API

Information

A RESTful API is available for integrating TokenPay into individual online stores.

TokenPay Point of Sale is available as a static QR code solution or as a dynamic QR code solution in conjunction with an end device of your choice on site. In order to achieve the best possible efficiency and user experience on site, we are happy to customise the interfaces and user interfaces according to your needs.

We are also happy to provide an interface to your checkout systems or an individualised overview on your end devices on site. It is also possible to customise the checkout sessions and add survey elements, for example. Get in touch now to find out more about the possibilities.

Integrate TokenPay Blockchain into your application

Information

TokenPay uses the Polygon blockchain to bundle all data and settle most transactions. Other blockchains are connected via bridges. 

Similar to other payment solutions, TokenPay requires a connection to your online shop and the corporate customer dashboard. During the payment process, the end customer is redirected from the checkout page to a unique payment page hosted on https://checkout.usetokenpay.com. After finalising the payment, the end customer is redirected to an individually configurable page. This process is also used if you integrate TokenPay Blockchain into your central interface.

It is also possible to integrate TokenPay Blockchain directly at smart contract level. In this case, integration on the Polygon blockchain is necessary. If your project is not or not primarily active on Polygon, it is also possible to set up interchain messaging protocols that interact with the emitted events of the TokenPay smart contracts. This means that TokenPay Blockchain remains open to all technologies within the blockchain world. We are also happy to support you in developing a customised solution.

You can find the current TokenPay Factory Contract here.

You can find the current TokenPay router contract here.

You can find the current intermediary token contract here.

Please note that all deployment addresses refer to Polygon mainnet.

Further routings, calculations and integrations of partners take place centrally and are confirmed individually by the end users in the authorisation requests with the respective wallets. 

The integration of TokenPay Blockchain into central systems is analogous to the integration of TokenPay E-Commerce.

The integration of TokenPay smart contracts can be done flexibly according to your needs. Below you will find the code reference of the current TokenPay SwapRouter entry point contract - further interfaces can be found here:

				
					// SPDX-License-Identifier: unlicensed
// (c) Copyright 2024 Kolibri GmbH, all rights reserved
pragma solidity ^0.8.9;

import '../../core/interfaces/one-to-one/ISwapFactory.sol';
import '../interfaces/one-to-one/ISwapRouter.sol';
import '../../core/interfaces/ISwapPair.sol';
import '../../core/interfaces/ISwapERC20.sol';
import '../libraries/SafeMath.sol';

contract SwapRouter is ISwapRouter {
  using SafeMath for uint;

  address public immutable override factory;
  address public immutable override WETH;

  modifier ensure(uint deadline) {
    require(deadline >= block.timestamp, 'SwapRouter: EXPIRED');
    _;
  }

  uint private unlocked = 1;
  modifier lock() {
    require(unlocked == 1, 'Swap: LOCKED');
    unlocked = 0;
    _;
    unlocked = 1;
  }

  modifier onlyNormal() {
    if (ISwapFactory(factory).getEoaWhitelist(msg.sender)) {
    } else {
      require(tx.origin == msg.sender, 'SwapRouter: onlyNormal failed'); 
    }
    _;
  }

  modifier once(address pair) {
    if (!ISwapFactory(factory).getOnce(pair, msg.sender)) {
    } else {
      if (ISwapFactory(factory).getEoaWhitelist(msg.sender)) {
      } else {
        require(false, 'SwapRouter: Once failed');
      }
    }
    _;
    ISwapFactory(factory).addOnce(pair, msg.sender);
  }

  constructor(address _factory, address _WETH) {
    factory = _factory;
    WETH = _WETH;
  }

  receive() external payable {
    assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
  }

  function getBlockNumber() private view onlyNormal returns (uint blockNumber) {
    blockNumber = block.number;
  }

  // **** ADD LIQUIDITY ****
  function _addLiquidity(
    address tokenA,
    address tokenB,
    uint amountADesired,
    uint amountBDesired
  ) private onlyNormal returns (uint amountA, uint amountB) {
    // create the pair if it doesn't exist yet
    if (ISwapFactory(factory).getPair(tokenA, tokenB) == address(0)) {
      ISwapFactory(factory).createPair(tokenA, tokenB);
    }
    (amountA, amountB) = (amountADesired, amountBDesired);
  }

  function addLiquidity(
    address tokenA,
    address tokenB,
    uint amountADesired,
    uint amountBDesired,
    address to,
    uint deadline
  ) external override ensure(deadline) lock onlyNormal once(pairFor(tokenA, tokenB)) returns (uint amountA, uint amountB, uint liquidity) {
    (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired);
    address pair = pairFor(tokenA, tokenB);
    safeTransferFrom(tokenA, msg.sender, pair, amountA);
    safeTransferFrom(tokenB, msg.sender, pair, amountB);
    liquidity = ISwapPair(pair).mint(to);
    ISwapFactory(factory).addTime(pair, msg.sender);
  }

  // **** REMOVE LIQUIDITY ****
  function removeLiquidity(
    address tokenA,
    address tokenB,
    uint liquidity,
    address to,
    uint deadline
  ) public override ensure(deadline) lock onlyNormal returns (uint amountA, uint amountB) {
    address pair = pairFor(tokenA, tokenB);
    require(ISwapFactory(factory).getTime(pair, msg.sender) + ISwapFactory(factory).getTimeToElapse() < getBlockNumber(), 'SwapRouter: Wait for elapsed time');
    ISwapPair(pair).transferFrom(msg.sender, pair, liquidity, factory); // send liquidity to pair
    (uint amount0, uint amount1) = ISwapPair(pair).burn(to);
    (address token0, ) = sortTokens(tokenA, tokenB);
    (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
  }

  // **** SWAP ****
  // requires the initial amount to have already been sent to the first pair
  function _swap(uint[] memory amounts, address[] memory path, address _to) private {
    for (uint i; i < path.length - 1; i++) {
      (address input, address output) = (path[i], path[i + 1]);
      (address token0, ) = sortTokens(input, output);
      uint amountOut = amounts[i + 1];
      (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
      address to = i < path.length - 2 ? pairFor(output, path[i + 2]) : _to;
      ISwapPair(pairFor(input, output)).swap(amount0Out, amount1Out, to, new bytes(0));
    }
  }

  function swapExactTokensForTokens(
    uint amountIn,
    uint amountOutMin,
    address[] calldata path,
    address to,
    uint deadline
  ) external override ensure(deadline) lock onlyNormal returns (uint[] memory amounts) {
    amounts = getAmountsOutInternal(amountIn, path);
    require(amounts[amounts.length - 1] >= amountOutMin, 'SwapRouter: INSUFFICIENT_OUTPUT_AMOUNT');
    safeTransferFrom(path[0], msg.sender, pairFor(path[0], path[1]), amounts[0]);
    _swap(amounts, path, to);
  }

  function doubleSwapExactTokensForTokens(
    uint amountInFirst,
    uint amountInSecond,
    uint amountOutMinFirst,
    uint amountOutMinSecond,
    address[] calldata pathFirst,
    address[] calldata pathSecond,
    address toSecond,
    uint deadline
  ) external override ensure(deadline) lock onlyNormal returns (uint[] memory amountsFirst, uint[] memory amountsSecond) {

    // Make sure it is a double swap
    require(pathFirst[0] == pathSecond[1] && pathFirst[1] == pathSecond[0], 'SwapRouter: INPUT_PATH_MISMATCHING');

    // First swap
    amountsFirst = getAmountsOutInternal(amountInFirst, pathFirst);
    require(amountsFirst[amountsFirst.length - 1] >= amountOutMinFirst, 'SwapRouter: INSUFFICIENT_OUTPUT_AMOUNT');
    safeTransferFrom(pathFirst[0], msg.sender, pairFor(pathFirst[0], pathFirst[1]), amountsFirst[0]);
    _swap(amountsFirst, pathFirst, msg.sender);

    // Second swap
    amountsSecond = getAmountsOutInternal(amountInSecond, pathSecond);
    require(amountsSecond[amountsSecond.length - 1] >= amountOutMinSecond, 'SwapRouter: INSUFFICIENT_OUTPUT_AMOUNT');
    safeTransferFrom(pathSecond[0], msg.sender, pairFor(pathSecond[0], pathSecond[1]), amountsSecond[0]);
    _swap(amountsSecond, pathSecond, toSecond);
  }

  function getAmountsOut(
    uint amountIn,
    address[] memory path
  ) public override lock onlyNormal returns (uint[] memory amounts) {
    return getAmountsOutInternal(amountIn, path);
  }

  function safeTransferFrom(
    address token,
    address from,
    address to,
    uint256 value
  ) private onlyNormal {
      // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
      (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
      require(
          success && (data.length == 0 || abi.decode(data, (bool))),
          'SwapRouter::transferFrom: transferFrom failed'
      );
  }

  // returns sorted token addresses, used to handle return values from pairs sorted in this order
  function sortTokens(address tokenA, address tokenB) private view onlyNormal returns (address token0, address token1) {
    require(tokenA != tokenB, 'SwapRouter: IDENTICAL_ADDRESSES');
    (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
    require(token0 != address(0), 'SwapRouter: ZERO_ADDRESS');
  }

  function pairFor(address tokenA, address tokenB) private view onlyNormal returns (address pair) {
    (address token0, address token1) = sortTokens(tokenA, tokenB);
    pair = ISwapFactory(factory).getPair(token0, token1);
  }

  // fetches and sorts the reserves for a pair
  function getReserves(
    address tokenA,
    address tokenB
  ) private view onlyNormal returns (uint reserveA, uint reserveB) {
    (address token0, ) = sortTokens(tokenA, tokenB);
    (uint reserve0, uint reserve1, ) = ISwapPair(pairFor(tokenA, tokenB)).getReserves();
    (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
  }

    // given an input amount of an asset returns the maximum output amount of the other asset
  function getAmountOut(
    uint amountIn,
    uint reserveIn,
    uint reserveOut,
    address outTokenAddr,
    address inTokenAddr
  ) private view onlyNormal returns (uint amountOut) {
    require(amountIn > 0, 'SwapRouter: INSUFFICIENT_INPUT_AMOUNT');
    require(reserveIn > 0 && reserveOut > 0, 'SwapRouter: INSUFFICIENT_LIQUIDITY');
    ISwapERC20 outToken = ISwapERC20(outTokenAddr);
    ISwapERC20 inToken = ISwapERC20(inTokenAddr);
    uint amount;
    if (outToken.decimals() == inToken.decimals()) {
      amount = amountIn;
    } else {
      if (outToken.decimals() > inToken.decimals()) {
        uint8 decimalsDiff = outToken.decimals() - inToken.decimals();
        amount = amountIn * 10 ** decimalsDiff;
      } else {
        uint8 decimalsDiff = inToken.decimals() - outToken.decimals();
        amount = amountIn / 10 ** decimalsDiff;
      }
    }
    ISwapFactory factoryInstance = ISwapFactory(factory);

    // If swapped from any to UHU / ALPHA / ...: Deduct fee
    uint amountAfterSwappingFee;
    if (factoryInstance.feeableTokens(outTokenAddr)) {
      amountAfterSwappingFee = amount.mul((1000 - factoryInstance.getFeeBase())) / 1000;
    } else { 
      // If swapped from any to any: Deduct no fee; 1:1 curve
      amountAfterSwappingFee = amount;
    }
    
    // If inToken is feeable
    if (factoryInstance.feeableTokens(inTokenAddr)) {
      if (factoryInstance.feeableTokens(outTokenAddr)) {
        amountOut = amountAfterSwappingFee;
      } else {
        amountOut = amountAfterSwappingFee * 1000 / factoryInstance.getCurrencyConversion(outTokenAddr);
      }
    } else { // If inToken is not feeable (all other tokens)
      if (factoryInstance.feeableTokens(outTokenAddr)) {
        amountOut = amountAfterSwappingFee.mul(factoryInstance.getCurrencyConversion(inTokenAddr)) / 1000;
      } else {
        amountOut = amountAfterSwappingFee * factoryInstance.getCurrencyConversion(inTokenAddr) / factoryInstance.getCurrencyConversion(outTokenAddr);
      }
    }
  }

  // performs chained getAmountOut calculations on any number of pairs
  function getAmountsOutInternal(
    uint amountIn,
    address[] memory path
  ) private view onlyNormal returns (uint[] memory amounts) {
    require(path.length >= 2, 'SwapRouter: INVALID_PATH');
    amounts = new uint[](path.length);
    amounts[0] = amountIn;
    for (uint i; i < path.length - 1; i++) {
      (uint reserveIn, uint reserveOut) = getReserves(path[i], path[i + 1]);
      amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut, path[i + 1], path[i]);
    }
  }

}
				
			

Integrate TokenPay Payment Links via API

Information

TokenPay Payment Links are designed as a no-code solution and allow you to fully configure and manage your TokenPay corporate customer account.

However, if you are interested in a customised solution for the mass management of payment links, please contact us.

 

Get in touch

en_USEnglish