Приём Bitcoin платежей с помощью шлюза Mycelium Gear

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

Mycelium Gear следует отраслевым стандартам обработки платежей, поэтому, если вы когда-либо занимались интеграцией любого платёжного шлюза (не обязательно Bitcoin), вы должны быстро понять, как всё работает и что делать. Для тех, кто никогда не работал с обработкой платежей, мы объясним каждую деталь и продемонстрируем примеры кода.

Mycelium Gear построен с использованием straight и straight-server программного обеспечения с открытым исходным кодом. Возможно, вы захотите проверить документацию на straight-server, чтобы лучше понять основные механизмы, но это необязательно. Многое из упомянутых в документации straight-server также упоминаются в текущей документации.

Обзор обработки платежей

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

Зачем вам нужен процессор платежей?

Обработчик платежа используется для обработки платежей, например, для списания с кредитной карты, и перенаправления обратно на сайт или интернет-магазин, когда платеж завершён. Когда пользователь нажимает «Оплатить» на сайте, вы перенаправляете его на специальную страницу оплаты на веб-сайте процессинга платежей, где он может ввести данные своей кредитной карты и совершить оплату. Затем, когда он завершает (или отменяет) платёж, процессор платежей позволяет сайту узнать через обратный вызов (иногда называемый webhook) и передаёт информацию об оплате, отправив http-запрос на сайт. Сайт должен получать эту информацию и обработать её.

Как обычно работают процессоры платежей?

Например, предположим, что у вас есть сайт под названием worldsbestshoes.com, который принимает платежи через VeryPay.com, обработчик платежей по кредитным картам. Пользователь выбирает свои ботинки, добавляет их в «корзину» на сайте и нажимает «покупка». На этом этапе вы перенаправляете его на

https://verypay.com/order/08ccaf5cd48628fb69900d7295cd46de5ac97dc3d798816f550a266f3eec01e1

Там пользователь видит форму, в которую он может ввести информацию о своей кредитной карте. Как только он закончит ввод, он нажимает «Оплатить». Как только он это сделает, VeryPay подключается к API банка выдавшего кредитную карту и пытается списать деньги. Если операция была успешной, шлюз перенаправляет пользователя обратно на сайт (или показывает ему кнопку, которая будет перенаправлять его туда вручную), а также выполняет callback (webhook)

POST https://worldsbestshoes.com/payments/callback

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

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

Как отличается обработка биткойнов?

Описанный процесс почти идентичен платежам биткойнов. Единственное различие заключается в том, что клиенту не нужно предоставлять какую-либо конфиденциальную информацию, такую ​​как номер кредитной карты. Вместо этого на странице оплаты он видит адрес биткойн-кошелька (а также QR-код), а затем может использовать свой кошелёк для отправки денег на этот адрес. Затем обработчик платёжного шлюза обнаруживает, что платёж был сделан, уведомляет продавца и перенаправляет клиента обратно на сайт продавца. Это именно то, что делает Mycelium Gear.

Обработка денег

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

Продавец, с другой стороны, должен быть готов доверять платёжному шлюзу. К счастью, в Bitcoin вы можете разделить хранение денег и обработку платежей. Используя BIP32, Mycelium Gear может генерировать новый адрес из открытого ключа, который вы получаете из своего bitcoin-кошелька (либо Mycelium, либо Electrum) для каждого платёжного счета.

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

Из-за этого лучше всего думать о Mycelium Gear как о платежной системе уведомлений, а не о платежном процессоре. Мы только наблюдаем за блокчейном, мы на самом деле не храним чьи-то деньги.

Терминология

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

Payment processor (процессор обработки платежей) — это специализированное программное обеспечение или услуга, которая интегрируется с сайтос и уведомляет об его об оплате. Для обработки bitcoin-платежей компания Mycelium Gear берёт обменные курсы с различных обменников, генерирует новые адреса биткойнов и отслеживает транзакции на эти адреса. Обратите внимание, что иногда платёжные процессоры также называются платежными шлюзами. Эти термины являются синонимами, однако шлюз в нашей терминологии будет иметь несколько более специфический смысл.

