Быстрый старт

Введение

Эта инструкция — о том, как организовать проведение платежей через программный интерфейс Gate платёжной платформы ecommpay. С тем, чтобы использовать свой пользовательский интерфейс и обеспечивать максимальное взаимодействие с пользователями на стороне веб-сервиса, а все взаимодействия с платёжной платформой выполнять на программном уровне, „под капотом“. И чтобы при этом можно было применять проверенные быстрые решения — с чёткими инструкциями и примерами кода на популярных языках программирования — PHP и Go.

В рамках этой инструкции рассматриваются действия, необходимые для проведения наиболее востребованного вида платежей — оплат в одну стадию с прямым использованием платёжных карт, — а также действия, необходимые для выполнения возвратов по таким оплатам. Поддержка этих функциональных возможностей, как правило, оптимальна для первичного тестирования и быстрого запуска платёжных проектов в работу. Кроме того, поддержавПоддержав эти возможности, можно довольно легко поддержать и любые другие, поскольку в основе работы с любыми типами платежей и платёжными методами, поддерживаемыми в платформе, лежат те же принципы и во многом те же процедуры, что и в основе работы с одностадийными карточными оплатами. Кроме того, эта инструкция может быть полезна, даже если вам не актуальны оплаты через Gate, но актуальны возвраты или выплаты.

Если вам не актуальны оплаты через Gate и требуется настроить лишь работу с возвратами или выплатами, можно освоить с помощью этой инструкции работу с подписью, ответами и оповещениями и перейти после этого к реализации соответствующих дополнений. Если актуально использовать Gate лишь для контроля состояния отдельных платежей, можно освоить здесь работу с подписью и перейти к статье Получение информации о состоянии платежа. И наконец, если актуально что-то принципиально другое, можно сориентироваться в соответствующих вариантах.

Рис.: Другие варианты

  • Если надо настроить решения с использованием платёжной формы ecommpay на сайтах и в мобильных приложениях, можно обратиться к разделам Payment Page и Интеграция с использованием SDK.
  • Если надо настроить оплаты с вызовом платёжной формы ecommpay по платёжным ссылкам, можно освоить с помощью этой инструкции работу с подписью, ответами и оповещениями и перейти после этого к соответствующей статье: Оплата по платёжной ссылке. Кроме того, оплаты по ссылкам можно инициировать и вручную, через интерфейс Dashboard, предназначенный для сотрудников мерчантов (подробнее).
  • Если надо настроить получение через программный интерфейс информации об операциях, опротестованиях и балансах, следует обратиться к разделу о работе с интерфейсом Data API.
  • Наконец, если актуально что-то ещё, можно обратиться к другим разделам настоящей документации и к специалистам ecommpay.
Совет: Отдельно можно отметить, что наряду с техническими вопросами для начала проведения платежей через Gate необходимо решить и организационные, в том числе вопросы о соответствии требованиям PCI DSS, если планируется проводить платежи с использованием платёжных карт. Информацию об этом можно найти в статье Организация взаимодействия.

На этом с вводными всё. Можно переходить к делу.

Краткая теория

Проекты и ключи

Работу с платёжной платформой ecommpay можно сравнить с использованием услуг гостиницы. Так, для заселения в гостиницу обычно необходимо получить номер и ключ от него, а для начала работы с платформой надо получить... проект и ключ от него. И как с номерами в гостиницах, в платформе может предоставляться разное количество проектов для одного клиента — под разные цели и задачи — при этом для каждого проекта (как и для каждого гостиничного номера) необходим свой ключ.

Как правило, для работы достаточно одного тестового и одного рабочего проектов. Это типичный случай, и в рамках быстрого старта мы исходим из него. Если вам по какой-либо причине необходимо больше проектов, это стоит обсудить с курирующим менеджером, но начать всё также можно с одного тестового проекта.

Если у вас уже есть идентификатор тестового проекта (project_id) и секретный ключ для него (secret_key), можно приготовиться к их использованию и переходить дальше. Если же вы ещё не бронировали тестовый проект, самое время это сделать и вернуться сюда.

Для начала работы с платформой следует получить проект и ключ от него. Если у вас уже есть идентификатор (project_id) и ключ (secret_key) тестового проекта, можно приготовиться к их использованию и переходить дальше. Если же вы ещё не оформляли тестовый проект, самое время это сделать и вернуться сюда.

Схема работы

Чтобы корректно проводить платежи через Gate, необходимо обеспечить сбор актуальных параметров, формирование и отправку к платёжной платформе соответствующих запросов и приём и обработку ответной информации от платформы. При этом все взаимодействия с пользователями (для сбора и отображения релевантной информации) должны обеспечиваться на стороне веб-сервиса с использованием собственных решений, в то время как все остальные процедуры (для обработки используемой информации) можно реализовывать на основе представленных далее примеров кода.

Если сфокусировать внимание на технических аспектах для веб-сервиса и платёжной платформы, то проведение оплаты можно представить следующим образом.

  В веб‑сервисе В платёжной платформе
1 При готовности пользователя оплатить заказ получаем все необходимые данные, фиксируем параметры платежа и подписываем их, после чего формируем запрос на проведение платежа и отправляем его в платёжную платформу
2 Информируем пользователя о том, что платёж проводится Принимаем запрос и работаем по нему, чтобы провести платёж. Если актуально, отправляем оповещение о необходимых действиях
3 Если актуально, принимаем оповещение о необходимых действиях, выполняем эти действия (с участием пользователя или без него) и отправляем в платёжную платформу запрос на продолжение платежа
4 Информируем пользователя о том, что платёж проводится Если актуально, принимаем дополнительный запрос и выполняем необходимые действия для продолжения платежа, после чего отправляем к веб-сервису оповещение о результате платежа
5 Принимаем оповещение с информацией о результате платежа и отображаем пользователю необходимые сведения

При работе с платежами других типов выполняемые действия могут отличаться, но в целом схема соответствует приведённой. Реализовывать её на стороне веб-сервиса можно самыми разными способами. Здесь, в рамках быстрого старта, описываются базовые процедуры, которые можно использовать и адаптировать в соответствии со спецификой вашего веб-сервиса.

Параметры запросов

Обязательный набор параметров, необходимых для проведения конкретного платежа, может отличаться в зависимости от типа этого платежа, специфики платёжного метода и платёжной системы, региональных особенностей и других факторов. Так, в каких-то случаях может требоваться указывать назначение платежа, адрес пользователя или другую информацию, в то время как в других случаях эти сведения могут быть необязательны. Поэтому при настройке работы с разными типами платежей и платёжными методами следует обращаться к соответствующим статьям настоящей документации и к спецификации Gate API.

Обязательный набор параметров, необходимых для проведения конкретного платежа, может отличаться в зависимости от различных факторов, поэтому при настройке работы с разными типами платежей и платёжными методами следует обращаться к соответствующим статьям настоящей документации и к спецификации Gate API.

Для проведения карточной оплаты в базовом случае следует определиться с её суммой и валютой, добавить к этим двум параметрам три идентификатора (проекта, платежа и пользователя) и данные платёжной карты, а затем сформировать подпись к этим параметрам.

Прим.: Данные карт могут указываться в явном виде, а также через стандартизированные токены и произвольные идентификаторы ранее сохранённых данных. Если соответствующие токены и идентификаторы ещё не созданы или информация о картах не перенесена в платформу ecommpay от другого провайдера, то сначала следует выполнить действия, необходимые для их создания. К этому можно приступить после настройки проведения оплат, их первичного тестирования и запуска — то есть после выполнения основных действий, описанных в этой инструкции.

Если данные карты указываются в явном виде, то к числу обязательных параметров относятся следующие.

Параметр Описание
general — объект, содержащий параметры с основными идентификационными сведениями запроса

project_id
integer

Идентификатор проекта. Его вместе с ключом выдаёт ecommpay, и его важно точно указывать даже в тестовых запросах. Иначе… стоит ждать реакцию, как при попытке зайти в чужой гостиничный номер, полученный от ecommpay.
Пример: 42

payment_id
string

Идентификатор платежа. Он может быть произвольным, но каждый раз должен быть уникальным в рамках используемого проекта. Иначе стоит ждать ошибку обращения, формируемый на стороне веб-сервиса.
Пример: Cosmoshop_purchase_2025-01-01_000001

signature
string

Подпись к параметрам запроса. Она формируется в соответствии со специальным алгоритмом, описанным далее. При этом для тестового проекта следует использовать тестовый ключ, для рабочего — рабочий, формируемая описанным далее способом.
Пример: rnv1OS3PJUKEJ5kw5wqoK0ftZGSd4Q6LX5A5NxK6d5alpND4sQTRFt7/9aFV+m3SRwNB8ba98GMsOY91yTVhEQ==

payment — объект, содержащий параметры с основными сведениями о платеже

payment_amount
integer

Сумма платежа. В тестовых запросах может быть произвольной, а в реальных должна точно соответствовать сумме заказа. Приводится, в дробных единицах валюты.
Пример: 8855 (для суммы 88,55)

payment_currency
string

Код валюты платежа. Приводится, в трёхбуквенном формате ISO 4217 alpha-3. В тестовых запросах могут использоваться любые из действующих кодов, а в реальных каждый раз должен использоваться код той валюты, в которой инициируется платёж. Для сверки можно использовать справочник валют.
Пример: USD

customer — объект, содержащий параметры с основными сведениями о пользователе

customer_id
string

Идентификатор пользователя в веб-сервисе. Может быть произвольным и повторяемым в разных запросах, но для каждого реального пользователя должен быть однозначно сопоставляемым с его учётной записью в веб-сервисе и уникальным в рамках проекта. Иначе возможны различные коллизии, в том числе при оценке рисков проведения платежей.
Пример: 17008

ip_address

IP-адрес пользователя. В тестовых запросах можно указывать IP-адрес мерчанта, в рабочих запросах каждый раз должен указываться IP-адрес, с которого пользователь инициирует оплату.
Пример: 248.121.176.220

card — объект, содержащий параметры со сведениями о платёжной карте пользователя

pan
string

Номер платёжной карты. В тестовых запросах можно указывать любые реалистичные значения, в том числе специальные, приведённые далее; в рабочих запросах должны использоваться реальные данные.
Пример: 4242424242424243

year
string

Порядковый номер года, в котором истекает срок действия карты, в формате ГГГГ. В тестовых запросах можно указывать произвольные значения, но в корректном формате и так, чтобы срок действия был актуален; в рабочих запросах должны использоваться реальные данные.
Пример: 2025

month
string

Порядковый номер месяца, в котором истекает срок действия карты, в виде числа от 1 до 12. В тестовых запросах можно указывать произвольные значения, но в корректном формате и так, чтобы срок действия был актуален; в рабочих запросах должны использоваться реальные данные.
Пример: 5

card_holder
string

Имя пользователя, в соответствии с указанным на карте и с учётом применяемых ограничений. В тестовых запросах можно указывать произвольные значения; в рабочих запросах должны использоваться реальные данные.
Пример: SONYA KOVALEVSKY

cvv
string

Код проверки подлинности карты, в соответствии с указанным на карте или полученным держателем от эмитента. В тестовых запросах можно указывать произвольные значения из трёх цифр; в рабочих запросах должны использоваться реальные данные.
Пример: 345

В запросе такой набор параметров может выглядеть следующим образом.

Рис.: Пример набора параметров для проведения карточной оплаты

{
  "general": {
      "project_id": 42,
      "payment_id": "Cosmoshop_purchase_2025-01-01_000001",
      "signature": "rdCiqlibt8SUMe3OVPNfKMYnQjQ6dkEvRQhMkJRg9ZJULdsKJEZU21E5Y/ISdv0FtXi3oJE5n4hSNK3Owo7Axw=="
  },
    "customer": {
      "ip_address": "248.121.176.220",
      "id": "17008"
  },
    "payment": {
      "amount": 8855,
      "currency": "USD"
  },
    "card": {
      "pan": "4242424242424243",
      "year": 2025,
      "month": 5,
      "card_holder": "SONYA KOVALEVSKY",
      "cvv": "123"
  }  
}

