v1.0

Cards#

CentaPay supports card-present-not-present (CNP) transactions across Kazakhstan and Uzbekistan.

KZ · UZ PCI DSS Required 3DS Supported

SALE — Single Message (SMS)#

Authorise and capture in one step. This is the standard flow for immediate payments.

POST https://{PAYMENT_URL}/post
curl -X POST https://{PAYMENT_URL}/post \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "action=SALE" \
  -d "client_key={CLIENT_KEY}" \
  -d "order_id=ORD-001" \
  -d "order_amount=5000" \
  -d "order_currency=KZT" \
  -d "order_description=Product+purchase" \
  -d "card_number=4111111111111111" \
  -d "card_exp_month=01" \
  -d "card_exp_year=2038" \
  -d "card_cvv2=000" \
  -d "payer_first_name=John" \
  -d "payer_last_name=Smith" \
  -d "[email protected]" \
  -d "payer_phone=77001234567" \
  -d "payer_country=KZ" \
  -d "payer_city=Astana" \
  -d "payer_address=10+Kunayeva+St" \
  -d "payer_zip=010000" \
  -d "payer_ip=192.168.1.1" \
  -d "term_url_3ds=https://yoursite.com/3ds-return" \
  -d "hash={HASH}"
$card = '4111111111111111';
$params = [
  'action'           => 'SALE',
  'client_key'       => CLIENT_KEY,
  'order_id'         => 'ORD-001',
  'order_amount'     => '5000',
  'order_currency'   => 'KZT',
  'order_description'=> 'Product purchase',
  'card_number'      => $card,
  'card_exp_month'   => '01',
  'card_exp_year'    => '2038',
  'card_cvv2'        => '000',
  'payer_first_name' => 'John',
  'payer_last_name'  => 'Smith',
  'payer_email'      => '[email protected]',
  'payer_phone'      => '77001234567',
  'payer_country'    => 'KZ',
  'payer_city'       => 'Astana',
  'payer_address'    => '10 Kunayeva St',
  'payer_zip'        => '010000',
  'payer_ip'         => $_SERVER['REMOTE_ADDR'],
  'term_url_3ds'     => 'https://yoursite.com/3ds-return',
];
// Formula 1 hash
$cp = substr($card,0,6).substr($card,-4);
$params['hash'] = md5(strtoupper(
  strrev($params['payer_email']).PASSWORD.strrev($cp)
));
$ch = curl_init(PAYMENT_URL.'/post');
curl_setopt_array($ch,[
  CURLOPT_POST=>true,
  CURLOPT_POSTFIELDS=>http_build_query($params),
  CURLOPT_RETURNTRANSFER=>true,
]);
$result = json_decode(curl_exec($ch),true);
import requests, hashlib

card = '4111111111111111'
email = '[email protected]'
cp = card[:6] + card[-4:]
h = hashlib.md5((email[::-1] + PASSWORD + cp[::-1]).upper().encode()).hexdigest()

resp = requests.post(f'{PAYMENT_URL}/post', data={
    'action': 'SALE',
    'client_key': CLIENT_KEY,
    'order_id': 'ORD-001',
    'order_amount': '5000',
    'order_currency': 'KZT',
    'order_description': 'Product purchase',
    'card_number': card,
    'card_exp_month': '01',
    'card_exp_year': '2038',
    'card_cvv2': '000',
    'payer_first_name': 'John',
    'payer_last_name': 'Smith',
    'payer_email': email,
    'payer_phone': '77001234567',
    'payer_country': 'KZ',
    'payer_city': 'Astana',
    'payer_address': '10 Kunayeva St',
    'payer_zip': '010000',
    'payer_ip': '192.168.1.1',
    'term_url_3ds': 'https://yoursite.com/3ds-return',
    'hash': h,
})
const crypto = require('crypto');
const axios = require('axios');
const qs = require('qs');
const rev = s => s.split('').reverse().join('');

const card = '4111111111111111';
const email = '[email protected]';
const cp = card.slice(0,6) + card.slice(-4);
const hash = crypto.createHash('md5')
  .update((rev(email) + PASSWORD + rev(cp)).toUpperCase())
  .digest('hex');

const {data} = await axios.post(`${PAYMENT_URL}/post`,
  qs.stringify({
    action:'SALE', client_key:CLIENT_KEY,
    order_id:'ORD-001', order_amount:'5000',
    order_currency:'KZT', order_description:'Product purchase',
    card_number:card, card_exp_month:'01',
    card_exp_year:'2038', card_cvv2:'000',
    payer_first_name:'John', payer_last_name:'Smith',
    payer_email:email, payer_phone:'77001234567',
    payer_country:'KZ', payer_city:'Astana',
    payer_address:'10 Kunayeva St', payer_zip:'010000',
    payer_ip:req.ip,
    term_url_3ds:'https://yoursite.com/3ds-return',
    hash
  }));

Currencies#

CountryCurrencyCodeAmount Format
🇰🇿 KazakhstanKazakhstani TengeKZTInteger — e.g. 5000
🇺🇿 UzbekistanUzbekistani SomUZSInteger — e.g. 120000
InternationalUS DollarUSDFloat XX.XX — e.g. 49.99

AUTH & CAPTURE — Dual Message (DMS)#

Authorise only (hold funds), then capture separately. Add auth=Y to the SALE request. For AUTH, you can set order_amount to 0 for card validation / tokenisation only.

To capture, send a CAPTURE request with the trans_id from the AUTH response. Partial capture is supported (once only). See CAPTURE reference.

ℹ️
A successful AUTH returns status: PENDING. The funds are held on the cardholder's account. You must CAPTURE within the hold period (typically 7 days, varies by issuer) or the hold expires.

Tokenisation#

Request a card token on the first transaction by adding req_token=Y to the SALE request. The response and callback return a card_token you can store and reuse for future charges without transmitting card data again.

To charge using a stored token, send card_token instead of card_number, card_exp_month, card_exp_year, card_cvv2. The hash formula changes — use the card_token variant of Formula 1.

⚠️
PCI scope: Even with tokenisation, your initial SALE request transmits raw card data server-to-server. Subsequent token-based charges reduce scope.

Sub-Merchants#

Use the channel_id parameter to route transactions to specific sub-merchants. This value is echoed in callbacks and available in reporting, enabling you to reconcile at sub-merchant level.