Gateway (шлюз) — это экземпляр, созданный в Mycelium Gear, который обрабатывает все платежи для определённого магазина. Например, у вас, как у продавца, может быть два интернет-магазина. Вы создадите два шлюза в панели администрирования Mycelium Gear для каждого веб-сайта.

Order (заказ) создаётся каждый раз, когда ваш клиент решает заплатить. Заказ представляет собой набор данных, содержащий информацию, такую ​​как сумма, которую нужно заплатить, адрес биткоин-кошелька, на который будет отправлен платёж, и многое другое. Каждый заказ хранится в нашей базе данных и связан с конкретным шлюзом и продавцом. Заказ также имеет статус, который может изменяться. Например, статус «paid» устанавливается, когда соответствующий биткойн-адрес получает правильную сумму.

Callback (обратный вызов) — это HTTP-запрос, который вызывается на стороне платежного процессинга каждый раз, когда заказ изменяет свой статус. Каждый шлюз позволяет вам установить URL-адрес, на который отправляется callback-запрос. Вам необходимо запрограммировать свой сайт, чтобы он мог обрабатывать этот запрос, извлекать необходимую информацию и правильно её использовать.

Создание нового шлюза

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

Заполнение формы

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

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

Confirmations (требуемые подтверждения) обычно должны быть установлены в 0, если вы не ожидаете больших сумм платежей. В биткойне каждая транзакция считается полностью подтверждённой только тогда, когда получит 6 подтверждений (что может занять около часа). Тем не менее, вы можете с уверенностью предположить, что платёж будет обработан и подтверждён через час, как только транзакция появится в сети Bitcoin. Обычно вы не хотите, чтобы ваши клиенты ждали, поэтому действующий отраслевой стандарт должен принимать транзакции с 0 подтверждениями. Таким образом, как только ваш клиент отправит деньги из своего кошелька, мы сможем обнаружить эту транзакцию и перенаправить его обратно на ваш сайт.

BIP32 pubkey является наиболее важным из полей и выглядит аналогично этому (начиная с xpub):

xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU

Он основан на стандарте Bitcoin BIP32 и получен из личного ключа вашего кошелька. Mycelium Gear использует его для создания нового адреса для каждого нового заказа. Не все кошельки поддерживают BIP32, но два из самых популярных — Mycelium и Electrum.

Order expiration period (Срок действия заказа) — это период времени, в течение которого клиент должен оплатить заказ. Если он не заплатит вовремя, заказ считается просроченным. Значение по умолчанию — 900 секунд (15 минут), которое является отраслевым стандартом.

After payment redirect url (URL-адрес перенаправления платежа) используется для возврата клиента на сайт после успешной покупки. Это может быть страница, специфичная для заказа или для учётной записи.

Когда включена опция auto redirect (автоматическое перенаправление), все пользователи, чьи платежи были успешными, автоматически перенаправляются на URL-адрес перенаправления после оплаты.

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

Раздел «Варианты конвертации валют» позволяет вам выбрать, в какой валюте будут отображаться цены вашего заказа, и какой источник цены биткойнов использовать для преобразования этого значения в соответствующее количество биткойнов. Значение по умолчанию — USD, и вы, вероятно, захотите оставить его таким образом, так как обменный курс Bitcoin может существенно колебаться, что затрудняет установление цен в BTC. Поля поставщика обменного курса позволяют вам выбирать, какие источники использовать для текущего обменного курса. Курсы обмена валют проверяются в порядке, установленном здесь, а последующие поставщики проверяют, не был ли предыдущий поставщик недоступен по какой-либо причине.

Блок Additional info (дополнительная информация о продавце) полностью необязательный. Заполнение этих полей позволит обеспечить клиентов более качественным обслуживанием. Эта информация не используется для соответствия — Mycelium Gear не обрабатывает никаких денег (просто следит за транзакциями в блокчейне) и не подлежит AML/KYC — шлюз никому не передаёт эту информацию.

Получение секретного ключа шлюза

После нажатия кнопки «Создать шлюз» под формой, если все поля заполнены верно, вы будете перенаправлены на информационную страницу шлюза и будет представлен секретный ключ. Вы должны записать его, поскольку он больше не появится. Если вы потеряете этот ключ, вам нужно будет вернуться в эту форму (отредактируйте шлюз) и установите флажок «Regenerate secret». Однако это аннулирует ваш предыдущий секрет, и любой из ваших интернет-магазинов, которые его использовали, должен будет срочно обновить его.

Секретный ключ — это, по сути, подпись, которой подписаны все ваши запросы к шлюзу, которые шлюз использует для проверки того, что именно вы отправляете запросы. Мы подробно объясним, как использовать этот секретный ключ в следующем разделе.

Подпись запроса

В большинстве запросов API требуется подпись, которая защищает шлюз от несанкционированного доступа. Подпись представляет собой HTTP-заголовок X-Signature со строкой около 88 символов:

Base64StrictEncode(
  HMAC-SHA512(
    REQUEST_METHOD + REQUEST_URI + SHA512(X-Nonce + REQUEST_BODY),
    GATEWAY_SECRET
  )
)

Где:

REQUEST_METHOD: «GET», «POST» и т. д.
REQUEST_URI: «/full/path/with?arguments&and#fragment«, без имени хоста
REQUEST_BODY: строка запроса с JSON или пустой строкой
X-Nonce: HTTP-заголовок с целым числом, которое должно быть увеличено с каждым запросом (защищает от повторной атаки), например (Time.now.to_f * 1000) .to_i
SHA512: двоичный SHA-2, 512 бит
HMAC-SHA512: двоичный HMAC с SHA512
GATEWAY_SECRET: ключ для HMAC
Base64StrictEncode: кодировка Base64 согласно RFC 4648

Для языка программирования Ruby подписывание уже реализовано в straight-server-kit gem.

Существует небольшая вероятность того, что запрос завершится с ошибкой «X-Nonce is invalid» из-за одновременного подписанного запроса с большим количеством nonce. Это может быть смягчено путем повторения запроса с обновленными значениями nonce и signature.

Некоторые языки, такие как JavaScript, имеют плохую поддержку бинарных строк, поэтому также действительна шестнадцатеричная подпись

HMAC-SHA512(
  REQUEST_METHOD + REQUEST_URI + SHA512(X-Nonce + REQUEST_BODY),
  GATEWAY_SECRET
)

Где:

SHA512: шестнадцатеричный SHA-2, 512 бит
HMAC-SHA512: hex-encoded HMAC с SHA512

Пример 1:

require 'base64'
require 'openssl'
sha512 = OpenSSL::Digest::SHA512.new
secret = '5ioHLiVwxqkS6Hfdev8pNQfhA9xy7dK957RBVYycMhfet23BTuGUPbYxA9TP6x9P'
nonce = 1442214027577 # (Time.now.to_f * 1000).to_i
body = ''
nonce_body_hash = sha512.digest(nonce.to_s + body.to_s)
nonce_body_hash.bytes == [123, 43, 252, 100, 228, 170, 180, 74, 102, 78, 146, 144, 197, 246, 136, 25, 81, 207, 216, 218, 222, 86, 40, 184, 184, 181, 177, 204, 2, 160, 123, 2, 221, 81, 181, 97, 213, 106, 107, 213, 182, 25, 151, 12, 153, 7, 180, 215, 67, 66, 14, 202, 216, 115, 106, 18, 84, 221, 241, 253, 77, 104, 193, 203]
request = "POST/gateways/6930af63a087cad5cd920e12e4729fe4f777681cb5b92cbd9a021376c0f91930/orders?amount=1&keychain_id=1" + nonce_body_hash
raw_signature = OpenSSL::HMAC.digest(sha512, secret, request)
raw_signature.bytes == [166, 197, 147, 167, 160, 132, 102, 44, 80, 195, 253, 1, 47, 61, 213, 12, 204, 129, 177, 11, 243, 86, 156, 85, 166, 69, 180, 246, 80, 208, 21, 100, 104, 32, 236, 166, 179, 212, 8, 203, 113, 84, 43, 17, 176, 184, 147, 25, 117, 212, 236, 177, 165, 253, 146, 131, 240, 101, 232, 186, 46, 61, 35, 20]
signature = Base64.strict_encode64(raw_signature)
signature == "psWTp6CEZixQw/0BLz3VDMyBsQvzVpxVpkW09lDQFWRoIOyms9QIy3FUKxGwuJMZddTssaX9koPwZei6Lj0jFA=="
# $ curl -H "X-Nonce: 1442214027577" -H "X-Signature: psWTp6CEZixQw/0BLz3VDMyBsQvzVpxVpkW09lDQFWRoIOyms9QIy3FUKxGwuJMZddTssaX9koPwZei6Lj0jFA==" -X POST 'https://gateway.gear.mycelium.com/gateways/6930af63a087cad5cd920e12e4729fe4f777681cb5b92cbd9a021376c0f91930/orders?amount=1&keychain_id=1'
# {"status":0,"amount":438481,"address":"mwtoSKLYQiAXtm2h7JV4aZtrixNjzESbYB","tid":null,"id":2562,"payment_id":"27066a1344323db82bb8f20e0fc209e58178a79edb61ca522c22946728f16c66","amount_in_btc":"0.00438481","amount_paid_in_btc":"0.0","keychain_id":1,"last_keychain_id":1}