Вместе с тем, при проведении платежей могут требоваться и другие параметры:

  • Параметры, необходимые в различных случаях, исходя из специфики конкретных платёжных систем и региональных особенностей. Если такие параметры не были переданы изначально, платёж может быть отклонён либо эти параметры могут быть запрошены в процессе его проведения.
  • Параметры, необходимые в различных случаях, исходя из специфики веб-сервиса, например при выполнении аутентификации 3‑D Secure на стороне веб-сервиса или для отправки уведомления о результате платежа пользователю. Если такие параметры не были переданы, то соответствующие возможности могут не поддерживаться.

Для работы с такими случаями может потребоваться настроить дополнительные процедуры. Они частично затронуты далее, а также отдельно разобраны в разделе Дополнения, к которому лучше переходить после настройки и тестирования всех базовых процедур.

В целом, сбор всех необходимых параметров можно настраивать как удобно, с оглядкой на архитектуру вашего веб-сервиса и иные факторы (например, с задействованием используемых справочников и баз данных). Здесь же, определившись с обязательными параметрами, можно приступать к реализации.

Базовая реализация

Введение

Реализовывать функции веб-сервиса для проведения платежей можно по-разному, в том числе за счёт создания своих программных решений. В рамках этого быстрого старта мы рассматриваем реализацию с использованием готового кода от ecommpay для подписывания данных, отправки запросов, приёма ответов на эти запросы и программных оповещений, и, вместе с тем, с использованием ваших решений на стороне веб-сервиса для всех остальных действий, включая получение информации от пользователей и предоставление им информации о проведении платежа.

Подписывание данных

Когда для всех необходимых параметров определены их значения (и только в таком случае), можно формировать подпись к ним и готовить запрос.

Рис.: Пример кода на PHP для работы с цифровой подписью (с использованием базового набора параметров для карточных оплат)

*/
class Signer
{
    const ITEMS_DELIMITER = ';';
    const ALGORITHM = 'sha512';
    const IGNORED_KEYS = ['frame_mode'];

    /**
     * Secret key
     *
     * @var string
     */
    private $secretKey;

    /**
     * __construct
     *
     * @param string $secretKey
     */
    public function __construct(string $secretKey)
    {
        $this->secretKey = $secretKey;
    }

    /**
     * Check signature
     *
     * @param array $params
     * @param string $signature
     * @return boolean
     */
    public function check(array $params, string $signature): bool
    {
        return $this->sign($params) === $signature;
    }

    /**
     * Return signature
     *
     * @param array $params
     * @return string
     */
    public function sign(array $params): string
    {
        $stringToSign = implode(self::ITEMS_DELIMITER, $this->getParamsToSign($params, self::IGNORED_KEYS));
        return base64_encode(hash_hmac(self::ALGORITHM, $stringToSign, $this->secretKey, true));
    }

    /**
     * Get parameters to sign
     *
     * @param array $params
     * @param array $ignoreParamKeys
     * @param string $prefix
     * @param bool $sort
     * @return array
     */
    private function getParamsToSign(
        array $params,
        array $ignoreParamKeys = [],
        string $prefix = '',
        bool $sort = true
    ): array {
        $projectId = 42;
        $paymentId = '12345';
        $customerId = '123';
        $ip = '192.168.1.1';
        $paymentAmount = '1000';
        $paymentCurrency = 'USD';
        $cardPan = '*4567';
        $cardYear = '2029';
        $cardMonth = '09';
        $cardHolder = 'SONYA KOVALEVSKY';
        $cardCvv = '123';


        $paramsToSign = [
            'general'  => [
            'project_id' => $projectId,
            'payment_id' => $paymentId,
            ],
            'customer'  => [
            'id' => $customerId,
            'ip_address' => $ip,
            ],
            'payment'  => [
            'payment_amount' => $paymentAmount,
            'payment_currency' => $paymentCurrency,
            ],
            'card'  => [
            'pan' => $cardPan,
            'year' => $cardYear,
            'month' => $cardMonth,
            'card_holder' => $cardHolder,
            'cvv' => $cardCvv,
            ],
        ];

        foreach ($params as $key => $value) {
            if (in_array($key, $ignoreParamKeys, true)) {
                continue;
            }

            $paramKey = ($prefix ? $prefix . ':' : '') . $key;
            if (is_array($value)) {
                $subArray = $this->getParamsToSign($value, $ignoreParamKeys, $paramKey, false);
                $paramsToSign = array_merge($paramsToSign, $subArray);
            } else {
                if (is_bool($value)) {
                    $value = $value ? '1' : '0';
                } else {
                    $value = (string)$value;
                }

                $paramsToSign[$paramKey] = $paramKey . ':' . $value;
            }
        }

        if ($sort) {
            ksort($paramsToSign, SORT_NATURAL);
        }

        return $paramsToSign;
    }
}

Рис.: Пример кода на Go для работы с цифровой подписью (с использованием базового набора параметров для карточных оплат)

package main

import (
	"crypto/hmac"
	"crypto/sha512"
	"encoding/base64"
	"sort"
)

const itemsDelimiter = ";"

var ignoredKeys = map[string]struct{}{"frame_mode": struct{}{}}

var secretKey string

func SetSecretKey(key string) {
	secretKey = key
}

func Check(params map[string]map[string]string, signature string) bool {
	return Sign(params) == signature
}

func Sign(params map[string]map[string]string) string {
	var stringToSign string

	paramsToSign := getParamsToSign(params, ignoredKeys, "", true)
	for _, value := range paramsToSign {
		for _, v := range value {
			stringToSign = stringToSign + itemsDelimiter + v
		}
	}

	mac := hmac.New(sha512.New, []byte(secretKey))
	mac.Write([]byte(stringToSign))

	return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}

func getParamsToSign(params map[string]map[string]string, ignoreParamKeys map[string]struct{},
	prefix string, sorted bool) map[string]map[string]string {
	var paymentId, customerId, ip, amount, currency, cardPan, cardYear, cardMonth, cardHolder, cardCvv string

	paramsToSign := map[string]map[string]string{
		"general": {
			"project_id": projectId,
			"payment_id": paymentId,
		},
		"customer": {
			"id":         customerId,
			"ip_address": ip,
		},
		"payment": {
			"payment_amount":   amount,
			"payment_currency": currency,
		},
		"card": {
			"pan":         cardPan,
			"year":        cardYear,
			"month":       cardMonth,
			"card_holder": cardHolder,
			"cvv":         cardCvv,
		},
	}

	for key, value := range params {
		for k, v := range value {
			if _, ok := ignoreParamKeys[k]; ok {
				continue
			}

			paramKey := k
			if prefix != "" {
				paramKey = prefix + ":" + k
			}

			paramsToSign[key][paramKey] = paramKey + ":" + v
		}
	}

	if sorted == true {
		return sortParams(paramsToSign)
	}

	return paramsToSign
}

func sortParams(params map[string]map[string]string) map[string]map[string]string {
	var sortedParams map[string]map[string]string
	keys := make([]string, 0, len(params))

	for kp := range params {
		keys = append(keys, kp)
	}
	sort.Strings(keys)

	for _, ks := range keys {
		sortedParams[ks] = params[ks]
	}

	return sortedParams
}

Отправка запросов и приём ответов на них

Когда все данные собраны и подписаны, можно отправлять запрос к требуемой конечной точке (их перечень представлен в спецификации Gate API). В нашем случае это /v2/payment/card/sale. При приёме запроса (как правило, в течение 100 мс) со стороны платёжной платформы к веб-сервису направляетсяПосле этого стоит ждать от платёжной платформы синхронный HTTP-ответ — с информацией о приёме запроса в обработку или об ошибках, из-за которых запрос не был принят.

В ответах от платформы используются следующие коды:

  • 200 OK — запрос принят в обработку. В таком случае можно ждать дальнейших оповещений о проведении платежа. Работу с такими оповещениями рассматриваем уже в следующем разделе.
  • 400 Bad Request — запрос не принят из-за того, что в нём не указан по крайней мере один из обязательных параметров или указана некорректная подпись. В таком случае можно дополнить запрос недостающими параметрами и обновить подпись (или просто обновить подпись, проверив при этом корректность используемых идентификатора и ключа проекта) и повторить отправку запроса.
  • 403 Forbidden — запрос не принят из-за отказа в доступе к конечной точке. В таком случае можно обратиться к специалистам технической поддержки ecommpay для добавления IP-адреса отправителя в список доверенных адресов.
  • 422 Unprocessable Entity — запрос не принят из-за синтаксической ошибки (например, из-за пропущенной запятой). В таком случае можно исправить ошибку и повторить отправку запроса.
  • 500 Internal Error — запрос не принят из-за сбоя в платёжной платформе. В таком случае можно повторить отправку запроса позднее.

Чтобы получать информацию из ответов от платформы, следует настроить их приём и обработку.

Рис.: Пример кода на PHP для отправки запросов и приёма ответов

$headers        = [];
$headerCallback = function ($curl, $header_line) use (&$headers) {
    if (strpos($header_line, ":") === false) {
        return strlen($header_line);
    }
    list($key, $value) = explode(":", trim($header_line), 2);
    $headers[trim($key)] = trim($value);

    return strlen($header_line);
};

$absUrl = 'https://abs.url';
$curl = curl_init();

$opts[CURLOPT_URL]            = $absUrl;
$opts[CURLOPT_RETURNTRANSFER] = true;
$opts[CURLOPT_CONNECTTIMEOUT] = $this->connectTimeout;
$opts[CURLOPT_TIMEOUT]        = $this->timeout;
$opts[CURLOPT_HEADERFUNCTION] = $headerCallback;
curl_setopt_array($curl, $opts);
$rbody = curl_exec($curl);
$errno = curl_errno($curl);
if ($rbody === false) {
    $errno   = curl_errno($curl);
    $message = curl_error($curl);
    curl_close($curl);
    $this->handleCurlError($absUrl, $errno, $message);
}
$rcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);

echo "http code = ".$rcode."\n";
echo "http response = ".$rbody."\n";

Рис.: Пример кода на Go для отправки запросов и приёма ответов

package main

import (
	"fmt"
	"net/http"
	"time"
)

type conf struct {
	connectTimeout, timeout time.Duration
	returnTransfer          bool
}

func main() {
	absUrl := "https://abs.url"
	var cf = conf{10 * time.Second, 20 * time.Second, true}

	req, err := http.NewRequest(http.MethodGet, absUrl, nil)
	if err != nil {
		fmt.Printf("client: could not create request: %s\n", err)

		return
	}

	client := http.Client{
		Timeout: cf.timeout,
	}

	res, err := client.Do(req)
	if err != nil {
		fmt.Printf("client: error making http request: %s\n", err)
		handleError(err)

		return
	}

	fmt.Printf("http code = %d\n", res.StatusCode)
	fmt.Printf("http response = %s\n", res.Body)
}

Приём оповещений и отправка ответов на них

В рамках проведения отдельного платежа от платформы к веб-сервису могут отправляться предписывающие и уведомительные оповещения.

Предписывающие оповещения требуют отправки каких-либо сведений в платёжную платформу, предоставления определённой информации пользователю, перенаправления его к сторонним сервисам или выполнения иных действий. Это промежуточные оповещения, на которые необходимо своевременно реагировать для корректного проведения платежей.

Уведомительные оповещения позволяют оперативно узнавать о результатах платежей и получать другую значимую информацию. Эту информацию можно использовать для оперативного обновления статусов заказов в веб-сервисе, информирования пользователей и других целей — в соответствии с моделью работы вашего сервиса. Уведомительные оповещения могут быть как промежуточными, с информацией о значимых событиях при проведении платежей, так и итоговыми, с информацией о результатах. И эту информацию можно использовать по мере необходимости.

Для приёма оповещений (как предписывающих, так и уведомительных) следует:

  1. Определить и задать в платёжной платформе адрес, предназначенный для приёма в веб-сервисе оповещений по проекту. Для этого можно открыть раздел Проекты интерфейса Dashboard и использовать инструменты на вкладке Оповещения.
  2. Настроить проверку целостности и разбор оповещений, ожидаемых по указанному адресу, так, чтобы не использовать в работе оповещения с некорректной подписью. Для такой настройки можно использовать приведённый далее пример кода.
  3. Настроить отправку синхронных HTTP-ответов о приёме оповещений: 200 ОК, если подпись корректна, и 400 Bad Request, если подпись некорректна.

Рис.: Пример данных из итогового оповещения

{
    "account": {
        "number": "424242******4243",
        "token": "f365bb1729f9b72fd9c09703a751c979f3becc679f29c3e35c91d18070d15654",
        "type": "visa",
        "card_holder": "SONYA KOVALEVSKY",
        "id": 45678,
        "expiry_month": "05",
        "expiry_year": "2025"
    },
    "customer": {
        "id": "17008",
    },
    "payment": {
        "date": "2023-01-11T13:02:42+0000",
        "id": "Cosmoshop_purchase_2025-01-01_000001",
        "method": "card",
        "status": "success",
        "sum": {
            "amount": 8855,
            "currency": "USD"
        },
        "type": "purchase",
        "description": ""
    },
    "project_id": 42,
    "operation": {
        "id": 969000002636,
        "type": "sale",
        "status": "success",
        "date": "2023-01-11T13:02:42+0000",
        "created_date": "2023-01-11T13:01:45+0000",
        "request_id": "c6eed1eb14c629b4ef20b3b8086d...d04132c34b0088cbc0be4667c",
        "sum_initial": {
            "amount": 8855,
            "currency": "USD"
        },
        "sum_converted": {
            "amount": 8855,
            "currency": "USD"
        },
        "provider": {
            "id": 408,
            "payment_id": "330157196",
            "date": "2023-01-11T13:02:32+0000",
            "auth_code": "",
            "endpoint_id": "612266625"
        },
        "code": "0",
        "message": "Success",
        "eci": "07"
    },
    "signature": "v7KNMpfogAxwRIL9tVftZ1ZZ5D/aZAeb0VMdeR+CqGrNxYyilUwSm...=="
}

Рис.: Пример кода на PHP для приёма оповещений

require_once __DIR__ . 'signature.php';

//@todo net set to merchant project https://api.merchant.com/callback.php

//@todo set projectId
$projectId = null;
//@todo set paymentId 
$paymentId = '';
//@todo set secretKey
$secretKey = '';


$response = json_decode(file_get_contents('php://input'), true);

$rsignature = $response['signature'];

unset($response['signature']);


if ((new Signer($secretKey))->check($response, $rsignature)) {
    header('HTTP/1.1 200 OK');
    header('Status: 200 OK');
    echo "signature is correct\n";  
    
    $projectId = $response['project_id'];
    $paymentId = $response['payment']['id'];

    //@todo save necessary info to the merchant system
} else {
    header('Status: 400 Bad Request');
    header('HTTP/1.1 400 Bad Request');
    echo "signature is invalid\n";
}

Рис.: Пример кода на Go для приёма оповещений

package main

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
)

//@todo net set to merchant project https://api.merchant.com/callback.go

//@todo set project
type project struct{}

type request struct {
	Signature string `json:"signature"`
}

var prj project

//@todo set secretKey
var secretKey string

//@todo set paymentId
var paymentId string

//@todo get request
var r *http.Request

func main() {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Printf("Error reading body: %v", err)
	}

	var req request

	err = json.Unmarshal(body, &req)
	if err != nil {
		log.Printf("Error parsing request: %v", err)
	}

	var w http.ResponseWriter

	if checkSignature(req, secretKey, req.Signature) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("signature is correct\n"))
	} else {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("signature is invalid\n"))
	}
}

func checkSignature(req request, secretKey string, signature interface{}) bool {
}

На этом можно остановиться и выполнить минимальное тестирование либо продолжить разбираться со спецификой реагирования на различные оповещения и перейти к тестированию после.

Реагирование на оповещения

Общая информация

Для реагирования на уведомительные оповещения, если это актуально, можно настроить использование необходимой информации из таких оповещений в соответствии со спецификой веб-сервисапри её получении.

Для реагирования на предписывающие оповещения обязательно следует разобраться с выполнением действий, которые могут быть необходимы для проведения платежа.

Вместе с тем, настроить реагирование на оповещения можно и позже, после реализации основных действий и их тестирования.

К действиям, необходимым при получении предписывающих оповещений, могут относиться:

  1. Сбор дополнительных сведений о платеже и их отправка в платформу.

    В таких случаях в оповещениях передаётся объект clarification_fields со списком параметров, которые необходимо отправить в последующем запросе к платёжной платформе. Как правило, запрашивается информация о пользователе: его имя и фамилия, дата рождения, платёжный адрес и иные подобные сведения.

    Рис.: Пример данных из оповещения со списком запрашиваемых параметров

    "clarification_fields":{
      "avs_data": [
      "avs_post_code",
      "avs_street_address"
      ]
    }

    Сбор требуемых сведений в таких ситуациях можно осуществлять любым удобным способом, в том числе из имеющейся базы данных или через заполнение пользователем соответствующих полей в интерфейсе веб-сервиса. Подробнее о работе с такими оповещениями — в разговоре о дополнениях, после первичной настройки и тестирования. Здесь же можно отметить, что такие ситуации бывают и требуют оперативного решения на стороне веб-сервиса.

  2. Перенаправление пользователя к стороннему сервису.

    В таких случаях оповещения, как правило, содержат объект acs или redirectData с адресом страницы сервиса, к которому необходимо перенаправить пользователя, и дополнительными сведениями. При получении таких оповещений следует перенаправлять пользователей по указанным адресам. Это можно делать с помощью HTML-форм.

    Рис.: Пример данных из оповещения с адресом для перенаправления пользователя

    "acs":{  
      "pa_req":"eJxVUtluwyAQ/BUrH2DA...n8/4htjT7Em", 
      "acs_url":"https://example.com/ACS",
      "md":"eyJfto7jg456ZCI6IiJ9"
    }

    Рис.: Пример скрипта для перенаправления пользователя

    <form id="3dsform" action="https://example.com/ACS" method="post" enctype="application/
    x-www-form-urlencoded">
        <input type="hidden" name="PaReq" value="eJxVUtluwyAQ/BUrH2DA...n8/4htjT7Em" />
        <input type="hidden" name="TermUrl" value="http://example.com/termurl" />
        <input type="hidden" name="MD" value="eyJfto7jg456ZCI6IiJ9" />
    </form>
    <script type="text/javascript">
        setTimeout(function() {
            document.getElementById("3dsform").submit();
        }, 1000);
    </script>

    После перенаправления пользователя согласно полученному оповещению может понадобиться принять какую-либо дополнительную информацию от целевого сервиса (например, от сервиса Access Control Server при аутентификации 3‑D Secure) или ожидать следующего оповещения от платформы — в соответствии со схемой проведения платежа и спецификой используемого метода. Подробнее о работе с такими оповещениями — в разговоре о дополнениях, после первичной настройки и тестирования.

  3. Отображение пользователю какой-либо информации.

    В таких случаях оповещения, как правило, содержат объект display_data с информацией, которую необходимо отобразить пользователю (например, в виде текста или QR-кода), и дополнительными сведениями.

    Рис.: Пример данных из оповещения с информацией для отображения пользователю

    "display_data": [
      {
         "type": "qr_data",
         "title": "QR Code",
         "data": "https://example.com/paygate/union/pay/MDAyMDUxNjk0OTMwNjYyMTQzMTgwOHwwNHw0ODgxMDAwMA=="
      },
      {
         "type": "add_info",
         "title": "QR Code Timeout",
         "data": "600"
      }
    ]

    Такие оповещения могут быть актуальны для платежей с использованием различных альтернативных платёжных методов. Работа с этими методами рассматривается в разделе Платёжные методы, и знакомиться с ней стоит после первичной настройки и тестирования.

Работа с карточными оплатами

При работе с карточными оплатами наиболее актуальными можно считать промежуточные оповещения для выполнения аутентификации 3‑D Secure. В таких оповещениях, как правило, содержатся сведения для перенаправления пользователя напрямую к сервису эмитента (если в оповещении получен объект acs) или к сервису провайдера (если получен объект redirectData). Перенаправить пользователя на требуемую страницу можно с помощью такой же HTML-формы, которая была представлена ранее (и повторена здесь).

Рис.: Пример скрипта для перенаправления пользователя

<form id="3dsform" action="https://example.com/ACS" method="post" enctype="application/
x-www-form-urlencoded">
    <input type="hidden" name="PaReq" value="eJxVUtluwyAQ/BUrH2DA...n8/4htjT7Em" />
    <input type="hidden" name="TermUrl" value="http://example.com/termurl" />
    <input type="hidden" name="MD" value="eyJfto7jg456ZCI6IiJ9" />

</form>
<script type="text/javascript">
    setTimeout(function() {
        document.getElementById("3dsform").submit();
    }, 1000);
</script>

При возвращении пользователя от сервиса эмитента следует принять от этого сервиса информацию о результате аутентификации, отправить эту информацию в HTTP-POST-запросе к платформе и принять синхронный HTTP-ответ. В случае возвращения пользователя от сервиса провайдера такая информация не передаётся, и со стороны веб-сервиса следует ожидать от платформы оповещение с информацией о результате платежа.

Рис.: Пример кода на PHP для реагирования на информацию о результате аутентификации

require_once __DIR__ . 'signature.php';

//@todo set projectId
$projectId = null;
//@todo set paymentId 
$paymentId = '';
//@todo set secretKey
$secretKey = '';


$params = [
    'general'  => [
        'project_id' => $projectId,
        'payment_id' => $paymentId,
    ],
    "pares" => "",
    "md" => ""
];

$absUrl = 'https://api.ecommpay.com/v2/payment/card/3ds_result';

$params['general']['signature']  = (new Signer($secretKey))->sign($params);
$request                         = json_encode($params);

$curl = curl_init();
$opts = [];

$opts[CURLOPT_POST]       = 1;
$opts[CURLOPT_POSTFIELDS] = $request;
$opts[CURLOPT_HTTPHEADER] = ['Content-Type: application/json', 'Content-Length: '.strlen($request)];

$headers        = [];
$headerCallback = function ($curl, $header_line) use (&$headers) {
    if (strpos($header_line, ":") === false) {
        return strlen($header_line);
    }
    list($key, $value) = explode(":", trim($header_line), 2);
    $headers[trim($key)] = trim($value);

    return strlen($header_line);
};

$opts[CURLOPT_URL]            = $absUrl;
$opts[CURLOPT_RETURNTRANSFER] = true;
$opts[CURLOPT_CONNECTTIMEOUT] = $this->connectTimeout;
$opts[CURLOPT_TIMEOUT]        = $this->timeout;
$opts[CURLOPT_HEADERFUNCTION] = $headerCallback;
curl_setopt_array($curl, $opts);
$rbody = curl_exec($curl);
$errno = curl_errno($curl);
if ($rbody === false) {
    $errno   = curl_errno($curl);
    $message = curl_error($curl);
    curl_close($curl);
    $this->handleCurlError($absUrl, $errno, $message);
}
$rcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);

echo "http code = ".$rcode."\n";
echo "http response = ".$rbody."\n";

Рис.: Пример кода на Go для реагирования на информацию о результате аутентификации

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"time"
)

type general struct{ project_id, payment_id, signature string }

type params struct {
	gnrl      general
	pares, md string
}

type conf struct {
	connectTimeout, timeout time.Duration
}

func main() {
	//@todo set project
	project := ""

	//@todo set secretKey
      var secretKey string

       //@todo set paymentId
       var paymentId string

	var cf = conf{10 * time.Second, 20 * time.Second}

	prms := params{
		gnrl:  general{project_id: project, payment_id: paymentId, signature: ""},
		pares: "",
		md:    "",
	}

	signature := sign(prms, secretKey)
	prms.gnrl.signature = signature

	absUrl := "https://api.ecommpay.com/v2/payment/card/3ds_result"
	reqBody, err := json.Marshal(prms)
	bodyReader := bytes.NewReader(reqBody)

	req, err := http.NewRequest(http.MethodPost, absUrl, bodyReader)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Content-Length", string(rune(bodyReader.Len())))

	client := http.Client{
		Timeout: cf.timeout,
	}

	res, err := client.Do(req)
	if err != nil {
		fmt.Printf("client: error making http request: %s\n", err)
		handleError(err)
		os.Exit(1)
	}

	defer res.Body.Close()

	fmt.Printf("http code = %d\n", res.StatusCode)
	fmt.Printf("http response = %s\n", res.Body)
}

func sign(par params, secretKey string) string {
	//@todo implement sign function
}

