# Быстрый старт {#ru_gate_quickstart} инструкция по оперативной организации приёма платежей через Gate, с использованием примеров исходного кода на PHP и Go **На уровень выше:**[Gate](ru_Gate_Integration_About.md) ## Введение {#ru_gate_quickstart_overview} Эта инструкция — о том, как организовать проведение платежей через программный интерфейс Gate платёжной платформы Ecommpay. С тем, чтобы использовать свой пользовательский интерфейс и обеспечивать максимальное взаимодействие с пользователями на стороне веб-сервиса, а все взаимодействия с платёжной платформой выполнять на программном уровне, „под капотом“. И чтобы при этом можно было применять проверенные быстрые решения — с чёткими инструкциями и примерами кода на популярных языках программирования — PHP и Go. В рамках этой инструкции рассматриваются действия, необходимые для проведения наиболее востребованного вида платежей — оплат в одну стадиюс прямым использованием платёжных карт, — а также действия, необходимые для выполнения возвратов по таким оплатам.Поддержка этих функциональных возможностей, как правило, оптимальна для первичного тестирования и быстрого запуска платёжных проектов в работу. Кроме того, поддержав эти возможности, можно довольно легко поддержать и любые другие, поскольку в основе работы с любыми типами платежейи платёжными методами, поддерживаемыми в платформе, лежат те же принципы и во многом те же процедуры, что и в основе работы с одностадийными карточными оплатами. Если вам не актуальны оплаты через Gate и требуется настроить лишь работу с возвратами или выплатами, можно освоить с помощью этой инструкции работу с подписью, ответами и оповещениями и перейти после этого к реализации соответствующих [дополнений](ru_gate_quickstart.md).Если актуально использовать Gate лишь для контроля состояния отдельных платежей, можно освоить здесь работу с подписью и перейти к статье [Получение информации о состоянии платежа](ru_Gate_payment_status_request.md). И наконец, если актуально что-то принципиально другое, можно сориентироваться в соответствующих вариантах. - Если надо настроить решения с использованием платёжной формы Ecommpay на сайтахи в мобильных приложениях, можно обратиться к разделам [Payment Page](ru_PP_about.md) и [Интеграция с использованием SDK](ru_sdk_overview.md). - Если надо настроить оплаты с вызовом платёжной формы Ecommpay по платёжным ссылкам, можно освоить с помощью этой инструкции работу с подписью, ответами и оповещениями и перейти после этого к соответствующей статье: [Оплата по платёжной ссылке](ru_platform_invoice_model.md). Кроме того, оплаты по ссылкам можно инициировать и вручную, через интерфейс Dashboard, предназначенный для сотрудников мерчантов \([подробнее](ru_dbl_payments.md)\). - Если надо настроить получение через программный интерфейс информации об операциях, опротестованиях и балансах, следует обратиться к разделу о работе с интерфейсом [Data API](ru_dbl_api_protocol.md). - Наконец, если актуально что-то ещё, можно обратиться к другим разделам настоящей документации и к специалистам Ecommpay. **Прим.:** Отдельно можно отметить, что наряду с техническими вопросами для начала проведения платежей через Gate необходимо решить и организационные, в том числе вопросы о соответствии требованиям PCI DSS, если планируется проводить платежи с использованием платёжных карт. Информацию об этом можно найти в статье [Организация взаимодействия](ru_gate_interaction_organisation.md). На этом с вводными всё. Можно переходить к делу. ## Краткая теория {#ru_gate_quickstart_theory} ### Проекты и ключи {#section_rsb_xlg_fvb .section} Работу с платёжной платформой Ecommpayможно сравнить с использованием услуг гостиницы. Так, для заселения в гостиницу обычно необходимо получить номер и ключ от него, а для начала работы с платформой надо получить... *проект и ключ* от него. И как с номерами в гостиницах, в платформе может предоставляться разное количество проектов для одного клиента — под разные цели и задачи — при этом для каждого проекта \(как и для каждого гостиничного номера\) необходим свой ключ. Как правило, для работы достаточно одного тестового и одного рабочего проектов. Это типичный случай, и в рамках быстрого старта мы исходим из него. Если вам по какой-либо причине необходимо больше проектов, это стоит обсудить с курирующим менеджером, но начать всё также можно с одного тестового проекта. Если у вас уже есть идентификатор тестового проекта \(`project_id`\) и секретный ключ для него \(`secret_key`\), можно приготовиться к их использованию и переходить дальше. Если же вы ещё не бронировали тестовый проект, самое время сделать это[через заявку](https://ecommpay.com/sign-up/) на основном сайте компании и вернуться сюда. ### Схема работы {#section_ftf_5jw_v5b .section} Чтобы корректно проводить платежи через Gate, необходимо обеспечить сбор актуальных параметров, формирование и отправку к платёжной платформе соответствующих запросов и приём и обработку ответной информации от платформы. При этом все взаимодействия с пользователями\(для сбора и отображения релевантной информации\) должны обеспечиваться на стороне веб-сервиса с использованием собственных решений, в то время как все остальные процедуры \(для обработки используемой информации\) можно реализовывать на основе представленных далее примеров кода. Если сфокусировать внимание на технических аспектах для веб-сервиса и платёжной платформы, то проведение оплаты можно представить следующим образом. | |В веб‑сервисе|В платёжной платформе| |--|-------------|---------------------| |1|При готовности пользователя оплатить заказ получаем все необходимые данные, фиксируем параметры платежа и подписываем их, после чего формируем запрос на проведение платежа и отправляем его в платёжную платформу|–| |2|Информируем пользователя о том, что платёж проводится|Принимаем запрос и работаем по нему, чтобы провести платёж. Если актуально, отправляем оповещение о необходимых действиях| |3|Если актуально, принимаем оповещение о необходимых действиях, выполняем эти действия \(с участием пользователя или без него\) и отправляем в платёжную платформу запрос на продолжение платежа|–| |4|Информируем пользователя о том, что платёж проводится|Если актуально, принимаем дополнительный запрос и выполняем необходимые действия для продолжения платежа, после чего отправляем к веб-сервису оповещение о результате платежа| |5|Принимаем оповещение с информацией о результате платежа и отображаем пользователю необходимые сведения|–| При работе с платежами других типов выполняемые действия могут отличаться, но в целом схема соответствует приведённой. Реализовывать её на стороне веб-сервиса можно самыми разными способами.Здесь, в рамках быстрого старта, описываются базовые процедуры, которые можно использовать и адаптировать в соответствии со спецификой вашего веб-сервиса. ### Параметры запросов {#section_m14_wkn_w5b .section} Обязательный набор параметров, необходимых для проведения конкретного платежа, может отличаться в зависимости от типа этого платежа, спецификиплатёжного метода и платёжной системы, региональных особенностей и других факторов. Так, в каких-то случаях может требоваться указывать назначение платежа, адрес пользователя или другую информацию, в то время как в других случаях эти сведения могут быть необязательны. Поэтому при настройке работы с разными типами платежейи платёжными методами следует обращаться к соответствующим статьям настоящей документации и к спецификации Gate API. Для проведения карточной оплаты в базовом случае следует определиться с её суммой и валютой, добавить к этим двум параметрам три идентификатора \(проекта, платежа и пользователя\) и данные платёжной карты, а затем сформировать подпись к этим параметрам. **Прим.:** Данные карт могут указываться в явном виде, а также через стандартизированные токены и произвольные идентификаторы ранее сохранённых данных. Если соответствующие токены и идентификаторы ещё не созданы или информация о картах не перенесена в платформу Ecommpay от другого провайдера, то сначала следует выполнить действия, необходимые для их создания. К этому можно приступить после настройки проведения оплат, их первичного тестирования и запуска — то есть после выполнения основных действий, описанных в этой инструкции. Если данные карты указываются в явном виде, то к числу обязательных параметров относятся следующие. |Параметр|Описание| |--------|--------| |`general` — объект, содержащий параметры с основными идентификационными сведениями запроса| |`project_id` integer |Идентификатор проекта. Его вместе с ключом выдаёт 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.В тестовых запросах могут использоваться любые из действующих кодов, а в реальных каждый раз должен использоваться код той валюты, в которой инициируется платёж. Для сверки можно использовать [справочник валют](ru_currency_codes.md). Пример: `USD` | |`customer` — объект, содержащий параметры с основными сведениями о пользователе| |`customer_id` string |Идентификатор пользователя в веб-сервисе.Может быть произвольным и повторяемым в разных запросах, но для каждого реального пользователя должен быть однозначно сопоставляемым с его учётной записью в веб-сервисе и уникальным в рамках проекта.Иначе возможны различные коллизии, в том числе при оценке рисков проведения платежей. Пример: `17008 ` | |`ip_address` string |`ip_address`IP-адрес пользователя.В тестовых запросах можно указывать IP-адрес мерчанта, в рабочих запросах каждый раз должен указываться IP-адрес, с которого пользователь инициирует оплату. Пример: `248.121.176.220` | |`card` — объект, содержащий параметры со сведениями о платёжной карте пользователя| |`pan` string |Номер платёжной карты.В тестовых запросах можно указывать любые реалистичные значения, в том числе специальные, приведённые [далее](ru_gate_quickstart.md); в рабочих запросах должны использоваться реальные данные. Пример: `4242424242424243` | |`year` string |Порядковый номер года, в котором истекает срок действия карты, в формате `ГГГГ`.В тестовых запросах можно указывать произвольные значения, но в корректном формате и так, чтобы срок действия был актуален; в рабочих запросах должны использоваться реальные данные. Пример: `2025` | |`month` string |Порядковый номер месяца, в котором истекает срок действия карты, в виде числа от 1 до 12.В тестовых запросах можно указывать произвольные значения, но в корректном формате и так, чтобы срок действия был актуален; в рабочих запросах должны использоваться реальные данные. Пример: `5` | |`card_holder` string |Имя пользователя, в соответствии с указанным на карте и с учётом применяемых [ограничений](ru_faq_payment_processing.md#fig_hgj_jds_4nb), если этот параметр обязателен для используемого проекта \(исключить его из числа обязательных параметров можно только по согласованию с курирующим менеджером Ecommpay после анализа и оценки рисков\). В тестовых запросах можно указывать произвольные значения; в рабочих запросах должны использоваться реальные данные. Пример: `SONYA KOVALEVSKY` | |`cvv` string |Код проверки подлинности карты, в соответствии с указанным на карте или полученным держателем от эмитента.В тестовых запросах можно указывать произвольные значения из трёх цифр; в рабочих запросах должны использоваться реальные данные. Пример: `345` | В запросе такой набор параметров может выглядеть следующим образом. ```language-json { "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 на стороне веб-сервиса или для отправки уведомления о результате платежа пользователю. Если такие параметры не были переданы, то соответствующие возможности могут не поддерживаться. Для работы с такими случаями может потребоваться настроить дополнительные процедуры. Они частично затронуты далее, а также отдельно разобраны в разделе [Дополнения](ru_gate_quickstart.md), к которому лучше переходить после настройки и тестирования всех базовых процедур. В целом, сбор всех необходимых параметров можно настраивать как удобно, с оглядкой на архитектуру вашего веб-сервиса и иные факторы \(например, с задействованием используемых справочников и баз данных\). Здесь же, определившись с обязательными параметрами, можно приступать к реализации. ## Базовая реализация {#ru_gate_quickstart_basic_implementation} ### Введение {#ru_gate_quickstart_basic_implementation_overview} Реализовывать функции веб-сервиса для проведения платежей можно по-разному, в том числе за счёт создания своих программных решений. В рамках этого быстрого старта мы рассматриваем реализацию с использованием готового кода от Ecommpay для подписывания данных, отправки запросов, приёма ответов на эти запросы и программных оповещений, и, вместе с тем, с использованием ваших решений на стороне веб-сервиса для всех остальных действий, включая получение информации от пользователей и предоставление им информации о проведении платежа. ### Подписывание данных {#ru_gate_quickstart_basic_implementation_signature} Когда для всех необходимых параметров определены их значения\(и только в таком случае\), можно формировать подписьк ним и готовить запрос. ```language-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; } } ``` ``` 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 } ``` ### Отправка запросов и приём ответов на них {#ru_gate_quickstart_basic_implementation_requests} Когда все данные собраны и подписаны, можно отправлять запрос к требуемой конечной точке\(их перечень представлен в спецификации Gate API\). В нашем случае это [/v2/payment/card/sale](https://api-developers.ecommpay.com/api-specification/card-payments/post-v2-payment-card-sale). При приёме запроса \(как правило, в течение 100 мс\) со стороны платёжной платформы к веб-сервису направляется синхронный HTTP-ответ — с информацией о приёме запроса в обработку или об ошибках, из-за которых запрос не был принят. В ответах от платформы используются следующие коды: - `200 OK` — запрос принят в обработку. В таком случае можно ждать дальнейших оповещений о проведении платежа. Работу с такими оповещениями рассматриваем уже в следующем разделе. - `400 Bad Request` — запрос не принят из-за того, что в нём не указан по крайней мере один из обязательных параметров или указана некорректная подпись. В таком случае можно дополнить запрос недостающими параметрами и обновить подпись \(или просто обновить подпись, проверив при этом корректность используемых идентификатора и ключа проекта\) и повторить отправку запроса. - `403 Forbidden` — запрос не принят из-за отказа в доступе к конечной точке. В таком случае можно обратиться к специалистам технической поддержки Ecommpay для добавления IP-адреса отправителя в список доверенных адресов. - `422 Unprocessable Entity` — запрос не принят из-за синтаксической ошибки \(например, из-за пропущенной запятой\). В таком случае можно исправить ошибку и повторить отправку запроса. - `500 Internal Error` — запрос не принят из-за сбоя в платёжной платформе. В таком случае можно повторить отправку запроса позднее. Чтобы получать информацию из ответов от платформы, следует настроить их приём и обработку. ```language-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"; ``` ``` 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) } ``` ### Приём оповещений и отправка ответов на них {#ru_gate_quickstart_basic_implementation_callbacks} В рамках проведения отдельного платежа от платформы к веб-сервису могут отправляться *предписывающие* и *уведомительные* оповещения. *Предписывающие* оповещения требуют отправки каких-либо сведений в платёжную платформу, предоставления определённой информации пользователю, перенаправления его к сторонним сервисам или выполнения иных действий. Это промежуточные оповещения, на которые необходимо своевременно реагировать для корректного проведения платежей. *Уведомительные* оповещения позволяют оперативно узнавать о результатах платежей и получать другую значимую информацию.Эту информацию можно использовать для оперативного обновления статусов заказов в веб-сервисе, информирования пользователей и других целей — в соответствии с моделью работы вашего сервиса. Уведомительные оповещения могут быть как промежуточными, с информацией о значимых событиях при проведении платежей, так и итоговыми, с информацией о результатах. Для приёма оповещений\(как предписывающих, так и уведомительных\) следует: 1. Определить и задать в платёжной платформе адрес, предназначенный для приёма в веб-сервисе оповещений по проекту. Для этого можно открыть раздел **Проекты** интерфейса Dashboard и использовать инструменты на вкладке **Оповещения**. 2. Настроить проверку целостности и разбор оповещений, ожидаемых по указанному адресу, так, чтобы не использовать в работе оповещения с некорректной подписью. Для такой настройки можно использовать приведённый далее пример кода. 3. Настроить отправку синхронных HTTP-ответов о приёме оповещений: `200 ОК`, если подпись корректна, и `400 Bad Request`, если подпись некорректна. ```language-java { "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...==" } ``` ```language-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"; } ``` ``` 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 { } ``` На этом можно остановиться и выполнить минимальное [тестирование](ru_gate_quickstart.md) либо продолжить разбираться со спецификой реагирования на различные оповещенияи перейти к тестированию после. ### Реагирование на оповещения {#ru_gate_quickstart_basic_implementation_callbacks_for_cards} #### Общая информация {#section_ygw_zh2_cxb .section} Для реагирования на *уведомительные* оповещения, если это актуально, можно настроить использование необходимой информации из таких оповещений в соответствии со спецификой веб-сервиса. Для реагирования на *предписывающие* оповещения обязательно следует разобраться с выполнением действий, которые могут быть необходимы для проведения платежа. Вместе с тем, настроить реагирование на оповещения можно и позже, после реализации основных действий и их тестирования. К действиям, необходимым при получении предписывающих оповещений, могут относиться: 1. Сбор дополнительных сведений о платеже и их отправка в платформу. В таких случаях в оповещениях передаётся объект `clarification_fields` со списком параметров, которые необходимо отправить в последующем запросе к платёжной платформе.Как правило, запрашивается информация о пользователе: его имя и фамилия, дата рождения, платёжный адрес и иные подобные сведения. ```language-json "clarification_fields":{ "avs_data": [ "avs_post_code", "avs_street_address" ] } ``` Сбор требуемых сведений в таких ситуациях можно осуществлять любым удобным способом, в том числе из имеющейся базы данных или через заполнение пользователем соответствующих полей в интерфейсе веб-сервиса.Подробнее о работе с такими оповещениями — в разговоре [о дополнениях](ru_gate_quickstart.md), после первичной настройки и тестирования. Здесь же можно отметить, что такие ситуации бывают и требуют оперативного решения на стороне веб-сервиса. 2. Перенаправление пользователя к стороннему сервису. В таких случаях оповещения, как правило, содержат объект `acs`или `redirectData` с адресом страницы сервиса, к которому необходимо перенаправить пользователя, и дополнительными сведениями. При получении таких оповещений следует перенаправлять пользователей по указанным адресам.Это можно делать с помощью HTML-форм. ```language-json "acs":{ "pa_req":"eJxVUtluwyAQ/BUrH2DA...n8/4htjT7Em", "acs_url":"https://example.com/ACS", "md":"eyJfto7jg456ZCI6IiJ9" } ``` ```language-xml
``` После перенаправления пользователя согласно полученному оповещению может понадобиться принять какую-либо дополнительную информацию от целевого сервиса \(например, от сервиса Access Control Server при аутентификации 3‑D Secure\) или ожидать следующего оповещения от платформы — в соответствии со схемой проведения платежа и спецификой используемого метода. Подробнее о работе с такими оповещениями — в разговоре [о дополнениях](ru_gate_quickstart.md), после первичной настройки и тестирования. 3. Отображение пользователю какой-либо информации. В таких случаях оповещения, как правило, содержат объект `display_data` с информацией, которую необходимо отобразить пользователю\(например, в виде текста или QR-кода\), и дополнительными сведениями. ```language-json "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" } ] ``` Такие оповещения могут быть актуальны для платежей с использованием различных альтернативных платёжных методов. Работа с этими методами рассматривается в разделе [Платёжные методы](ru_pm_about.md), и знакомиться с ней стоит после первичной настройки и тестирования. #### Работа с карточными оплатами {#section_c1h_dwt_xxb .section} При работе с карточными оплатами наиболее актуальными можно считать промежуточные оповещения для выполнения аутентификации 3‑D Secure. В таких оповещениях, как правило, содержатся сведения для перенаправления пользователя напрямую к сервису эмитента\(если в оповещении получен объект `acs`\)или к сервису провайдера\(если получен объект `redirectData`\). Перенаправить пользователя на требуемую страницу можно с помощью такой же HTML-формы, которая была представлена ранее \(и повторена здесь\). ```language-xml
``` При возвращении пользователя от сервиса эмитента следует принять от этого сервиса информацию о результате аутентификации, отправить эту информацию в HTTP-POST-запросе к платформе и принять синхронный HTTP-ответ. В случае возвращения пользователя от сервиса провайдера такая информация не передаётся, и со стороны веб-сервиса следует ожидать от платформы оповещение с информацией о результате платежа. ```language-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"; ``` ``` 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 рассматривается [в отдельной статье](ru_gate_payment_3ds.md), к которой можно обращаться уже после тестирования первичного решения. ## Тестирование {#ru_gate_quickstart_testing} Когда работа с подписью, ответами и оповещениями организована, можно приступать к тестированию проведения платежей. При работе с тестовым проектом можно использовать два вида платёжных реквизитов: *специальные* тестовые, позволяющие тестировать заданные сценарии работы, и *произвольные* реалистичные, позволяющие дополнительно проверять работу с платежами в разных случаях. В базовом случае можно использовать следующие тестовые номера платёжных карт\(для проведения платежей по заданным кратчайшим сценариям\). |Эмулируемый сценарий|Конечный статус платежа| |Платёж проведён|Платёж отклонён| |--------------------|:----------------------| |---------------|:--------------| |Оплата без аутентификации 3‑D Secure|`4000 0000 0000 0077`|`4111 1111 1111 1111`| |Оплата с аутентификацией 3‑D Secure|`4314 2200 0000 0056`|`5544 3300 0000 0045`| Для более масштабного тестирования можно использовать расширенный набор тестовых данных для [карточных](ru_test_cards.md)и различных [альтернативных](ru_pm_testing.md) платежей, а также произвольные данные, включая реквизиты реальных карт, кошельков и других платёжных инструментов. Такой набор позволяет тестировать различные сценарии проведения платежей, например с выполнением аутентификации 3‑D Secure без каких-либо действий пользователя. И это является безопасным, поскольку для всех данных в тестовой среде обеспечивается тот же уровень защиты, что и в рабочей, но реальные платежи при этом не проводятся. По итогам тестирования можно считать реализованными базовые функции по проведению платежей и при желании переходить дальше — к различным дополнениям, которые могут быть полезны уже на первых порах работы с платёжной платформой, и к запуску решения в работу. ## Дополнения {#ru_gate_quickstart_additional_aspects} ### Общий контроль проведения платежей {#section_osj_vd4_fvb .section} После проведения нескольких тестовых платежей можно разобраться с тем, как контролировать ситуацию по ним.Для общего контроля платежей в платформе предусмотрены пользовательский интерфейс Dashboard и программный интерфейс Data API. С их помощью можно получать различными способами сводную информацию о суммах, статусах и других атрибутах проводимых платежей, но с задержкой вплоть до нескольких минут. Для работы с этими интерфейсами необходимо предварительно получить [доступ](ru_dbl_overview.md) к интерфейсу Dashboard, настроить права доступа к текстовому проекту и, при необходимости работы с Data API, сформировать соответствующие токен и секретный ключ.После этого можно переходить к работе с информацией о платежах. В интерфейсе Dashboard для контроля состояния платежей предусмотрены раздел **Платежи** \(с информацией о платежах всех типов\) и специализированные разделы с информацией о платежах различных типов, а также карточки с информацией об отдельных платежах. Справочная информация об использовании этих разделов представлена в соответствующем [разделе документации](ru_dbl_payments.md). ![](images/ecommpay/dbl/ru_quickstart_dbl_overview.svg "Реестр платежей") ![](images/ecommpay/dbl/ru_quickstart_dbl_payment_details.svg "Карточка платежа") В Data API предусмотрены различные конечные точки, через запросы к которым можно получать информацию о группах платежей или отдельных платежах. Работа с такими запросами описана в соответствующем [разделе документации](ru_dbl_using_api.md). ### Оперативный контроль состояния отдельных платежей {#section_jbb_lkf_cxb .section} Чтобы получать оперативную информацию о состоянии отдельных платежей через Gate API, следует использовать HTTP-POST-запросы к конечной точке [/v2/payment/status](https://api-developers.ecommpay.com/api-specification/requests-for-information/post-v2-payment-status)\(с указанием идентификаторов проекта и платежа\) и принимать синхронные HTTP-ответысо сведениями по этим запросам. ```language-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"; } ``` ``` 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, можно обращаться [к соответствующей статье](ru_Gate_payment_status_request.md). ### Возврат средств {#section_vzl_nzf_fvb .section} Если по каким-либо из проведённых оплат необходимо выполнить возвраты, для этого можно использовать Dashboard и Gate. При работе через Dashboard можно открывать карточки целевых оплат и использовать расположенную в них кнопку **Возврат**, а также применять пакетную отправку запросов через файлы \([подробнее](ru_dbl_payments.md)\). При работе через Gate следует использовать запросы к соответствующим конечным точкам \(с учётом специфики платёжных методов\). В случае карточных оплат это запросы к конечной точке [/v2/payment/card/refund](https://api-developers.ecommpay.com/api-specification/card-payments/post-v2-payment-card-refund). В каждом запросе на возврат по карточной оплате необходимо указывать целевые идентификаторы проекта и платежа, а также описание причины возврата. Вместе с этими обязательными сведениями можно указывать также сумму и код валюты возврата, если возвратить необходимо только часть суммы. В таких случаях код валюты должен соответствовать исходному в платеже, иначе запрос на возврат отклоняется. Для более глубокого освоения работы с возвратами через Gate можно использовать [отдельную статью](ru_Gate_Refund.md). ```language-json { "general": { "project_id": 42, "payment_id": "Cosmoshop_purchase_2025-01-01_000001", "signature": "of8k9xeKJ7KLTZYO56lCv+f1M0Sf/7eg==" }, "payment": { "description": "Item return" } } ``` ```language-json { "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==" } ``` ### Организация работы с другими типами платежей {#section_g1n_zrw_dwb .section} Настроив проведение карточных оплат, можно настроить работу и с другими типами платежейи платёжными методами.Чтобы сориентироваться в том, как в платформе проводятся платежи разных типов, и настроить работу с необходимыми типами платежей и платёжными методами, можно использовать \(с описанием поддерживаемых типов платежей и их статусов\)и [материалы о методах](ru_pm_about.md)\(с описанием специфики проведения платежей с использованием различных методов\). ### Использование вспомогательных процедур и дополнительных возможностей {#section_hp1_p5x_2wb .section} При проведении платежей через платёжную платформу Ecommpay могут применяться различные *процедуры* и *возможности*. Вспомогательные процедуры требуются в отдельных случаях для решения специфических задач, например по аутентификации пользователей.Когда такие процедуры актуальны, они обязательны для проведения платежей, поэтому важно уметь с ними работать. В свою очередь, дополнительные возможности не блокируют проведение платежей имогут выполняться по желанию мерчанта.Они рассчитаны на улучшение качества предоставляемого сервиса. После настройки проведения оплат, описанных в этом быстром старте, может быть полезно настроить выполнение процедур и возможностей, актуальных в соответствии со спецификой вашего веб-сервисаи задействуемых платёжных методов. Среди них можно выделить следующие: - [Дополнение информации о платеже](ru_Gate_Clarification.md)— чтобы своевременно предоставлять дополнительные данные, которые могут запрашиваться платёжными системами; - [Аутентификация 3‑D Secure](ru_gate_payment_3ds.md)— чтобы обеспечивать аутентификацию пользователей для проведения платежей с использованием платёжных карт; - [Отправка уведомлений пользователям](ru_gate_receipts.md)— чтобы информировать пользователей через электронную почту о проведении платежей и других событиях. Помимо этих процедур и возможностей можно настраивать и другие, описанные в разделах[Вспомогательные процедуры](ru_gate_procedures.md) и[Дополнительные возможности](ru_Gate_Additional_capabilities.md). ## Запуск {#ru_gate_quickstart_launch_project} После реализации базовых функций, тестирования актуальных возможностей и настройки необходимых вам сценариев работы можно переходить к запуску рабочего проекта. Важно, чтобы к этому моменту были решены основные организационные вопросы. В таком случае вопросы технические сводятся к настройке свойств проекта на стороне платёжной платформы и к началу использования идентификатора и ключа рабочего проекта на стороне веб-сервиса. Также после запуска можно продолжать настраивать работу с различными типами платежей, платёжными методами,процедурами и возможностями — с учётом ваших потребностей — и обращаться с вопросами и обратной связью к нашим специалистам. Успехов!