Пример 2:

require 'openssl'
sha512 = OpenSSL::Digest::SHA512.new
secret = '5ioHLiVwxqkS6Hfdev8pNQfhA9xy7dK957RBVYycMhfet23BTuGUPbYxA9TP6x9P'
nonce = 1442214785601 # (Time.now.to_f * 1000).to_i
body = ''
nonce_body_hash = sha512.hexdigest(nonce.to_s + body.to_s)
nonce_body_hash == "ae1a1076b17a25db88a98c9cc7a563d76ea495326731ae4280a7ba23d49d0f72b3279db3526e6aa478d1d3534d2e493fd85f707270bb616d789aa49041498f8e"
request = "POST/gateways/6930af63a087cad5cd920e12e4729fe4f777681cb5b92cbd9a021376c0f91930/orders?amount=1&keychain_id=1" + nonce_body_hash
signature = OpenSSL::HMAC.hexdigest(sha512, secret, request)
signature == "c08fdd361cf9a39e9fb0f908d4ff1c9799c46eb0721b4ed69de3353b087ae4e6fa321dbe047d004e7e8444a44b455eb511c56a60441c6ebe3a610bd855bbb865"
# $ curl -H "X-Nonce: 1442214785601" -H "X-Signature: c08fdd361cf9a39e9fb0f908d4ff1c9799c46eb0721b4ed69de3353b087ae4e6fa321dbe047d004e7e8444a44b455eb511c56a60441c6ebe3a610bd855bbb865" -X POST 'https://gateway.gear.mycelium.com/gateways/6930af63a087cad5cd920e12e4729fe4f777681cb5b92cbd9a021376c0f91930/orders?amount=1&keychain_id=1'
# {"status":0,"amount":438481,"address":"mwtoSKLYQiAXtm2h7JV4aZtrixNjzESbYB","tid":null,"id":2563,"payment_id":"f352e5542646f98e7035f4d6636efefece08a96db5427ede3cc82e3076e5d4b7","amount_in_btc":"0.00438481","amount_paid_in_btc":"0.0","keychain_id":1,"last_keychain_id":1}

Пример 3:

require 'openssl'
sha512 = OpenSSL::Digest::SHA512.new
secret = '5ioHLiVwxqkS6Hfdev8pNQfhA9xy7dK957RBVYycMhfet23BTuGUPbYxA9TP6x9P'
nonce = 1442215362723 # (Time.now.to_f * 1000).to_i
body = '{"amount":1,"keychain_id":1}'
nonce_body_hash = sha512.hexdigest(nonce.to_s + body.to_s)
nonce_body_hash == "5e587ea40fc9f5a04746aac4f2c90c78fe49cd24d2d208d12732101e0a5c12f00583655925228c25fe68a1197b5b3e478b75a4351bb38d95c18353f3d6bfe569"
request = "POST/gateways/6930af63a087cad5cd920e12e4729fe4f777681cb5b92cbd9a021376c0f91930/orders" + nonce_body_hash
signature = OpenSSL::HMAC.hexdigest(sha512, secret, request)
signature == "4d1e6b02f30aa6ca0c0fafeedea3e785ad9929a7bb8645c2621413abfebf68323791ae6bb76e8374b48db09c4bfdba4c083c5916de2f0f582ac68a32cefe63f1"
# $ curl -H "Content-Type: application/json" -H "X-Nonce: 1442215362723" -H "X-Signature: 4d1e6b02f30aa6ca0c0fafeedea3e785ad9929a7bb8645c2621413abfebf68323791ae6bb76e8374b48db09c4bfdba4c083c5916de2f0f582ac68a32cefe63f1" -X POST -d '{"amount":1,"keychain_id":1}' 'https://gateway.gear.mycelium.com/gateways/6930af63a087cad5cd920e12e4729fe4f777681cb5b92cbd9a021376c0f91930/orders'
# {"status":0,"amount":438481,"address":"mwtoSKLYQiAXtm2h7JV4aZtrixNjzESbYB","tid":null,"id":2564,"payment_id":"35bf5bd6aafcadf336be042582203e94211844c973ef67e83f681a9e2b907dd0","amount_in_btc":"0.00438481","amount_paid_in_btc":"0.0","keychain_id":1,"last_keychain_id":1}

Создание заказов

Создание заказа — это первый шаг к принятию платежа. Как правило, вы хотите создать заказ после того, как ваш клиент нажимает кнопку «покупка» или аналогичная кнопка в вашем интернет-магазине. После создания заказа с использованием нашего RESTFUL API вы можете перенаправить пользователя на страницу оплаты Mycelium Gear, связанную с этим заказом.

Запрос и подпись

Чтобы создать заказ, вы должны отправить подписанный POST-запрос на https://gateway.gear.mycelium.com/gateways/:gateway_id/orders с хотя бы одним параметром — он определяет сумму, подлежащую оплате за этот заказ. Сумма должна быть в валюте, которую вы предварительно установили для шлюза. Если валютой шлюза является BTC, тогда сумма должна быть указана в сатоши. Таким образом, запрос может выглядеть так:

POST /gateways/:api_gateway_id/orders?amount=1

Вы можете получить значение api_gateway_id из информации вашего шлюза в панели администратора.

Keychain_id используется для генерации адреса для следующего заказа. Это может быть любое целое число > 0, но лучше, если это последовательное целое число, поэтому следите за идентификаторами вашего заказа в приложении. С идентификатором keychain запрос будет выглядеть так:

POST /gateways/:api_gateway_id/orders?amount=1&keychain_id=1

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

Вы можете отправить дополнительные данные с транзакцией, которые позже будут возвращены вам без изменений в обратном вызове. Это полезно, если вы хотите определить, какая запись в вашей БД связана с каким заказом, используя что-то другое, кроме order_id. Вы можете создать покупку и отправить свой идентификатор в параметре callback_data:

POST /gateways/:api_gateway_id/orders?amount=1&keychain_id=1&callback_data=purchase_id_123

Позже, когда выполнится запрос обратного вызова, параметр callback_data будет возвращён обратно, и вы сможете найти эту покупку в своей БД.

Ответ

В ответ на вышеуказанный запрос вы получите следующий json от Mycelium Gear:

{
  "status": 0,
  "amount": 1,
  "address": "12REjGNsZfdWj5kWTuMZ2p6WPeyWFWwUT8",
  "transaction_ids": [],
  "id": 1298,
  "payment_id": "5fb72e26b23cef0900779487698893b6f566e9b8386dfb57bfabe30448b7b163",
  "amount_in_btc": "0.00000001",
  "amount_paid_in_btc": "0.00000001",
  "keychain_id": 1,
  "last_keychain_id": 1
}

С помощью этой информации вы можете вручную отобразить платёжный адрес клиенту на веб-сайте (чтобы он не покидал ваш сайт), или вы можете перенаправить его на страницу оплаты Mycelium Gear с помощью payment_id. URL-адрес платёжной страницы для перенаправления будет следующим:

https://gateway.gear.mycelium.com/pay/5fb72e26b23cef0900779487698893b6f566e9b8386dfb57bfabe30448b7b163

Повторное использование адреса и keychain_id

keychain_id используется для получения следующего адреса из вашего BIP32 ключа. Если вы попытаетесь создать заказ с тем же keychain_id, у него будет тот же адрес, что  будет не очень хорошим тоном. Однако это разрешено, и для этого есть веская причина.

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

Если у вас 20 заказов подряд и попытайтесь создать еще один, Gear увидит это и автоматически повторно использует keychain_id (и, следовательно, адрес) 20-го порядка. Он также установит для повторно используемого поля 21-го порядка значение 1. Вы увидите, что оно отмечено как повторно используемое на панели администратора.

Очень важно убедиться, что вы случайно не предоставили keychain_id, который находится слишком далеко от последнего использованного. Например, если last_keychain_id шлюза равен 10, не используйте 35 для следующего заказа, используйте 11. last_keychain_id всегда возвращается с другой информацией при создании или проверке состояния заказа. Убедитесь, что вы всегда отслеживаете last_keychain_id в своем приложении — он обычно возвращается вам в json с другой информацией о заказе при создании или проверке заказов.

Пример кода

Предположим, у вас есть контроллер Rails с действием complete_purchase, которое обрабатывает запросы клиентов, когда они нажимают кнопку «покупка» на сайте. Вот какой код для него может быть:

require 'straight-server-kit'
def complete_purchase
  # Save purchase in own DB
  purchase  = Purchase.create()
  # Perform signed request to Mycelium Gear API
  client    = StraightServerKit::Client.new(gateway_id: api_gateway_id, secret: gateway_secret)
  new_order = StraightServerKit::Order.new(amount: purchase.amount, callback_data: purchase.id)
  order     = client.orders.create(new_order)
  # Save order reference
  purchase.update(order_id: order.id)
  # Redirect customer to the payment page
  redirect_to client.pay_url(order)
end

Получение статуса заказа и изменение обратного вызова

Всякий раз, когда изменяется статус заказа, Mycelium Gear выполняет HTTP-запрос GET на url, указанный в поле обратного вызова шлюза (если вы его заполнили) или в параметре callback_url заказа. Таким образом, ваш сайт знает, что платёж был либо успешным, либо по какой-то причине произошёл сбой. Этот HTTP-запрос также иногда называют webhook, хотя мы предпочитаем не использовать этот термин.

Важная информация передаётся в этом HTTP-запросе как параметры URL. Вот как может выглядеть типичный обратный вызов:

GET https://worldsbestshoes.com/payments/callback?order_id=1&amount=1&amount_in_btc=0.00000001&amount_paid_in_btc=0.00000001&status=2&address=1NZov2nm6gRCGW6r4q1qHtxXurrWNpPr1q&transaction_ids=["tid1"]&keychain_id=1&last_keychain_id=1&after_payment_redirect_to=http://example.com/payments/success&auto_redirect=true&callback_data=some+random+data

Кроме того, запрос будет иметь специальный заголовок:

X-Signature: UeXPK9RlYFFLdYpWeGBpSd4OWslJR076VBQU4prJlzMpe3f2KL4eUVfpiZ+Z9/c71tqYZgYWeIN78NE1/Snmyw==

В следующем списке подробно описываются параметры GET-запроса обратного вызова:

  • order_id (номер заказа) — внутренний идентификатор заказа. С помощью него вы можете найти соответствующую запись о покупке в своей базе данных. Он также используется для подписи.
  • amount (количество) — это сумма в валюте шлюза, которая должна была быть оплачена
  • amount_in_btc — это сумма в btc, которая должна была быть оплачена
  • amount_paid_in_btc — это сумма в btc, которая была оплачена
  • status — возвращает числовое значение, которое может быть любым из следующих:
    1 — неподтверждённый; транзакция была получена, но пока ещё недостаточно подтверждений
    2 — оплачен полностью
    3 — недоплаченный; недостаточно денег
    4 — переплата; получено слишком много
    5 — истёк; клиент не заплатил вовремя
    6 — отменён; клиент отменил заказ
  • address (адрес) — биткойн-адрес, по которому должна быть сделана транзакция
  • transaction_ids — если статус равен 1,2,3 или 4, поле будет содержать JSON-массив с идентификаторами транзакций биткойна
  • callback_data — содержит любые дополнительные данные, которые были отправлены вместе с транзакцией при создании заказа. Например, если вы отправили «hello world» в качестве данных в запросе, который создал этот заказ, вы также получите ту же строку с обратным вызовом — как значение для ключа callback_data в возвращаемом json.
  • Заголовок X-Signature — это самая важная информация в отношении безопасности. Имейте в виду, что любой может послать запрос на ваш обработчик обратных вызовов и обмануть ваш сайт, сообщая, что определённый заказ был оплачен. Чтобы избежать этого, Mycelium Gear использует подписи для запроса обратного вызова, так что вы можете проверить, что он пришёл из Mycelium Gear, а не от кого-то другого.

Подпись обратного вызова

Она генерируется так же, как подпись для запросов к API, за исключением того, что используются пустые Nonce и Body.

Base64StrictEncode(
  HMAC-SHA512(
    REQUEST_METHOD + REQUEST_URI,
    GATEWAY_SECRET
  )
)

В Ruby подпись обратного вызова может быть проверена с использованием straight-server-kit gem.

request_uri = URI(env['REQUEST_URI']).request_uri rescue env['REQUEST_URI']
  if StraightServerKit.valid_callback?(signature:   headers['X-Signature'],
                                       request_uri: request_uri,
                                       secret:      gateway_secret)
    # update order
  else
    # log incident and return 200
  end
data = {
  signature: 'UeXPK9RlYFFLdYpWeGBpSd4OWslJR076VBQU4prJlzMpe3f2KL4eUVfpiZ+Z9/c71tqYZgYWeIN78NE1/Snmyw==',
  request_uri: '/payments/callback?order_id=1&amount=1&amount_in_btc=0.00000001&amount_paid_in_btc=0.00000001&status=2&address=1NZov2nm6gRCGW6r4q1qHtxXurrWNpPr1q&transaction_ids=["tid1"]&keychain_id=1&last_keychain_id=1&after_payment_redirect_to=http://example.com/payments/success&auto_redirect=true&callback_data=some+random+data',
  secret: 'gateway.secret',
}
StraightServerKit.valid_callback?(**data) == true

Детали проверки подписи.

require 'openssl'
require 'base64'
sha512 = OpenSSL::Digest::SHA512.new
nonce = nil
body = nil
method = 'GET'
request_uri = '/payments/callback?order_id=1&amount=1&amount_in_btc=0.00000001&amount_paid_in_btc=0.00000001&status=2&address=1NZov2nm6gRCGW6r4q1qHtxXurrWNpPr1q&transaction_ids=["tid1"]&keychain_id=1&last_keychain_id=1&after_payment_redirect_to=http://example.com/payments/success&auto_redirect=true&callback_data=some+random+data'
secret = 'gateway.secret'
constant_digest = sha512.digest("#{nonce}#{body}")
constant_digest == sha512.digest('')
constant_digest.bytes == [207, 131, 225, 53, 126, 239, 184, 189, 241, 84, 40, 80, 214, 109, 128, 7, 214, 32, 228, 5, 11, 87, 21, 220, 131, 244, 169, 33, 211, 108, 233, 206, 71, 208, 209, 60, 93, 133, 242, 176, 255, 131, 24, 210, 135, 126, 236, 47, 99, 185, 49, 189, 71, 65, 122, 129, 165, 56, 50, 122, 249, 39, 218, 62]
request = "#{method}#{request_uri}#{constant_digest}"
raw_signature = OpenSSL::HMAC.digest(sha512, secret, request)
raw_signature.bytes == [81, 229, 207, 43, 212, 101, 96, 81, 75, 117, 138, 86, 120, 96, 105, 73, 222, 14, 90, 201, 73, 71, 78, 250, 84, 20, 20, 226, 154, 201, 151, 51, 41, 123, 119, 246, 40, 190, 30, 81, 87, 233, 137, 159, 153, 247, 247, 59, 214, 218, 152, 102, 6, 22, 120, 131, 123, 240, 209, 53, 253, 41, 230, 203]
signature = Base64.strict_encode64(raw_signature)
signature == 'UeXPK9RlYFFLdYpWeGBpSd4OWslJR076VBQU4prJlzMpe3f2KL4eUVfpiZ+Z9/c71tqYZgYWeIN78NE1/Snmyw=='

Версия JavaScript с помощью jsSHA.

var nonce = '';
var body = '';
var method = 'GET';
var request_uri = '/payments/callback?order_id=1&amount=1&amount_in_btc=0.00000001&amount_paid_in_btc=0.00000001&status=2&address=1NZov2nm6gRCGW6r4q1qHtxXurrWNpPr1q&transaction_ids=["tid1"]&keychain_id=1&last_keychain_id=1&after_payment_redirect_to=http://example.com/payments/success&auto_redirect=true&callback_data=some+random+data';
var secret = 'gateway.secret';
var sha512 = new jsSHA("SHA-512", "TEXT");
sha512.update(nonce + body);
var constant_digest = sha512.getHash("ARRAYBUFFER");
var request = method + request_uri;
var sha512 = new jsSHA("SHA-512", "ARRAYBUFFER");
sha512.setHMACKey(secret, "TEXT");
sha512.update(new TextEncoder("UTF-8").encode(request));
sha512.update(constant_digest);
var signature = sha512.getHMAC("B64");
alert(signature);

Проверка состояния заказа вручную

Хотя уведомление через обратный вызов является стандартным способом информирования веб-сайта о том, что статус заказа изменился, иногда этого может быть недостаточно. Например, ваш сайт может быть недоступен и, возможно, не сможет обработать обратный вызов в момент его выполнения. Несмотря на то, что Mycelium Gear будет пытаться отправлять новые запросы обратного вызова, чтобы попытаться связаться с вашим сайтом, он будет продолжать делать это в течение часа.

Таким образом, важно иметь ещё один надёжный метод проверки статуса заказов. Чтобы проверить текущий статус заказа, выполните следующий подписанный запрос GET:

GET https://gateway.gear.mycelium.com/gateways/:api_gateway_id/orders/:payment_id

где: api_gateway_id — это идентификатор вашего шлюза, который вы можете найти на своей информационной странице; payment_id — это то, как вы определяете свой заказ (он был возвращен вам вначале, когда вы создали заказ). Ответ будет возвращать json, аналогичный тому, что обратный вызов мог передать вам через params:

{
  "status": 2,
  "amount": 7894000,
  "address": "1NZov2nm6gRCGW6r4q1qHtxXurrWNpPr1q",
  "transaction_ids": ["f0f9205e41bf1b79cb7634912e86bb840cedf8b1d108bd2faae1651ca79a5838"],
  "id": 1,
  "payment_id": "y78033435ea02f024f9abdfd04adabe314a322a0d353c33beb3acb7d97f1bdeb",
  "amount_in_btc": "0.07894",
  "amount_paid_in_btc": "0.07894",
  "keychain_id": 3,
  "last_keychain_id": 3
}

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

Websocket заказы

Другой способ отслеживания статуса заказа — постоянно подключаться к соответствующему веб-узлу. Фактически, это то, что делает страница оплаты, на которую перенаправляется ваш клиент. Тем не менее, вы можете решить, что не хотите использовать страницу оплаты Mycelium Gear. Возможно, вы хотели бы представить своему клиенту свою собственную настроенную страницу оплаты, которая будет находиться на вашем собственном сайте. Даже если вы используете стандартную страницу оплаты, ваше программное обеспечение может по-прежнему хотеть подключаться к веб-расписанию заказа, а не ждать запроса обратного вызова.

Чтобы подключиться к websocket, вы должны использовать следующий URL:

https://gateway.gear.mycelium.com/gateways/:api_gateway_id/orders/1/websocket

где 1 — идентификатор заказа. Когда статус заказа изменяется, websocket возвращает данные заказа в json, точно такие же данные, которые будут возвращены в качестве ответа на обычный статус проверки HTTP-запроса вручную.

Аннулирование заказа

Если пользователь отменяет свою покупку, лучше также отменить заказ в Mycelium Gear. Заказ можно отменить, отправив подписанный запрос:

POST /gateways/:api_gateway_id/orders/(:order_id or :payment_id)/cancel
Вы можете отправить тот же запрос, используя straight-server-kit:
client = StraightServerKit::Client.new(gateway_id: api_gateway_id, secret: gateway_secret)
order  = client.orders.cancel(id: purchase.order_id)

Получение значения keychain_id для шлюза

Вы можете получить последний идентификатор ключевого слова для определённого шлюза со следующим запросом:

GET https://gateway.gear.mycelium.com/gateways/:gateway_id/last_keychain_id

Тогда вы получите что-то вроде этого:

{"gateway_id": 1, "last_keychain_id": 10}