Более подробно работа с аутентификацией 3‑D Secure рассматривается в отдельной статье, к которой можно обращаться уже после тестирования первичного решения.

Тестирование

Когда работа с подписью, ответами и оповещениями организована, можно приступать к тестированию проведения платежей. При работе с тестовым проектом можно использовать два вида платёжных реквизитов: специальные тестовые, позволяющие тестировать заданные сценарии работы, и произвольные реалистичные, позволяющие дополнительно проверять работу с платежами в разных случаях.

В базовом случае можно использовать следующие тестовые номера платёжных карт (для проведения платежей по заданным кратчайшим сценариям).

Эмулируемый сценарий Конечный статус платежа
Платёж проведён Платёж отклонён
Оплата без аутентификации 3‑D Secure 4000 0000 0000 0077 4111 1111 1111 1111
Оплата с аутентификацией 3‑D Secure 4314 2200 0000 0056 5544 3300 0000 0045

Для более масштабного тестирования можно использовать расширенный набор тестовых данных для карточных и различных альтернативных платежей, а также произвольные данные, включая реквизиты реальных карт, кошельков и других платёжных инструментов. Такой набор позволяет тестировать различные сценарии проведения платежей, например с выполнением аутентификации 3‑D Secure без каких-либо действий пользователя. И это является безопаснымЭто безопасно, поскольку для всех данных в тестовой среде обеспечивается тот же уровень защиты, что и в рабочей, но реальные платежи при этом не проводятся.

По итогам тестирования можно считать реализованными базовые функции по проведению платежей и при желании переходить дальше — к различным дополнениям, которые могут быть полезны уже на первых порах работы с платёжной платформой, и к запуску решения в работу.

Дополнения

Общий контроль проведения платежей

После проведения нескольких тестовых платежей можно разобраться с тем, как контролировать ситуацию по ним. Для общего контроля платежей в платформе предусмотрены пользовательский интерфейс Dashboard и программный интерфейс Data API. С их помощью можно получать различными способами сводную информацию о суммах, статусах и других атрибутах проводимых платежей, но с задержкой вплоть до нескольких минут. Для работы с этими интерфейсами необходимо предварительно получить доступ к интерфейсу Dashboard, настроить права доступа к текстовому проекту и, при необходимости работы с Data API, сформировать соответствующие токен и секретный ключ. После этого можно переходить к работе с информацией о платежах.

В интерфейсе Dashboard для контроля состояния платежей предусмотрены раздел Платежи (с информацией о платежах всех типов) и специализированные разделы с информацией о платежах различных типов, а также карточки с информацией об отдельных платежах. Справочная информация об использовании этих разделов представлена в соответствующем разделе документации.

В Data API предусмотрены различные конечные точки, через запросы к которым можно получать информацию о группах платежей или отдельных платежах. Работа с такими запросами описана в соответствующем разделе документации.

Оперативный контроль состояния отдельных платежей

Чтобы получать оперативную информацию о состоянии отдельных платежей через Gate API, следует использовать HTTP-POST-запросы к конечной точке /v2/payment/status (с указанием идентификаторов проекта и платежа) и принимать синхронные HTTP-ответы со сведениями по этим запросам на них.

Рис.: Пример кода на PHP для формирования и отправки запроса на получение информации о платеже

require_once __DIR__ . 'signature.php';

//@todo set projectId
$projectId = null;
//@todo set paymentId 
$paymentId = '';
//@todo set secretKey
$secretKey = '';


$params = [
    'general'  => [
        'project_id' => $projectId,
        'payment_id' => $paymentId,
    ]
];

$absUrl = 'https://api.ecommpay.com/v2/payment/status';

$params['general']['signature']  = (new Signer($secretKey))->sign($params);
$request                         = json_encode($params);

$curl = curl_init();
$opts = [];

$opts[CURLOPT_POST]       = 1;
$opts[CURLOPT_POSTFIELDS] = $request;
$opts[CURLOPT_HTTPHEADER] = ['Content-Type: application/json', 'Content-Length: '.strlen($request)];

$headers        = [];
$headerCallback = function ($curl, $header_line) use (&$headers) {
    if (strpos($header_line, ":") === false) {
        return strlen($header_line);
    }
    list($key, $value) = explode(":", trim($header_line), 2);
    $headers[trim($key)] = trim($value);

    return strlen($header_line);
};

$opts[CURLOPT_URL]            = $absUrl;
$opts[CURLOPT_RETURNTRANSFER] = true;
$opts[CURLOPT_CONNECTTIMEOUT] = $this->connectTimeout;
$opts[CURLOPT_TIMEOUT]        = $this->timeout;
$opts[CURLOPT_HEADERFUNCTION] = $headerCallback;
curl_setopt_array($curl, $opts);
$rbody = curl_exec($curl);
$errno = curl_errno($curl);
if ($rbody === false) {
    $errno   = curl_errno($curl);
    $message = curl_error($curl);
    curl_close($curl);
    $this->handleCurlError($absUrl, $errno, $message);
}
$rcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);

echo "http code = ".$rcode."\n";
echo "http response = ".$rbody."\n";

$response = json_decode($rbody, true);

$rsignrature = $response['signature'];

unset($response['signature']);

if ((new Signer($secretKey))->check($response, $rsignrature)) {
    echo "signature is correct\n";
} else {
    echo "signature is invalid\n";
}

Рис.: Пример кода на Go для формирования и отправки запроса на получение информации о платеже

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net/http"
	"os"
	"time"
)

//@todo net set to merchant project https://api.merchant.com/callback.go

type general struct{ project_id, payment_id, signature string }

type params struct{ gnrl general }

type response struct {
	Signature string `json:"signature"`
}

