Как провести платеж с помощью Yandex.Money API на Ruby on Rails?

Я использую RoR и гем по ссылке https://github.com/yandex-money/yandex-money-sdk-ruby
Читаю доку, раздел: Payments from bank cards without authorization
Там три шага. После третьего имею статус ext_auth_required, во входящем параметре acs_uri получаю ссылку, на которую нужно послать post (прочитал здесь):
Следует открыть WebView и отправить клиента при помощи POST-запроса по адресу acs_uri с параметрами acs_params
. Теперь мне нужно каким-то образом редиректится на acs_uri методом POST. Каким образом мне это сделать? Редирект идет по GET запросу, а нужен POST и переход на страницу Яндекс.Денег. Если пробовать простым redirect_to - Яндекс выдает ошибку: "Ой, что-то пошло не так". В общем вопрос в том, что делать дальше после описанных 3 шагов? Инструкция кончается, а платеж еще не прошел.
Мой код :
require 'yandex_money/api'
require "httparty"

module YandexMoneyHelper
	class Payment
			include HTTParty

			def initialize
			end

			def account_replenishment(amount, email)
				i_id = ym_get_instance_id
				r_id = ym_get_request_id(i_id, amount, email)
				res = ym_process_payment(i_id, r_id)
			end
		protected
			#Функции для приема платежей с банковских карт

			#Получение идентификатора экземпляра приложения на основе 
			# client_id (выдается при регистрации приложения).
			# Получается один раз. Сохраняется в безопасном месте
			def ym_get_instance_id
				api = YandexMoney::Api.new(client_id: Rails.application.secrets.yandex_money_app_id)
				instance_id = api.get_instance_id
			end

			def ym_get_request_id(inst_id, amount, email)
				api = YandexMoney::Api.new(
				  client_id: Rails.application.secrets.yandex_money_app_id,
				  instance_id: inst_id
				)
				response = api.request_external_payment({
				  pattern_id: "p2p", #Фиксированное значение «p2p».
				  to: Rails.application.secrets.yandex_money_wallet, #Номер счета для пополнения
				  #amount: "1.00" Сумма к списанию с банковской карты (на счет поступит эта сумма минус комиссия).
				  amount_due: amount, #Сумма к зачислению на счет (с карты будет списана эта сумма плюс комиссия).
				  message: "Пополнение счета от " + email #Комментарий к зачислению, отображается получателю
				})
				if response.status == "success"
				  request_id = response.request_id
				else
				  request_id = null
				end
			end

			def ym_process_payment(inst_id, req_id)
				api = YandexMoney::Api.new(instance_id: inst_id)
				result = api.process_external_payment({
				  request_id: req_id,
				  ext_auth_success_uri: "http://example.com/success",
				  ext_auth_fail_uri: "http://example.com/fail"
				})
				if result.status == "ext_auth_required"
					#Сюда нужно написать редирект методом POST!
				end
			end		
	end
end
  • Вопрос задан
  • 5803 просмотра
Решения вопроса 1
@sunnmas Автор вопроса
Ruby
Вернулся спустя полгода к проблеме, допилил, работает, юзайте, и да, укажите на проблемы безопасности, если они есть...
Gemfile
gem 'yandex-money-sdk'
gem 'devise' #управление юзерами

routes
get		'/кошелек/пополнить', :to=> 'cabinet/balance#replenishment', :as => :replenishment
		post	'/пополнение/счета', :to=> 'cabinet/balance#payment_process', :as => :payment_process
		get		'/платеж/не/прошел', :to=> 'cabinet/balance#payment_fail', :as => :payment_fail
		get		'/платеж/прошел/:secure_code', :to=> 'cabinet/balance#payment_success', :as => :payment_success

app/helpers/yandex_money_helper.rb
require 'yandex_money/api'

module YandexMoneyHelper
	class YM
		# Класс служит для приема денег на Яндекс кошелек от других пользователей

		# Разработчик регистрирует свое приложение в Яндекс.Деньгах,
		# получает client_id - идентификатор приложения. 
		# В течение жизненного цикла приложения client_id не изменяется.
		CLIENT_ID = Rails.application.secrets.yandex_money_app_id
		# Номер кошелька для приема нлатежей
		WALLET_ID = Rails.application.secrets.yandex_money_wallet

		attr_reader :successful
		attr_reader :error
		attr_reader :secure_code

		def initialize
			puts "=::YANDEX MONEY INITIALIZE::="
			puts "CLIENT_ID: #{CLIENT_ID}"
			puts "WALLET_ID: #{WALLET_ID}"

			#Получение идентификатора экземпляра приложения на основе client_id
			@instance_id = YandexMoney::ExternalPayment.get_instance_id(CLIENT_ID)
			if @instance_id.status == "success"
				@instance_id = @instance_id.instance_id
				@ext_pay = YandexMoney::ExternalPayment.new @instance_id
				@successful = true
			else
				@successful = false
				@error = @instance_id.error
			end

			puts "instance_id: #{@instance_id}"
			puts "ext_pay: #{@ext_pay}"
		end
		# Пополнение счета:
		def recieve_payment(amount, email)
			puts "reciving payment..."
			@successful = false
			@secure_code = rand 10000000..99999999
			puts "secure_code: #{@secure_code}"
			response = 	@ext_pay.request_external_payment({
			  pattern_id: "p2p", #Фиксированное значение «p2p».
			  to: WALLET_ID, #Номер счета для пополнения
			  #amount: "1.00" Сумма к списанию с банковской карты (на счет поступит эта сумма минус комиссия).
			  amount_due: amount, #Сумма к зачислению на счет (с карты будет списана эта сумма плюс комиссия).
			  message: "Пополнение счета от #{email}" #Комментарий к зачислению, отображается получателю
			})
			puts "get request_id: #{response}"
			if response.status != "success"
				@error = response.error
				return
			end

			@request_id = response.request_id
			puts "request_id: #{@request_id}"
			puts "process_payment..."
			res = @ext_pay.process_external_payment({
				request_id: @request_id,
				ext_auth_success_uri: Rails.application.routes.url_helpers.payment_success_url(:secure_code => @secure_code),
				ext_auth_fail_uri: Rails.application.routes.url_helpers.payment_fail_url
			})
			@successful = true
			puts res
			res
		rescue => e
			@successful = false
			@error = e.message
			puts "error recieving payment #{e.message}"
			{:status => "error", :error => e.message}
		end
	end
