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


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


Регистрация и настройка


Регистрация очень простая, с настройкой несколько сложнее.


Советую создать 2 магазина, один для разработки и тестирования, а второй – "боевой". Для локальной разработки очень удобно использовать ngrok и указать соответсвующий домен (заранее проверив, что он свободен).


Создание магазина максимально простое:



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



Для тестового магазина удобнее всего будет указать любой свободный домен ngrok. "URL обработчика" – это адрес, куда будет стучаться PayKassa для уведомления о том, что статус заказа изменился. "URL успешной / неуспешной оплаты" – адреса, куда пользователя будут направлять после совершения оплаты. На них можно разместить простую страницу с описанием того, что пользователю требуется сделать после успешной / неуспешной оплаты.


После создания нам понадобится ID магазина:



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


Это всё, что требуется для начала приёма платежей.


Получение URL для платежа


В статье будет использоваться значение true для параметра test во всех запросах. Чтобы перейти в боевой режим достаточно его не указывать.


У PayKassa есть простая обёртка на PHP для работы с API, но для остальных языков написание подобной обёртки тоже не должно вызывать сложностей. А для большего понимания процесса все примеры будут сопровождаться примером запросов с использованием curl.


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



В качестве валюты будем везде использовать Bitcoin (system: 11, currency: "BTC").


Платежное API PayKassa находится по адресу https://paykassa.pro/sci/0.3/index.php (на момент написания статьи) и поддерживает два метода: sci_create_order для получения URL оплаты и sci_confirm_order для проверки статуса платежа.


Пример получения URL для оплаты с использованием curl:


curl https://paykassa.pro/sci/0.3/index.php -d 'func=sci_create_order&amount=0.1&currency=BTC&order_id=1&comment=test&system=4&sci_id=SCI_ID&sci_key=SCI_KEY&domain=DOMAIN&test=false' -H 'Content-type: application/x-www-form-urlencoded'

С использованием библиотеки PHP (ссылка на paykassa_sci.class.php):


<?php
    require_once('paykassa_sci.class.php');

    $paykassa = new PayKassaSCI( 
        $paykassa_shop_id,       // идентификатор магазина
        $paykassa_shop_password  // пароль магазина
    );

    // параметры: сумма, валюта, номер заказа, комментарий, платежная система
    $res = $paykassa->sci_create_order(0.01, "BTC", 1, "Test Payment", 11);

    if ($res['error']) {
        echo $res['message'];   // $res['message'] - текст сообщения об ошибке
        //действия в случае ошибки
    } else {
        //  перенаправление на страницу оплаты
        $redirect_uri = $res['data']['url'];
        header("Refresh: 0; url=$redirect_uri");
    }
?>

Для работы с API в других языках будет удобно написать небольшую обёртку и использовать её:


Для ruby:


# paykassa.rb
require 'net/http'

class Paykassa
  BASE_SCI_URI = URI('https://paykassa.pro/sci/0.3/index.php')

  # вариант для Ruby On Rails свежих версий
  # в secrets.yml необходимо наличие следующих значений:
  # paykassa:
  #   sci_id: SCI_ID
  #   sci_key: SCI_SECRET_KEY
  #   domain: SCI_DOMAIN
  # если у вас не RoR - то можно просто не указывать стандартное значение:
  # def initialize(auth)
  # но в этом случае понадобится передать в auth хеш с ключами sci_id, sci_key и domain
  def initialize(auth = Rails.application.secrets[:paykassa])
    @_auth = auth
  end

  # создание запроса на оплату, в ответе получаем хеш на базе JSON пришеднего с сервера
  def create_order(amount:, currency:, order_id:, comment:, system:)
    make_request(
      func: :sci_create_order,
      amount: amount,
      currency: currency,
      order_id: order_id,
      comment: comment,
      system: system
    )
  end

  # проверка статуса платежа
  def confirm_order(private_hash)
    make_request(func: :sci_confirm_order, private_hash: private_hash)
  end

  private

  def make_request(data)
    res = Net::HTTP.post_form(BASE_SCI_URI, data.merge(@_auth))
    JSON.parse(res.body).deep_symbolize_keys
  end
end

Получение url для оплаты:


    paykassa = Paykassa.new
    # если нет secrets.yml или хотим указать явно данные авторизации:
    # paykassa = Paykassa.new(sci_id: 0, sci_key: '123', domain: 'habrashop.ngrok.io')
    result = paykassa.create_order(
      amount: 0.01,
      currency: 'BTC',
      order_id: 1,
      comment: "Payment №1",
      system: 11
    )

    # если произошла ошибка
    raise StandardError.new(result[:message]) if result[:error]

    url = result[:data][:url]
    # дальше можно перенаправить пользователя по этому url: redirect_to url

Для node.js:


var https = require('https');
var querystring = require('querystring');

function mergeArray(array1,array2) {
  for(item in array1) {
    array2[item] = array1[item];
  }
  return array2;
}

function PayKassaApi(sci_id, sci_key, domain, test) {
  this.sci_id = sci_id;
  this.sci_key = sci_key;
  this.domain = domain;
  this.test = test || false;
};
PayKassaApi.methods = [
  'sci_create_order',
  'sci_confirm_order'
]
PayKassaApi.prototype.sendRequest = function(method, params, callback) {
  if (PayKassaApi.methods.indexOf(method) === -1) {
    throw new Error('wrong method name ' + method)
  };

  if (callback == null) {
    callback = params;
  };

  var data = {
    method: method,
    sci_id: this.sci_id,
    sci_key: this.sci_key,
    domain: this.domain,
    test: this.test
  }
  data = mergeArray(params, data)

  var body = querystring.stringify(data);

  var options = {
    host: 'paykassa.pro',
    port: 443,
    path: '/sci/0.3/index.php',
    method: 'POST',            
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
  };

  var request = https.request(options, function (response) {
    var result = '';
    response.setEncoding('utf8');

    response.on('data', function (chunk) {
      result += chunk;
    });

    // Listener for intializing callback after receiving complete response
    response.on('end', function () {
      try {
        callback(JSON.parse(result));
      } catch (e) {
        console.error(e);
        callback(result);
      }
    });
  });

  request.write(body)
  request.end()
};

for (var i = 0; i < PayKassaApi.methods.length; i++) {
  PayKassaApi.prototype[PayKassaApi.methods[i]] = function (method) {
    return function (params, callback) {
      this.sendRequest(method, params, callback)
    }
  }(PayKassaApi.methods[i])
}

module.exports = PayKassaApi

Использование:


var PayKassa = require("./paykassa")
var paykassa = new Api({ sci_id: 0, sci_key: '123', domain: 'habratest.ngrok.io', test: true })
paykassa.sci_create_order({
  amount: 0.01,
  currency: 'BTC',
  order_id: 1,
  comment: 'test order №1',
  system: 1
}, function (res) {
  if (res.error) {
    // если ошибка - кидаем Exception
    throw new Error(res.message)
  } else {
    // иначе редиректим или передаём на фронт / в темплейт
    console.log(res.data.url)
  }
})

Для python:


import httplib
import urllib
import json

class PayKassa:
    sci_domain = 'paykassa.pro'
    sci_path = '/sci/0.3/index.php'

    def __init__(self, sci_id, sci_key, domain, test):
        self.sci_id = sci_id
        self.sci_key = sci_key
        self.domain = domain
        self.test = test and 'true' or 'false'

    def sci_create_order(self, amount, currency, order_id, comment, system):
        return self.make_request({
            'func': 'sci_create_order',
            'amount': amount,
            'currency': currency,
            'order_id': order_id,
            'comment': comment,
            'system': system
        })

    def sci_confirm_order(self, private_hash):
        return self.make_request({ 'func': 'sci_confirm_order', 'private_hash': private_hash })

    def make_request(self, params):
        fields = {'sci_id': self.sci_id, 'sci_key': self.sci_key, 'domain': self.domain, 'test': self.test}.copy()
        fields.update(params)
        encoded_fields = urllib.urlencode(fields)
        headers = {'Content-Type': 'application/x-www-form-urlencoded'}

        conn = httplib.HTTPSConnection(self.sci_domain)
        conn.request('POST', self.sci_path, encoded_fields, headers)
        response = conn.getresponse()
        return json.loads(response.read())

Использование:


paykassa = PayKassa(0, '123', 'habratest.ngrok.io', False)
result = paykassa.sci_create_order(0.001, 'BTC', 1, 'Order number 1', 11)
if result['error']:
  print(result['message'])
else:
  print(result['data']['url'])

Сам запрос представляет собой обычный multipart form/data POST запрос.


Описание параметров:


  • func — метод API
  • system — ID платежной системы, список соответствия ID и платежных систем см. выше
  • currency — валюта платежа, возможные значения тоже см. выше
  • amount — сумма в валюте платежа
  • order_id — уникальный идентификатор заказа, вы должны сгенерировать его самостоятельно
  • comment — комментарий к заказу, доступен клиенту (надо проверить)
  • sci_id — ID магазина, полученный при создании магазина
  • sci_key — секретный ключ магазина, указанный при создании магазина
  • domain — домен, указанный при создании магазина
  • test — тестовый режим

В ответ вам придёт подобный JSON:


{
  "error": false,
  "message": "Счет успешно выставлен",
  "data": {
    "url": "https://paykassa.pro/sci/redir.php?hash=HASH",
    "method": "GET",
    "params": {
      "hash": "HASH"
    }
  }
}

Из него нам интересен ключ error и data.url. Если error равен false — то можно перенаправлять пользователя по адресу, указанному в data.url.


Если перейти по этому адресу в не тестовом режиме, то можно увидеть страницу оплаты:


Страница оплаты


Проверка оплаты


После совершения платежа PayKassa обратится к вам на сервер по адресу, указанному в поле "URL обработчика" при создании магазина. Это обращение будет содержать только идентификатор заказа, без его статуса. Чтобы получить статус заказа (успешно он обработан или нет) – требуется совершить запрос с указанием полученного идентификатора:


curl https://paykassa.pro/sci/0.3/index.php -d 'func=sci_confirm_order&private_hash=PRIVATE_HASH&sci_id=SCI_ID&sci_key=SCI_KEY&domain=DOMAIN&test=true' -H 'Content-type: application/x-www-form-urlencoded'

Пример для ruby:


    paykassa = Paykassa.new
    private_hash = params[:private_hash] # для RoR / sinatra
    result = paykassa.confirm_order(private_hash)

    # если произошла ошибка
    raise StandardError.new(result[:message]) if result[:error]

    order_id = res[:data][:order_id] # ID ордера
    amount = res[:data][:amount] # зачисленная сумма

Пример Python:


paykassa = PayKassa(0, '123', 'habratest.ngrok.io', False)
result = paykassa.confirm_order(request.POST['private_hash']) # пример для Django
if result['error']:
  print(result['message'])
else:
  print(result['data']['order_id'])
  print(result['data']['amount'])

Ну и пример для node.js:


var PayKassa = require("./paykassa")
var paykassa = new Api({ sci_id: 0, sci_key: '123', domain: 'habratest.ngrok.io', test: true })
paykassa.sci_confirm_order({
    private_hash: req.body.private_hash // пример для express
  }, function (res) {
  if (res.error) {
    // если ошибка - кидаем Exception
    throw new Error(res.message)
  } else {
    // иначе получаем сумму и номер заказа
    console.log(res.data.order_id)
    console.log(res.data.amount)
  }
})

В ответ придёт подобный JSON:


{"error":false,"message":"Платеж успешно подтвержден","data":{"transaction":"XXX","shop_id":"XXX","order_id":"1","amount":"0.01","currency":"BTC","system":"bitcoin","hash":"HASH"}}

Здесь всё стандартно — если error равно false – значит, платеж для заказа data.order_id на сумму data.amount прошел успешно. data.amount содержит фактически полученную сумму платежа. В зависимости от вашей логики обработки заказов и выставления платежей иногда его надо проверять (например, зачисление средств, где пользователь сам указывает сумму), иногда нет (например, если пользователь никак не может повлиять напрямую на выставляемую сумму заказа или мы зачисляем на баланс пользователя сумму без учёта комиссии).


Также, стоит заметить, что примерно в это же время пользователь перенаправляется на URL успешной / неуспешной оплаты.


На этом процесс базовой интеграции можно считать успешно завершенным. Как видите, интеграция агрегатора оказалась гораздо проще, чем реализация приёма платежей самому. Если ваши объёмы меньше $100-$1000 в месяц — то такой способ для вас, скорее всего, окажется более выгодным.

Комментарии (1)


  1. P0rt
    22.03.2018 13:17
    +1

    Опечатка: то такой способ для вам