Pull to refresh

Ruby On Rails и взаимодействие с REST Qiwi Shop

Reading time 6 min
Views 7.7K
Имею огромное желание рассказать о том, как просто работать с QIWI Shop, используя Ruby on Rails.

Для чего нужен QIWI Shop? Например, у Вас есть свой онлайн-магазин и Вам необходимо принимать платежи от пользователей. Qiwi достаточно распространен в мире. Он не требует наличия персонального аттеста для вывода средств, как это, например, требуют в WebMoney. Поэтому QIWI достаточно привлекателен для интеграрации в онлайн-магазины.



Плюсы:
— Платежный сервис Qiwi представлен в 8 странах: России, Казахстане, Молдове, Румынии, Беларуси, США, Бразилии, Иордании.
— Тысячи терминалов по всей стране (Россия), что облегчает пополнение кошельков без дополнительных процентов.
— Человеку не надо быть знакомым с интернетом, чтобы за минуту оплатить покупку или пополнить баланс из другого города – достаточно дойти до терминала в ближайшем магазине.
— Отсутствие процентов между переводами с одного кошелька на другой.
— Дополнительный уровень защиты с помощью одноразовых СМС-паролей.
— Бесплатное информирование об операциях с Qiwi кошельком.

Я разрабатываю на Ruby On Rails, поэтому примеры, которые буду приводить, будут на языке Ruby.

Qiwi предлагает несколько способов интеграции:
REST-протокол
HTTP-протокол
Форма выставления счета
SOAP-протокол (устаревший)

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

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

REST-протокол — наиболее полный протокол. Предоставляет всю доступную функциональность для интернет-магазинов. Поддерживает реализацию REST сервера на стороне магазина, поддерживающего автоматизированную логику проведения заказов. Реализацию можно выполнить, например, используя Java, C#, PHP, Ruby и другие.

Рассмотрим REST, который предоставляет множество функций и позволяет комфортно работать с магазином Qiwi.
Функции, которые рассмотрим:
— Создание счета.
— Опрос статуса счета.

Нет смысла рассматривать все методы, т.к. по подобию их легко можно реализовать. Как говорит документация самого Qiwi, сложность реализации REST — высокая, но ничего подобного, все выглядит достаточно просто и лаконично. Рассмотрим пример.

Для начала работы нужно получить свои APP_ID и APP_PASSWORD и SHOP_ID в личном кабинете Qiwi Shop, а для этого нужно пройти регистрацию в Qiwi Shop и дождаться одобрения со стороны Qiwi.



Для авторизации, нужно зашифровать данные магазина в base64 следующим образом:
user_creds = Base64.encode64(Codename::Application::APP_ID + ':' + Codename::Application::APP_PASSWORD)

Codename::Application::APP_ID - глобальная константа из application.rb

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

headers = {
        :accept => :json,
        :authorization => 'Basic ' + user_creds,
        :content_type => 'application/x-www-form-urlencoded; charset=utf-8'
    }

Так же необходим id счета, который является обычной уникальной строкой, по которой можно будет идентифицировать платеж для дальнейших операций (проверить оплату по счету, отметить счет и т.д.). Сформируем:
bill_id = current_user.email.split("@").first + '-' +  SecureRandom.hex(5) 

В моем случае формирование bills_id происходит следующим образом.

Берется первая часть email пользователя и добавляется к ней через дефис уникальная строка. Получается на выходе: 'snowacat-03a765e046'. Формируем ссылку для создания счета:
url = "https://w.qiwi.com/api/v2/prv/" + Codename::Application::SHOP_ID + "/bills/" + bill_id

Время жизни платежа (время в течении которого можно оплатить платеж):
life_time = Time.now.utc + Codename::Application::PAYMENTS_DAYS.day

Важно: время жизни должно быть в формате iso8601.

В нашем случае время платежа неделя:
Codename::Application::PAYMENTS_DAYS.day = 7

Остальные необходимые параметры:
phone_number = '+79181234567'
amount = 50

Формируем тело запроса:
data = {
    user: 'tel:' + phone_number,
    amount: amount,
    ccy: 'RUB',
    comment: 'User email: ' + current_user.email,
    lifetime: life_time.iso8601,
    pay_source: 'qw',
    prv_name: 'Super Mortal Kombat Shop'
}

Выполняем транзакцию с помощью гема Rest Client:
begin
  result = RestClient.put url, data, headers
rescue => e
  payments_logger.info('Creating bill failed! Email: ' + current_user.email + ' Error: ' + e.message )
  flash[:error] = 'Выставить счет не вышло. Ошибка: ' + e.message

  redirect_to action: :pay
  return
end

На этом все. Все основные действия разобраны. Теперь рассмотрим дополнительное и необходимое для понимая полного метода создания счета.

Модель Payment:
class Payment < ActiveRecord::Base
  STATUSES = [DEFAULT = 0, PAID = 1]

  belongs_to :user
  validates :amount, :numericality => { greater_than: 0 }
end

Структура таблицы:
class CreatePayments < ActiveRecord::Migration
  def change
    create_table :payments do |t|
      t.integer :user_id, :null => false
      t.foreign_key :users
      t.float :amount, :default => 0.0, :null => false
      t.string :bill_id, :limit => 256, :null => false
      t.string :phone_number, :limit => 256, :null => false
      t.integer :status, :limit => 1, :default => 0, :null => false

      t.timestamps null: false
    end
  end
end

Полный код метода, в котором отменяется неоплаченный счет, если у пользователя есть таковой.
def create_bill
    require 'rest-client'
    require 'base64'
    require 'securerandom'

    user_creds = Base64.encode64(Codename::Application::APP_ID + ':' + Codename::Application::APP_PASSWORD)
    headers = {
        :accept => :json,
        :authorization => 'Basic ' + user_creds,
        :content_type => 'application/x-www-form-urlencoded; charset=utf-8'
    }

    # Delete old bill, if current user have it
    bill = current_user.payments.where(status: Payment::DEFAULT).first
    if bill
      # Cancel bill
      url = "https://w.qiwi.com/api/v2/prv/" + Codename::Application::SHOP_ID + "/bills/" + bill.bill_id
      data = {
          status: 'rejected'
      }
      begin
        RestClient.patch url, data, headers
        bill.delete
      rescue => e
        payments_logger.info('Cancelling bill failed! Email: ' + current_user.email + ' Error: ' + e.message )
        flash[:error] = 'У вас есть неоплаченный счет. Отменить его не вышло. Причина: ' + e.message

        redirect_to action: :pay
        return
      end
    end

    bill_id = current_user.email.split("@").first + '-' +  SecureRandom.hex(5)

    payment = current_user.payments.new(
       amount: params[:amount],
       bill_id: bill_id,
       phone_number: params[:user_phone]
    )

    # Validate data
    if payment.invalid?
      flash[:error] = 'Невалидные данные'
      redirect_to action: :pay
      return
    end

    url = "https://w.qiwi.com/api/v2/prv/" + Codename::Application::SHOP_ID + "/bills/" + payment.bill_id

    life_time = Time.now.utc + Codename::Application::PAYMENTS_DAYS.day

    data = {
        user: 'tel:' + payment.phone_number,
        amount: payment.amount,
        ccy: 'RUB',
        comment: 'User email: ' + current_user.email,
        lifetime: life_time.iso8601,
        pay_source: 'qw',
        prv_name: ' Super Mortal Kombat Shop'
    }

    # Start transaction
    begin
      result = RestClient.put url, data, headers
    rescue => e
      payments_logger.info('Creating bill failed! Email: ' + current_user.email + ' Error: ' + e.message )
      flash[:error] = 'Выставить счет не вышло. Ошибка: ' + e.message
      payment.delete
      redirect_to action: :pay
      return
    end

    result = JSON.parse(result)

    if result["response"]["result_code"] == 0
      payment.save
      flash[:notice] = 'Счет выставлен'
      redirect_to action: :pay
    else
      payment.delete
      flash[:error] = 'Выставить счет не вышло. Статус ошибки: ' + result["response"]["result_code"]
      redirect_to action: :pay
    end
  end

Пояснения:

Для отмены счета используется сгенерированный ранее bill_id и patch запрос.

После успешного выставления счета, необходимо проверять оплату. Для этого создадим модель payment_grabber.rb с методом self.get_payments, который будет вызываться по планировщику (например, с помощью планировщика rufus-sheduler).
def self.get_payments
	...
	...
end	

Получаем из БД все неоплаченные счета и проверяем их:
# Get all payments with default statuses
bills = Payment.where(status: Payment::DEFAULT)

bills.each do |bill|
url = "https://w.qiwi.com/api/v2/prv/" + Codename::Application::SHOP_ID + "/bills/" + bill.bill_id

begin
	bill_status = RestClient.get url, headers
rescue => e
	grabber_logger.info('Check bill failed! Error: ' + e.message)
	next
end

bill_status = JSON.parse(bill_status)

if bill_status["response"]["bill"]["status"] == 'paid'
	bill.update(status: Payment::PAID)

	user = User.find_by_id(bill.user_id)
	new_amount = user.money_real + bill.amount
	user.update_attribute(:money_real, new_amount)
elsif bill_status["response"]["bill"]["status"] == 'waiting'
	next
elsif bill_status["response"]["bill"]["status"] == 'expired' || bill_status["response"]["bill"]["status"] == 'unpaid' || bill_status["response"]["bill"]["status"] == 'rejected'
	bill.delete
else
	next
end

Код планировщика:
scheduler.every Codename::Application::GRABBER_INTERVAL do
	PaymentGrabber.get_payments
end

Переменные, которые хранятся в application.rb
SHOP_ID = '111111'
APP_ID = '00000000'
APP_PASSWORD = 'XXXXXXXXXXXX'
# Count days for payments
PAYMENTS_DAYS = 7
# Grabber interval in minutes. How othen sheduler will check payments
GRABBER_INTERVAL = '3m'

Эта статья поможет Rails разработчикам интегрировать Qiwi Shop в интернет магазины. На момент написания статьи примеров готового кода на Ruby On Rails в сети не было (не найдено), а в готовых решениях Qiwi — только список CMS.

Спасибо за внимание, на этом все.
Tags:
Hubs:
+13
Comments 11
Comments Comments 11

Articles