end


/app/controllers/cabinet/balance_controller.rb
class Cabinet::BalanceController < Cabinet::CabinetController
#Пополнение счета
	def replenishment
		@amount = 100
	end

	def payment_process
		@params[:amount] = {:type => :integer, :in => 1..10000.0}
		return if !sanitize_params

		@amount = params[:amount]
		# Инициализация модуля Яндекс Деньги
		ym = YM.new
		err = "Ошибка работы с модулем оплаты "
		if !ym.successful
			err << ym.error if Rails.env.development?
			flash[:error] = err
			redirect_to replenishment_path
			return
		end
		# Прием платежа на кошелек Яндекс Денег
		res = ym.recieve_payment(@amount, "#{current_user.id}: #{current_user.email}")
		if !ym.successful
			err << ym.error if Rails.env.development?
			flash[:error] = err
			redirect_to replenishment_path
			return
		end
		# Требуется внешняя аутентификация пользователя
		if res.status != "ext_auth_required"
			err = "Нет запроса на внешнюю авторизацию"
			err << "status: #{res.status} error: #{res.error}" if Rails.env.development?
			flash[:error] = err
			redirect_to replenishment_path
			return
		end
		# Сохраняем проверочный код платежа в сессии
		session[:ym_secure_code] = ym.secure_code
		session[:ym_amount] = @amount

		# Отсылаем методом POST acs_params:
		@acs_params = res.acs_params
		@acs_uri = res.acs_uri
		render :payment_process
	end

	def payment_fail
		msg = 'Платеж не прошел.'
		
		reason = case params[:reason]
		when 'no3ds' then 'Указан неверный код CVV.'
		when 'not-enough-funds' then 'Недостаточно средств.'
		else
			e = Exception.new 'Платеж не прошел.'
			ExceptionNotifier.notify_exception(e,
				:env => request.env, 
				:data => {:message => "Ошибка при совершении оплаты: #{params[:reason]}"})
			params[:reason]
		end
		flash[:error] = msg << ' ' << reason

		redirect_to replenishment_path
	end

	def payment_success
#Эти 2 строки измените под свои нужды:
		@params[:secure_code] = {:type => :integer, :in => 10000000..99999999}
		return if !sanitize_params

		secure_code = params[:secure_code]

		if !session[:ym_secure_code] || !session[:ym_amount]
			flash[:error] = "Платеж не прошел. Ошибка сессии"
			redirect_to replenishment_path
			return
		end

		amount = session[:ym_amount].to_i
		if session[:ym_secure_code].to_i != secure_code
			flash[:error] = "Не верный проверочный код платежа. Платеж не прошел"
		else
			current_user.with_lock do
				@inc = Incoming.create(:user_id => current_user.id, :amount => amount)
				current_user.amount += amount
				current_user.amount = eval(sprintf("%8.2f",current_user.amount))
				current_user.save(validate: false)
			end
			notify	"Баланс пополнен",
					"Баланс пополнен на сумму #{amount} #{rub}. Текущий баланс: #{current_user.amount} #{rub}"
			UserMailer.new_incoming(@inc.id).deliver if current_user.notify_incomings
			flash[:success] = "Платеж прошел"
		end
		redirect_to replenishment_path
	ensure
		session[:ym_secure_code] = nil
		session[:amount] = nil
	end
end


app/views/replenishment.html.haml
= form_tag(payment_process_path, method: "post") do
	= label_tag(:amount, "Сумма без учета комисси банка, #{rub}:")
	= number_field_tag :amount,  @amount ,in: 1..10000, step: 1
	= submit_tag "Пополнить", :class => "button"

app/views/payment_process.html.haml
%p Подождите, Вы будете перенаправлены на страницу Яндекс Денег
%form#ext_auth_required{:action=>@acs_uri}
	%input{:type=>"hidden", :name=>"cps_context_id", :value=> @acs_params["cps_context_id"]}
	%input{:type=>"hidden", :name=>"paymentType", :value=> @acs_params["paymentType"]}

:javascript
	document.forms["ext_auth_required"].submit();


secure_code здесь это случайное число, которое рождается во время нового платежа и сохраняется в сессии юзера. Оно прикручено для того. чтобы пользователь не смог перейти по адресу /платеж/прошел и на шару пополнить себе баланс. Очищается из сессии в любом случае при переходе по этому адресу.

Здесь форма ext_auth_required нужна для посылки POST запроса на сервак яндекса (см. api ЯД)
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
shaks
@shaks
Редирект делать через жс прокладку. Сабмитить форму просто. Я делал именно так (правда не с ЯДом).
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы