[Из песочницы] Ruby On Rails и взаимодействие с REST Qiwi Shop
Имею огромное желание рассказать о том, как просто работать с 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.
Спасибо за внимание, на этом все.