func main() {
	//@todo set project
	project := ""
       //@todo set secretKey
       var secretKey string
       //@todo set paymentId
       var paymentId string

	prms := params{
		gnrl: general{project_id: project, payment_id: paymentId, signature: ""},
	}

	signature := sign(prms, secretKey)
	prms.gnrl.signature = signature

	absUrl := "https://api.ecommpay.com/v2/payment/status"
	reqBody, err := json.Marshal(prms)
	bodyReader := bytes.NewReader(reqBody)

	req, err := http.NewRequest(http.MethodPost, absUrl, bodyReader)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Content-Length", string(rune(bodyReader.Len())))

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		fmt.Printf("client: error making http request: %s\n", err)
		handleError(err)
		os.Exit(1)
	}

	defer res.Body.Close()

	fmt.Printf("http code = %d\n", res.StatusCode)
	fmt.Printf("http response = %s\n", res.Body)

	var respBody response
	b, err := io.ReadAll(res.Body)

	err = json.Unmarshal(b, &respBody)
	if err != nil {
		log.Printf("Error parsing response: %v", err)
	}

	rsign := respBody.Signature

	if checkSignature(respBody, secretKey, rsign) {
		fmt.Printf("signature is correct\n")
	} else {
		fmt.Printf("signature is invalid\n")
	}
}

func sign(par params, secretKey string) string {
	//@todo implement sign function
}

func checkSignature(resp response, secretKey string, signature interface{}) bool {
	//@todo implement checkSignature function
}

При возникновении вопросов, касающихся получения информации о платежах через Gate, можно обращаться к соответствующей статье.

Подробнее о работе с такими запросами — в отдельной статье.

Возврат средств

Если по каким-либо из проведённых оплат необходимо выполнить возвраты, для этого можно использовать Dashboard и Gate.

При работе через Dashboard можно открывать карточки целевых оплат и использовать расположенную в них кнопку Возврат, а также применять пакетную отправку запросов через файлы (подробнее).

При работе через Gate следует использовать запросы к соответствующим конечным точкам (с учётом специфики платёжных методов). В случае карточных оплат это запросы к конечной точке /v2/payment/card/refund. Для более глубокого освоения работы с возвратами через Gate можно использовать отдельную статью.

В каждом запросе на возврат по карточной оплате необходимо указывать целевые идентификаторы проекта и платежа, а также описание причины возврата. Вместе с этими обязательными сведениями можно указывать также сумму и код валюты возврата, если возвратить необходимо только часть суммы. В таких случаях код валюты должен соответствовать исходному в платеже, иначе запрос на возврат отклоняется. Для более глубокого освоения работы с возвратами через Gate можно использовать отдельную статью.

Рис.: Пример набора параметров для запроса на возврат

{
  "general": {
    "project_id": 42,
    "payment_id": "Cosmoshop_purchase_2025-01-01_000001",
    "signature": "of8k9xeKJ7KLTZYO56lCv+f1M0Sf/7eg=="
  },
  "payment": {
    "description": "Item return"
  }
}

Рис.: Пример данных из оповещения о возврате

{ 
  "project_id":42,
  "payment":{
    "id":"Cosmoshop_purchase_2025-01-01_000001",
    "type":"purchase", // Тип платежа — разовая оплата
    "status":"refunded", // Статус платежа после полного возврата
    "date":"2023-01-12T15:20:36+0000",
    "method":"card",
    "sum":{ 
      "amount":0, // Актуальная сумма платежа после полного возврата
      "currency":"USD" // Код валюты платежа
    }
  },
  "account":{
    "number":"424242******4243",
    "token":"14c24c8a5384b413f11b2956a82ddaeea609ea49",
    "type":"visa",
    "card_holder":"SONYA KOVALEVSKY",
    "expiry_month":"05",
    "expiry_year":"2025"
  },
  "customer":{
    "id":"17008"
  },
  "operation_description":"Item return", // Описание причины возврата
  "operation":{
    "id":3861,
    "type":"refund", // Тип операции
    "status":"success", // Статус операции
    "date":"2023-01-12T15:21:00+0000",
    "created_date":"2023-01-12T15:20:58+0000",
    "request_id":"67a97cd6b14f1aa0543c81e18cd270b66-aadc6e790206d5-00038611",
    "sum_initial":{ 
      "amount":8855, // Сумма возврата
      "currency":"USD" // Код валюты возврата (в соответствии с валютой платежа)
    },
    "sum_converted":{
      "amount":8855,
      "currency":"USD"
    },
    "code":"0",
    "message":"Success",
    "provider":{
      "id":414,
      "payment_id":"",
      "endpoint_id":414
    }
  },
  "signature":"of8k9xerKSK4XL1QFaDH3p9Mh0CIcjmOwSwKJ7KLTZYO56lCv+f1M0Sf/7eg=="
}

Организация работы с другими типами платежей

Настроив проведение карточных оплат, можно настроить работу и с другими типами платежей и платёжными методами. Чтобы сориентироваться в том, как в платформе проводятся платежи разных типов, и настроить работу с необходимыми типами платежей и платёжными методами, можно использовать модельмодель проведения платежей (с описанием поддерживаемых типов платежей и их статусов) и материалы о методах (с описанием специфики проведения платежей с использованием различных методов).

Использование вспомогательных процедур и дополнительных возможностей

При проведении платежей через платёжную платформу ecommpay могут применяться различные процедуры и возможности. Вспомогательные процедуры требуются в отдельных случаях для решения специфических задач, например по аутентификации пользователей. Когда такие процедуры актуальны, они обязательны для проведения платежей, поэтому важно уметь с ними работать. В свою очередь, дополнительные возможности не блокируют проведение платежей и могут выполняться по желанию мерчанта. Они рассчитаны на улучшение качества предоставляемого сервиса.

После настройки проведения оплат, описанных в этом быстром старте, может быть полезно настроить выполнение процедур и возможностей, актуальных в соответствии со спецификой вашего веб-сервиса и задействуемых платёжных методов. Среди них можно выделить следующие:

Помимо этих процедур и возможностей можно настраивать и другие, описанные в разделах Вспомогательные процедуры и Дополнительные возможности.

Запуск

После реализации базовых функций, тестирования актуальных возможностей и настройки необходимых вам сценариев работы можно переходить к запуску рабочего проекта. Важно, чтобы к этому моменту были решены основные организационные вопросы. В таком случае вопросы технические сводятся к настройке свойств проекта на стороне платёжной платформы и к началу использования идентификатора и ключа рабочего проекта на стороне веб-сервиса.

Также после запуска можно продолжать настраивать работу с различными типами платежей, платёжными методами, процедурами и возможностями — с учётом ваших потребностей — и обращаться с вопросами и обратной связью к нашим специалистам.

Успехов!