зеркало из
https://github.com/glebtv/yookassa.git
synced 2026-09-05 10:25:50 +03:00
Add Rails webhook engine, docs, and dependency refresh
Этот коммит содержится в:
@@ -8,9 +8,16 @@ end
|
||||
|
||||
require "yookassa"
|
||||
require "webmock/rspec"
|
||||
WebMock.disable_net_connect!(allow_localhost: true)
|
||||
|
||||
Dir[File.expand_path("support/**/*.rb", __dir__)].sort.each { |file| require file }
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.order = :random
|
||||
config.filter_run focus: true
|
||||
config.run_all_when_everything_filtered = true
|
||||
|
||||
config.before do
|
||||
WebMock.reset!
|
||||
end
|
||||
end
|
||||
|
||||
102
spec/support/rails_test_app.rb
Обычный файл
102
spec/support/rails_test_app.rb
Обычный файл
@@ -0,0 +1,102 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
begin
|
||||
require "logger"
|
||||
require "rack/test"
|
||||
require "rails"
|
||||
require "action_controller/railtie"
|
||||
require "capybara"
|
||||
require "capybara/rspec"
|
||||
require "capybara/cuprite"
|
||||
rescue LoadError
|
||||
nil
|
||||
end
|
||||
|
||||
if defined?(Rails)
|
||||
require "yookassa/engine"
|
||||
require File.expand_path("../../app/controllers/yookassa/webhooks_controller", __dir__)
|
||||
|
||||
class YookassaSpecBrowserController < ActionController::Base
|
||||
def index
|
||||
render inline: <<~HTML
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<button id="send">Send webhook</button>
|
||||
<div id="result">pending</div>
|
||||
<script>
|
||||
document.getElementById("send").addEventListener("click", function() {
|
||||
fetch("/yookassa/webhooks/<%= Yookassa.config.webhook_token %>", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
event: "payment.succeeded",
|
||||
object: { id: "browser-payment", status: "succeeded" }
|
||||
})
|
||||
}).then(function(response) {
|
||||
document.getElementById("result").textContent = String(response.status);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
end
|
||||
end
|
||||
|
||||
class YookassaSpecCustomWebhooksController < Yookassa::WebhooksController
|
||||
class_attribute :last_payload, default: nil
|
||||
|
||||
private
|
||||
|
||||
def process_webhook(payload)
|
||||
self.class.last_payload = payload
|
||||
end
|
||||
end
|
||||
|
||||
class YookassaSpecApp < Rails::Application
|
||||
config.root = File.expand_path("../..", __dir__)
|
||||
config.eager_load = false
|
||||
config.secret_key_base = "test-secret-key-base"
|
||||
config.logger = Logger.new(nil)
|
||||
config.hosts << "www.example.com"
|
||||
config.hosts << "example.org"
|
||||
config.hosts << "localhost"
|
||||
config.hosts << "127.0.0.1"
|
||||
config.consider_all_requests_local = true
|
||||
end
|
||||
|
||||
YookassaSpecApp.initialize!
|
||||
|
||||
YookassaSpecApp.routes.draw do
|
||||
mount Yookassa::Engine => "/yookassa"
|
||||
post "/custom-yookassa/:token", to: "yookassa_spec_custom_webhooks#create"
|
||||
get "/browser", to: "yookassa_spec_browser#index"
|
||||
end
|
||||
|
||||
Capybara.app = YookassaSpecApp
|
||||
Capybara.server = :webrick
|
||||
Capybara.default_max_wait_time = 5
|
||||
Capybara.register_driver(:cuprite) do |app|
|
||||
Capybara::Cuprite::Driver.new(app, headless: true)
|
||||
end
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include Rack::Test::Methods
|
||||
|
||||
config.define_derived_metadata(file_path: %r{/spec/yookassa/webhooks_}) do |metadata|
|
||||
metadata[:rails] = true
|
||||
end
|
||||
|
||||
config.before(:each, rails: true) do
|
||||
Yookassa.configure do |yookassa_config|
|
||||
yookassa_config.shop_id = "shop-id"
|
||||
yookassa_config.api_key = "api-key"
|
||||
yookassa_config.webhook_token = "secret-token"
|
||||
yookassa_config.webhook_allowed_ips = Yookassa::Config::DEFAULT_WEBHOOK_ALLOWED_IPS.dup
|
||||
end
|
||||
|
||||
YookassaSpecCustomWebhooksController.last_payload = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
29
spec/yookassa/webhooks_browser_spec.rb
Обычный файл
29
spec/yookassa/webhooks_browser_spec.rb
Обычный файл
@@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe "Yookassa webhook endpoint", :rails, :browser do
|
||||
include Capybara::DSL
|
||||
|
||||
around do |example|
|
||||
previous_driver = Capybara.current_driver
|
||||
Capybara.current_driver = :cuprite
|
||||
example.run
|
||||
Capybara.current_driver = previous_driver
|
||||
end
|
||||
|
||||
before do
|
||||
Yookassa.config.webhook_allowed_ips = ["127.0.0.1"]
|
||||
|
||||
payments_client = instance_double(Yookassa::Payments)
|
||||
allow(Yookassa).to receive(:payments).and_return(payments_client)
|
||||
allow(payments_client).to receive(:find).with(payment_id: "browser-payment")
|
||||
.and_return(instance_double(Yookassa::Entity::Payment, id: "browser-payment", status: "succeeded"))
|
||||
end
|
||||
|
||||
it "accepts browser-submitted webhook request" do
|
||||
visit "/browser"
|
||||
|
||||
click_button "Send webhook"
|
||||
|
||||
expect(page).to have_text("200")
|
||||
end
|
||||
end
|
||||
93
spec/yookassa/webhooks_controller_spec.rb
Обычный файл
93
spec/yookassa/webhooks_controller_spec.rb
Обычный файл
@@ -0,0 +1,93 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Yookassa::WebhooksController do
|
||||
def app
|
||||
YookassaSpecApp
|
||||
end
|
||||
|
||||
let(:payload_hash) do
|
||||
{
|
||||
"event" => "payment.succeeded",
|
||||
"object" => {
|
||||
"id" => "payment-1",
|
||||
"status" => "succeeded"
|
||||
}
|
||||
}
|
||||
end
|
||||
let(:payload) { JSON.generate(payload_hash) }
|
||||
let(:headers) do
|
||||
{
|
||||
"CONTENT_TYPE" => "application/json",
|
||||
"REMOTE_ADDR" => remote_addr
|
||||
}
|
||||
end
|
||||
let(:remote_addr) { "185.71.76.1" }
|
||||
|
||||
describe "POST /yookassa/webhooks/:token", :rails do
|
||||
it "accepts webhook when token, remote_ip, and API re-fetch checks pass" do
|
||||
payments_client = instance_double(Yookassa::Payments)
|
||||
allow(Yookassa).to receive(:payments).and_return(payments_client)
|
||||
allow(payments_client).to receive(:find).with(payment_id: "payment-1")
|
||||
.and_return(instance_double(Yookassa::Entity::Payment, id: "payment-1", status: "succeeded"))
|
||||
|
||||
post "/yookassa/webhooks/secret-token", payload, headers
|
||||
|
||||
expect(last_response.status).to eq(200)
|
||||
end
|
||||
|
||||
it "rejects webhook when token is invalid" do
|
||||
post "/yookassa/webhooks/wrong-token", payload, headers
|
||||
|
||||
expect(last_response.status).to eq(401)
|
||||
end
|
||||
|
||||
it "rejects webhook when source remote_ip is outside allowlist" do
|
||||
payments_client = instance_double(Yookassa::Payments)
|
||||
allow(Yookassa).to receive(:payments).and_return(payments_client)
|
||||
allow(payments_client).to receive(:find).with(payment_id: "payment-1")
|
||||
.and_return(instance_double(Yookassa::Entity::Payment, id: "payment-1", status: "succeeded"))
|
||||
|
||||
post "/yookassa/webhooks/secret-token", payload, headers.merge("REMOTE_ADDR" => "203.0.113.10")
|
||||
|
||||
expect(last_response.status).to eq(401)
|
||||
end
|
||||
|
||||
it "rejects webhook when fetched object differs by status" do
|
||||
payments_client = instance_double(Yookassa::Payments)
|
||||
allow(Yookassa).to receive(:payments).and_return(payments_client)
|
||||
allow(payments_client).to receive(:find).with(payment_id: "payment-1")
|
||||
.and_return(instance_double(Yookassa::Entity::Payment, id: "payment-1", status: "pending"))
|
||||
|
||||
post "/yookassa/webhooks/secret-token", payload, headers
|
||||
|
||||
expect(last_response.status).to eq(401)
|
||||
end
|
||||
end
|
||||
|
||||
describe "IP source check", :rails do
|
||||
it "uses request.remote_ip" do
|
||||
controller = described_class.new
|
||||
request = instance_double(ActionDispatch::Request)
|
||||
|
||||
allow(request).to receive(:remote_ip).and_return("185.71.76.5")
|
||||
allow(request).to receive(:ip).and_raise("request.ip should not be used")
|
||||
allow(controller).to receive(:request).and_return(request)
|
||||
|
||||
expect(controller.send(:source_ip_allowed?)).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe "controller inheritance", :rails do
|
||||
it "allows app controller override via inheritance" do
|
||||
payments_client = instance_double(Yookassa::Payments)
|
||||
allow(Yookassa).to receive(:payments).and_return(payments_client)
|
||||
allow(payments_client).to receive(:find).with(payment_id: "payment-1")
|
||||
.and_return(instance_double(Yookassa::Entity::Payment, id: "payment-1", status: "succeeded"))
|
||||
|
||||
post "/custom-yookassa/secret-token", payload, headers
|
||||
|
||||
expect(last_response.status).to eq(200)
|
||||
expect(YookassaSpecCustomWebhooksController.last_payload).to eq(payload_hash)
|
||||
end
|
||||
end
|
||||
end
|
||||
Ссылка в новой задаче
Block a user