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.

Спасибо за внимание, на этом все.

ссылка на оригинал статьи http://habrahabr.ru/post/265391/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *