Last active
October 5, 2019 03:34
-
-
Save c80609a/af4e47e163995eebd3e05ba9e62b5b1b to your computer and use it in GitHub Desktop.
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
class YandexKassa | |
VAT_CODE_1 = 1 #Без НДС | |
VAT_CODE_2 = 2 #НДС по ставке 0% | |
VAT_CODE_3 = 3 #НДС по ставке 10% | |
VAT_CODE_4 = 4 #НДС чека по ставке 18% | |
VAT_CODE_5 = 5 #НДС чека по расчетной ставке 10/110 | |
VAT_CODE_6 = 6 #НДС чека по расчетной ставке 18/118 | |
class Error < StandardError | |
def initialize(message) | |
Rails.logger.error "\e[31m %s: %s \e[0m\n" % [self.class.name, message] | |
super message | |
end | |
end | |
class InvalidRequest < Error; end | |
class NotSupported < Error; end | |
class InvalidCredentials < Error; end | |
class Forbidden < Error; end | |
class NotFound < Error; end | |
class TooManyRequests < Error; end | |
class InternalServerError < Error; end | |
PAYMENT_PENDING = 'pending'.freeze | |
PAYMENT_SUCCEED = 'succeeded'.freeze | |
PAYMENT_CANCELED = 'canceled'.freeze | |
PAYMENT_WAITING_FOR_CAPTURE = 'waiting_for_capture'.freeze | |
def initialize(props) | |
@shop_key, @secret_key, @base_url = | |
props.symbolize_keys.values_at(:shop_key, :secret_key, :base_url) | |
@amount = nil | |
@currency = nil | |
@return_url = nil | |
@uuid = nil | |
@payload = nil | |
end | |
## Создание платежа на Яндекс.Кассе. | |
# | |
# @return [Hash] | |
# | |
# Пример ответа: | |
# | |
# { | |
# "id": "21740069-000f-50be-b000-0486ffbf45b0", | |
# "status": "pending", | |
# "paid": false, | |
# "amount": { | |
# "value": "2.00", | |
# "currency": "RUB" | |
# }, | |
# "confirmation": { | |
# "type": "redirect", | |
# "confirmation_url": "https://money.yandex.ru/api-pages/v2/payment-confirm/epl?orderId=21740069-000f-50be-b000-0486ffbf45b0" | |
# }, | |
# "created_at": "2017-10-14T10:53:29.072Z", | |
# "metadata": {}, | |
# } | |
def payment_create(amount:, return_url:, currency:'RUB', uuid:, receipt: nil) | |
@amount = amount | |
@currency = currency | |
@return_url = return_url | |
@uuid = uuid | |
_make_create_payload(receipt) | |
_send_request | |
end | |
## Вернёт информацию о платеже. | |
# | |
# Пример ответа: | |
# { | |
# "id" => "21efe1d9-000f-5000-9000-1790d8f88b9b", | |
# "status" => "canceled", | |
# "paid" => false, | |
# "amount" => { | |
# "value"=>"50.00", | |
# "currency"=>"RUB" | |
# }, | |
# "created_at" => "2018-01-16T10:04:36.712Z", | |
# "metadata" => {}, | |
# "payment_method" => { | |
# "type" => "bank_card", | |
# "id" => "21efe1d9-000f-5000-9000-1790d8f88b9b", | |
# "saved" => false | |
# }, | |
# "recipient" => { | |
# "account_id" => "501133", | |
# "gateway_id" => "1501133" | |
# }, "test" => true | |
# } | |
def payment_info(payment_id:, uuid:) | |
@uuid = uuid | |
url = '%s%s' % [@base_url, payment_id] | |
_make_info_payload | |
_send_request(url, :get) | |
end | |
def payment_capture(payment_id:, uuid:, amount:, currency:'RUB') | |
@uuid = uuid | |
@amount = amount | |
@currency = currency | |
url = '%s%s%s' % [@base_url, payment_id, '/capture'] | |
_make_capture_payload | |
_send_request(url, :post) | |
end | |
private | |
def _make_create_payload(receipt) | |
@payload = { | |
amount: { | |
value: @amount, | |
currency: @currency | |
}, | |
capture: false, # Платёж становится `waiting_for_capture` после `pending`, и мы должны принять его, чтобы завершить платёж | |
receipt: receipt, | |
confirmation: { | |
type: 'redirect', # способ оплаты юзер выбирает сам на веб-странице Яндекс.Кассы | |
return_url: @return_url # URL в кабинете, на который вернется пользователь после подтверждения или отмены платежа на веб-странице Яндекс.Кассы | |
} | |
} | |
end | |
def _make_info_payload | |
@payload = { } | |
end | |
def _make_capture_payload | |
@payload = { | |
amount: { | |
value: @amount, | |
currency: @currency | |
} | |
} | |
end | |
# @return [Hash] | |
def _send_request(url = @base_url, method = :post) | |
curl = Curl::Easy.new | |
curl.url = url | |
curl.timeout = 20 | |
curl.connect_timeout = 20 | |
curl.ssl_verify_peer = false | |
curl.verbose = true | |
curl.http_auth_types = [:basic] | |
curl.username = '%s:%s' % [@shop_key, @secret_key] | |
curl.headers['Idempotence-Key'] = @uuid | |
curl.headers['Content-Type'] = 'application/json' | |
_log_info_send(method, url, @payload, @shop_key, @secret_key) | |
# byebug | |
case method | |
when :get; curl.get | |
when :post; curl.post @payload.to_json | |
else raise StandardError.new 'Неизвестный http-метод' | |
end | |
response = JSON.load(curl.body_str) rescue { } | |
message = response['description'] rescue nil | |
message ||= '%s...' % curl.body_str[0..1024] | |
case curl.status.to_i | |
when 200 | |
_log_info_answer response | |
response | |
when 202 # Запрос обрабатывается -- необходимо повторить запрос с тем же заголовком Idempotence-Key | |
# TODO:: необходимо повторить запрос с тем же заголовком Idempotence-Key [20171127] | |
when 400 # Неправильный запрос (response['code']=invalid_request,not_supported) | |
case response['code'] | |
when 'invalid_request' | |
raise InvalidRequest.new message | |
when 'not_supported' | |
raise NotSupported.new message | |
else | |
raise Error.new message | |
end | |
when 401 # Неверный shop_id или secret_key (response['code']=invalid_credentials) | |
raise InvalidCredentials.new message | |
when 403 # Secret_key верный, но не хватает прав (response['code']=forbidden) | |
raise Forbidden.new message | |
when 404 # Ресурс не найден (response['code']=not_found) | |
raise NotFound.new message | |
when 429 # Превышен лимит запросов в ед. времени (response['code']=too_many_requests) | |
raise TooManyRequests.new message | |
when 500 # response['code']=internal_server_error | |
raise InternalServerError.new message | |
else | |
raise Error.new message | |
end | |
end # _send_request | |
def _log_info_send(method, url, data, shop_key, secret_key) | |
Rails.logger.info("\n\e[33m Started CURL %s %s \n Parameters: %s \n shop_key: %s \n secret_key: %s \n idempotence_key: %s \e[0m" % [method.to_s.upcase, url, data, shop_key, secret_key, @uuid]) | |
end | |
def _log_info_answer(response) | |
Rails.logger.info("\e[32m Responce: %s \e[0m\n" % response) | |
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
module Advertisers | |
module Payments | |
# Запросить у Яндекс.Кассы информацию о платеже. | |
class GetInfoYandexKassaPayment | |
attr_reader :errors | |
attr_reader :payment_info | |
def initialize | |
@errors = Validators::Errors.new | |
@payment_info = nil | |
end | |
# @param [Billing::Request<ActiveRecord::Base>] request | |
def perform(request) | |
@errors = Validators::Errors.new | |
@payment_info = nil | |
yandex_client = YandexKassa.new YANDEX_CONFIG | |
begin | |
yandex_response = yandex_client.payment_info( | |
uuid: request.uuid, | |
payment_id: request.payment_id | |
) | |
rescue YandexKassa::InternalServerError => e | |
@errors.add('common', Validators::Rule::CONNECTION_ERROR) | |
return false | |
rescue YandexKassa::Error => e | |
@errors.add('common', Validators::Rule::SYSTEM_ERROR) | |
return false | |
end | |
# Ожидаем, что сервер пришлёт данные о платеже, т.е. в данных должны | |
# быть хотя бы +type+ и +object+. | |
# Если сервер прислал что-то другое -- считаем, что всё плохо. | |
if yandex_response | |
@payment_info = yandex_response | |
true | |
else | |
@errors.add('common', Validators::Rule::CONNECTION_ERROR) | |
false | |
end | |
end # def perform | |
end # class | |
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
class Advertiser::Payments::RequestsController < Advertiser::LayoutController | |
## GET yandex_kassa/return/:id | |
# После завершения оплаты на Яндекс.Кассе, юзера вернут по этому маршруту, | |
# и мы проверяем статус платежа, делая запрос на Яндекс.Кассу, только для того, чтобы | |
# показать соответствующее flash-сообщение. | |
# Параметр `:id` был вшит в `def yandex_kassa_create` -- чтобы распознать, статус какого платежа надо проверить. | |
def yandex_kassa_return | |
request = Billing::Request.find_by(id: params[:id], user_id: current_user.id) | |
if request | |
service = Advertisers::Payments::CheckYandexPaymentStatus.new # проверяем статус платежа, вычисляем, какое сообщение показать | |
if service.perform request | |
flash[:growl] = service.is_payment_succeed ? growl_success(service.growl) : growl_warning(service.growl) # Платёж (ПРОВЕДЁН || ОТМЕНЁН || ОЖИДАЕТ ПОДТВЕРЖДЕНИЯ || ЗАВЕРШЕН И ОЖИДАЕТ ВАШИХ ДЕЙСТВИЙ) | |
else | |
flash[:growl] = growl_error(service.errors.translate.values[0].first) # Validators::Rule::CONNECTION_ERROR || Validators::Rule::SYSTEM_ERROR | |
end | |
else | |
flash[:growl] = growl_error(I18n.t 'system.errors.record_not_found') | |
end | |
redirect_to payments_payers_path | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment