-
-
Save raveman/13f9c8a48b881def8761d700a9cfdd28 to your computer and use it in GitHub Desktop.
Интеграция Яндекс.Кассы с Rails
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment