-
-
Save kryzhovnik/5b73c1c0637e47b01eaa to your computer and use it in GitHub Desktop.
# 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 |
%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'} Оплатить
А можете вкратце отбъяснить почему Вы реализуете интеграцию яндекс касс таким способом, а не через "active merchant"? И есть ли у Вас более подробный материал по интеграции, а то в интернете по яндекс кассе и rails практически ничего нету.
добавить к модели
require 'builder'
@kryzhovnik спасибо, добрый человек! сэкономил кучи времени разработки
Хорошо.. А что за модель Order? И как инициализировать платёж, т.е. составить url для отправки пользователя на оплату?