Skip to content

Instantly share code, notes, and snippets.

@kryzhovnik
Created May 22, 2015 15:34
Show Gist options
  • Star 27 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save kryzhovnik/5b73c1c0637e47b01eaa to your computer and use it in GitHub Desktop.
Save kryzhovnik/5b73c1c0637e47b01eaa to your computer and use it in GitHub Desktop.
Интеграция Яндекс.Кассы с Rails
# config/routes.rb
YandexKassaIntegration::Application.routes.draw do
# ...
scope '/yandex_kassa' do
controller 'yandex_kassa', constraints: { subdomain: 'ssl' } do
post :check
post :aviso
get :success
get :fail
post :fail # исключение: при неуспехе оплаты из кошелька Яндекс.Денег приходит запрос методом POST
end
end
end
# app/models/yandex_kassa.rb
module YandexKassa
PARAMS_MAP = {
requestDatetime: :request_datetime, # xs:dateTime Момент формирования запроса в ИС Оператора.
action: :action, # xs:normalizedString, до 16 символов Тип запроса. Значение: «checkOrder» (без кавычек).
md5: :md5, # xs:normalizedString, ровно 32 шестнадцатеричных символа, в верхнем регистре MD5-хэш параметров платежной формы, правила формирования описаны в разделе 4.4 «Правила обработки HTTP-уведомлений Контрагентом».
shopId: :shop_id, # xs:long Идентификатор Контрагента, присваиваемый Оператором.
shopArticleId: :shop_article_id, # xs:long Идентификатор товара, присваиваемый Оператором.
invoiceId: :invoice_id, # xs:long Уникальный номер транзакции в ИС Оператора.
orderNumber: :order_id, # xs:normalizedString, до 64 символов Номер заказа в ИС Контрагента. Передается, только если был указан в платежной форме.
customerNumber: :customer_number, # xs:normalizedString, до 64 символов Идентификатор плательщика (присланный в платежной форме) на стороне Контрагента: номер договора, мобильного телефона и т.п.
orderCreatedDatetime: :order_created_datetime, # xs:dateTime Момент регистрации заказа в ИС Оператора.
orderSumAmount: :order_sum_amount, # CurrencyAmount Стоимость заказа. Может отличаться от суммы платежа, если пользователь платил в валюте, которая отличается от указанной в платежной форме. В этом случае Оператор берет на себя все конвертации.
orderSumCurrencyPaycash: :order_sum_currency_paycash, # CurrencyCode Код валюты для суммы заказа.
orderSumBankPaycash: :order_sum_bank_paycash, # CurrencyBank Код процессингового центра Оператора для суммы заказа.
shopSumAmount: :shop_sum_amount, # CurrencyAmount Сумма к выплате Контрагенту на р/с (стоимость заказа минус комиссия Оператора).
shopSumCurrencyPaycash: :shopSumCurrencyPaycash, # CurrencyCode Код валюты для shopSumAmount.
shopSumBankPaycash: :shop_sum_bank_paycash, # CurrencyBank Код процессингового центра Оператора для shopSumAmount.
paymentPayerCode: :payment_payer_code, # YMAccount Номер счета в ИС Оператора, с которого производится оплата.
paymentType: :payment_type, # xs:normalizedString Способ оплаты заказа. Список значений приведен в таблице 6.6.1.
}
SIGNATURE_PARAMS = [:order_sum_amount,
:order_sum_currency_paycash, :order_sum_bank_paycash,
:shop_id, :invoice_id, :customer_number
]
class Action
class_attribute :action_name, :shop_id, :password
self.shop_id = Rails.application.secrets.yandex_kassa['shop_id']
self.password = Rails.application.secrets.yandex_kassa['shop_password']
attr_reader :params
def initialize(controller_params)
@params = map_params(controller_params)
end
def valid_signature?
values = [action_name] + SIGNATURE_PARAMS.map { |name| params[name] } + [password]
generate_signature(values) == params[:md5]
end
def order
@order ||= Order.find(params[:order_id])
end
def response
raise NotImplementedError
end
private
def map_params(params)
hashable_array = PARAMS_MAP.map do |param, mapped_param|
[mapped_param, params[param]]
end
HashWithIndifferentAccess[hashable_array]
end
def generate_signature(*params)
Digest::MD5.hexdigest(params.join(';')).upcase
end
end
class CheckOrder < Action
self.action_name = 'checkOrder'
def response
xml = Builder::XmlMarkup.new
xml.instruct! :xml, version: '1.0', encoding: 'UTF-8'
xml.checkOrderResponse(performedDatetime: Time.current.iso8601,
code: code,
invoiceId: params[:invoice_id],
shopId: shop_id
)
xml.target!
end
private
def code
if valid_signature?
valid_params? ? '0' : '100'
else
'1'
end
end
def valid_params?
if order
order.amount == params[:order_sum_amount].to_i
else
false
end
end
end
class PaymentAviso < Action
self.action_name = 'paymentAviso'
def response
xml = Builder::XmlMarkup.new
xml.instruct! :xml, version: '1.0', encoding: 'UTF-8'
xml.paymentAvisoResponse(performedDatetime: Time.current.iso8601,
code: code,
invoiceId: params[:invoice_id],
shopId: shop_id
)
xml.target!
end
def payment_type
params[:payment_type]
end
private
def code
valid_signature? ? '0' : '1'
end
end
end
# app/controllers/yandex_kassa_controller.rb
class YandexKassaController < ActionController::Base
before_filter :find_order
def check
check_order = YandexKassa::CheckOrder.new(params)
render text: check_order.response
end
def aviso
aviso = YandexKassa::PaymentAviso.new(params)
if aviso.valid_signature?
# Заказ оплачен, платеж поступил на счет Яндекс.Кассы.
# Здесь нужно поместить код исполнения заказа
end
render text: aviso.response
end
def success
# Платеж на сайте Яндекс.Кассы успешно завершен, клиент вернулся на ваш
# сайт по ссылке "Вернуться в магазин".
# В зависимости от выбранного способа оплаты, к этому моменту заказ
# может быть оплачен, а может и нет. Подтверждение оплаты приходит
# в метод `aviso`
redirect_to root_url, notice: I18n.t('messages.payment_completed')
end
def fail
# Платеж на сайте Яндекс.Кассы завершился ошибкой оплаты
redirect_to root_url, notice: I18n.t('messages.payment_failed')
end
private
def find_order
@order = Order.find(params[:orderNumber])
end
end
@voodee
Copy link

voodee commented Sep 17, 2015

Хорошо.. А что за модель Order? И как инициализировать платёж, т.е. составить url для отправки пользователя на оплату?

@yuriineb
Copy link

yuriineb commented Oct 2, 2015

%form{:action => 'https://demomoney.yandex.ru/eshop.xml', :method => 'post', class: 'form-horizontal formpay'}
%input{:name => 'shopId', :type => 'hidden', :value => '101951'}
%input{:name => 'scid', :type => 'hidden', :value => '527643'}
%input{:name => 'CustomerNumber', :size => '64', :type => 'hidden', :value => @active_user_company.id}
.form-group
%input.form-control{placeholder: 'Введите сумму', type: 'text', name: 'sum'}/
.form-group
.row
.col-md-6
.radio
%label
%input#optionsRadios1{:checked => 'true', :name => 'paymentType', :type => 'radio', :value => 'AC'}
%span.titlepay Банковские карты
.payment-big-icons-bankcards
.col-md-6
.radio
%label
%input#optionsRadios1{:checked => '', :name => 'paymentType', :type => 'radio', :value => 'WM'}
%span.titlepay WebMoney (WMR)
.payment-big-icons-wmr
.col-md-6
.radio
%label
%input#optionsRadios1{:checked => '', :name => 'paymentType', :type => 'radio', :value => 'PC'}
%span.titlepay Яндекс.Деньги
.payment-big-icons-yad
.col-md-6
.radio
%label
%input#optionsRadios1{:checked => '', :name => 'paymentType', :type => 'radio', :value => 'AB'}
%span.titlepay Альфа-Клик
.payment-big-icons-alpha
.col-md-6
.radio
%label
%input#optionsRadios1{:checked => '', :name => 'paymentType', :type => 'radio', :value => 'SB'}
%span.titlepay Сбербанк Онлайн
.payment-big-icons-sber
.form-group
.col-md-12.text-right
%button.btn.btn-default{'data-dismiss' => 'modal', :type => 'button'} Отмена
%button.btn.btn-success{:type => 'submit'} Оплатить

@bzvyagintsev
Copy link

А можете вкратце отбъяснить почему Вы реализуете интеграцию яндекс касс таким способом, а не через "active merchant"? И есть ли у Вас более подробный материал по интеграции, а то в интернете по яндекс кассе и rails практически ничего нету.

@mibamur
Copy link

mibamur commented Sep 3, 2016

добавить к модели
require 'builder'

@Timrael
Copy link

Timrael commented May 18, 2018

@kryzhovnik спасибо, добрый человек! сэкономил кучи времени разработки

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment