From f3ff78fee1007d711885d95f84eab20de463accd Mon Sep 17 00:00:00 2001 From: Ivan Shamatov Date: Mon, 1 Nov 2021 23:12:07 +0300 Subject: [PATCH] Rewriting Client and changing interface --- lib/yookassa.rb | 33 ++++++++----- lib/yookassa/client.rb | 44 ++++++++++++++++++ lib/yookassa/entity/error.rb | 2 + lib/yookassa/entity/payment.rb | 2 + lib/yookassa/entity/refund.rb | 1 + lib/yookassa/http_helpers.rb | 29 ------------ lib/yookassa/payment.rb | 46 ------------------- lib/yookassa/payments.rb | 35 ++++++++++++++ lib/yookassa/refund.rb | 28 ----------- lib/yookassa/refunds.rb | 25 ++++++++++ .../{payment_spec.rb => payments_spec.rb} | 14 +++--- .../{refund_spec.rb => refunds_spec.rb} | 15 +++--- spec/yookassa_spec.rb | 35 +++++++++++--- 13 files changed, 175 insertions(+), 134 deletions(-) create mode 100644 lib/yookassa/client.rb delete mode 100644 lib/yookassa/http_helpers.rb delete mode 100644 lib/yookassa/payment.rb create mode 100644 lib/yookassa/payments.rb delete mode 100644 lib/yookassa/refund.rb create mode 100644 lib/yookassa/refunds.rb rename spec/yookassa/{payment_spec.rb => payments_spec.rb} (91%) rename spec/yookassa/{refund_spec.rb => refunds_spec.rb} (79%) diff --git a/lib/yookassa.rb b/lib/yookassa.rb index aaebd55..9ab3420 100644 --- a/lib/yookassa.rb +++ b/lib/yookassa.rb @@ -1,22 +1,31 @@ # frozen_string_literal: true -require "http" require "dry-struct" +require "forwardable" require "yookassa/version" -require "yookassa/payment" -require "yookassa/refund" -require "yookassa/entity/payment" -require "yookassa/entity/refund" -require "yookassa/entity/error" require "yookassa/config" -require "yookassa/http_helpers" +require "yookassa/client" module Yookassa - def self.configure - yield(config) - end + ConfigError = Class.new(StandardError) - def self.config - @config ||= Config.new + class << self + extend Forwardable + + def configure + yield(config) + end + + def config + @config ||= Config.new + end + + def client + raise ConfigError, "Specify `shop_id` and `api_key` settins in a `.configure` block" if @config.nil? + + @client ||= Client.new(shop_id: @config.shop_id, api_key: @config.api_key) + end + + def_delegators :client, :payments, :refunds end end diff --git a/lib/yookassa/client.rb b/lib/yookassa/client.rb new file mode 100644 index 0000000..03e3369 --- /dev/null +++ b/lib/yookassa/client.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require "http" +require_relative "./payments" +require_relative "./refunds" +require_relative "./entity/error" + +module Yookassa + class Client + API_URL = "https://api.yookassa.ru/v3/" + + attr_reader :http + + def initialize(shop_id:, api_key:) + @http = HTTP.basic_auth(user: shop_id, pass: api_key).headers(accept: "application/json") + end + + def payments + @payments ||= Payments.new(self) + end + + def refunds + @refunds ||= Refunds.new(self) + end + + def get(endpoint, query: {}) + api_call { http.get("#{API_URL}#{endpoint}", params: query) } + end + + def post(endpoint, idempotency_key:, payload: {}) + api_call { http.headers("Idempotence-Key" => idempotency_key).post("#{API_URL}#{endpoint}", json: payload) } + end + + private + + def api_call + response = yield if block_given? + body = JSON.parse(response.body.to_s, symbolize_names: true) + return body if response.status.success? + + Entity::Error.new(**body) + end + end +end diff --git a/lib/yookassa/entity/error.rb b/lib/yookassa/entity/error.rb index 6d225e4..5e99f65 100644 --- a/lib/yookassa/entity/error.rb +++ b/lib/yookassa/entity/error.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require_relative "./types" + module Yookassa module Entity class Error < Dry::Struct diff --git a/lib/yookassa/entity/payment.rb b/lib/yookassa/entity/payment.rb index 3eee770..f64d642 100644 --- a/lib/yookassa/entity/payment.rb +++ b/lib/yookassa/entity/payment.rb @@ -16,6 +16,8 @@ module Yookassa # Payment ID in YooMoney. attribute :id, Types::String + attribute? :idempotency_key, Types::String + # status [string, required] # Payment status. Possible values: pending, waiting_for_capture, succeeded, and canceled. # More about the life cycle of a payment https://yookassa.ru/en/developers/api#:~:text=life%20cycle%20of%20a%20payment%C2%A0 diff --git a/lib/yookassa/entity/refund.rb b/lib/yookassa/entity/refund.rb index 03613cb..920547b 100644 --- a/lib/yookassa/entity/refund.rb +++ b/lib/yookassa/entity/refund.rb @@ -7,6 +7,7 @@ module Yookassa module Entity class Refund < Dry::Struct attribute :id, Types::String + attribute? :idempotency_key, Types::String attribute :status, Types::String attribute :payment_id, Types::String attribute :created_at, Types::String diff --git a/lib/yookassa/http_helpers.rb b/lib/yookassa/http_helpers.rb deleted file mode 100644 index 26e80d6..0000000 --- a/lib/yookassa/http_helpers.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module Yookassa - module HttpHelpers - API_URL = "https://api.yookassa.ru/v3/" - - def get(endpoint, query: {}) - response = client.get("#{API_URL}#{endpoint}", params: query) - body = JSON.parse(response.body.to_s, symbolize_names: true) - - return Entity::Error.new(**body) if response.status.client_error? - - yield(body) if block_given? - end - - def post(endpoint, idempotency_key:, payload: {}) - response = client.headers("Idempotence-Key" => idempotency_key).post("#{API_URL}#{endpoint}", json: payload) - body = JSON.parse(response.body.to_s, symbolize_names: true) - - return Entity::Error.new(**body) if response.status.client_error? - - yield(body) if block_given? - end - - def client - HTTP.basic_auth(user: shop_id, pass: api_key).headers(accept: "application/json") - end - end -end diff --git a/lib/yookassa/payment.rb b/lib/yookassa/payment.rb deleted file mode 100644 index 982d2f6..0000000 --- a/lib/yookassa/payment.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require_relative "http_helpers" - -module Yookassa - class Payment - include HttpHelpers - - attr_reader :shop_id, :api_key - - def initialize(shop_id: Yookassa.config.shop_id, api_key: Yookassa.config.api_key) - @shop_id = shop_id - @api_key = api_key - end - - def get_payment_info(payment_id:) - get("payments/#{payment_id}") do |response| - Entity::Payment.new(**response) - end - end - - def create(payment:, idempotency_key: SecureRandom.hex(10)) - post("payments", payload: payment, idempotency_key: idempotency_key) do |response| - Entity::Payment.new(**response) - end - end - - def capture(payment_id:, idempotency_key: SecureRandom.hex(10)) - post("payments/#{payment_id}/capture", idempotency_key: idempotency_key) do |response| - Entity::Payment.new(**response) - end - end - - def cancel(payment_id:, idempotency_key: SecureRandom.hex(10)) - post("payments/#{payment_id}/cancel", idempotency_key: idempotency_key) do |response| - Entity::Payment.new(**response) - end - end - - # def self.list(shop_id: Yookassa.config.shop_id, api_key: Yookassa.config.api_key) - # get("payments", shop_id: shop_id, api_key: api_key) do |resp| - # resp['items'].map { Entity::Payment.new(_1) } - # end - # end - end -end diff --git a/lib/yookassa/payments.rb b/lib/yookassa/payments.rb new file mode 100644 index 0000000..47623ff --- /dev/null +++ b/lib/yookassa/payments.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require_relative "./entity/payment" + +module Yookassa + class Payments + def initialize(api) + @api = api + end + + def find(payment_id:) + data = api.get("payments/#{payment_id}") + Entity::Payment.new(**data) + end + + def create(payment:, idempotency_key: SecureRandom.hex(10)) + data = api.post("payments", payload: payment, idempotency_key: idempotency_key) + Entity::Payment.new(**data.merge(idempotency_key: idempotency_key)) + end + + def capture(payment_id:, idempotency_key: SecureRandom.hex(10)) + data = api.post("payments/#{payment_id}/capture", idempotency_key: idempotency_key) + Entity::Payment.new(**data.merge(idempotency_key: idempotency_key)) + end + + def cancel(payment_id:, idempotency_key: SecureRandom.hex(10)) + data = api.post("payments/#{payment_id}/cancel", idempotency_key: idempotency_key) + Entity::Payment.new(**data.merge(idempotency_key: idempotency_key)) + end + + private + + attr_reader :api + end +end diff --git a/lib/yookassa/refund.rb b/lib/yookassa/refund.rb deleted file mode 100644 index 09cc6e5..0000000 --- a/lib/yookassa/refund.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -require_relative "http_helpers" - -module Yookassa - class Refund - include HttpHelpers - - attr_reader :shop_id, :api_key - - def initialize(shop_id: Yookassa.config.shop_id, api_key: Yookassa.config.api_key) - @shop_id = shop_id - @api_key = api_key - end - - def get_refund_info(payment_id:) - get("refunds/#{payment_id}") do |response| - Entity::Refund.new(**response) - end - end - - def create(payload:, idempotency_key: SecureRandom.hex(10)) - post("refunds", payload: payload, idempotency_key: idempotency_key) do |response| - Entity::Refund.new(**response) - end - end - end -end diff --git a/lib/yookassa/refunds.rb b/lib/yookassa/refunds.rb new file mode 100644 index 0000000..43869c0 --- /dev/null +++ b/lib/yookassa/refunds.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require_relative "./entity/refund" + +module Yookassa + class Refunds + def initialize(api) + @api = api + end + + def find(payment_id:) + data = api.get("refunds/#{payment_id}") + Entity::Refund.new(**data) + end + + def create(payload:, idempotency_key: SecureRandom.hex(10)) + data = api.post("refunds", payload: payload, idempotency_key: idempotency_key) + Entity::Refund.new(**data.merge(idempotency_key: idempotency_key)) + end + + private + + attr_reader :api + end +end diff --git a/spec/yookassa/payment_spec.rb b/spec/yookassa/payments_spec.rb similarity index 91% rename from spec/yookassa/payment_spec.rb rename to spec/yookassa/payments_spec.rb index f13b39a..2286edb 100644 --- a/spec/yookassa/payment_spec.rb +++ b/spec/yookassa/payments_spec.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true -RSpec.describe Yookassa::Payment do - let(:settings) { { shop_id: "SHOP_ID", api_key: "API_KEY" } } - let(:idempotency_key) { 12_345 } - let(:payment) { described_class.new(**settings) } +RSpec.describe Yookassa::Payments do + let(:config) { { shop_id: "SHOP_ID", api_key: "API_KEY" } } + let(:client) { Yookassa::Client.new(**config) } + let(:idempotency_key) { SecureRandom.hex(1) } + + let(:payment) { client.payments } let(:body) { File.read("spec/fixtures/payment_response.json") } before { stub_request(:any, //).to_return(body: body, headers: { "Content-Type" => "application/json" }) } @@ -55,11 +57,11 @@ RSpec.describe Yookassa::Payment do it_behaves_like "returns_payment_object" end - describe "#get_payment_info" do + describe "#find" do let(:payment_id) { "2490ded1-000f-5000-8000-1f64111bc63e" } let(:url) { "https://api.yookassa.ru/v3/payments/#{payment_id}" } - subject { payment.get_payment_info(payment_id: payment_id) } + subject { payment.find(payment_id: payment_id) } it "sends a request" do subject diff --git a/spec/yookassa/refund_spec.rb b/spec/yookassa/refunds_spec.rb similarity index 79% rename from spec/yookassa/refund_spec.rb rename to spec/yookassa/refunds_spec.rb index 526d3d9..d075d65 100644 --- a/spec/yookassa/refund_spec.rb +++ b/spec/yookassa/refunds_spec.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true -RSpec.describe Yookassa::Refund do - let(:settings) { { shop_id: "SHOP_ID", api_key: "API_KEY" } } - let(:idempotency_key) { 12_345 } - let(:payment) { described_class.new(**settings) } +RSpec.describe Yookassa::Refunds do + let(:config) { { shop_id: "SHOP_ID", api_key: "API_KEY" } } + let(:client) { Yookassa::Client.new(**config) } + let(:idempotency_key) { SecureRandom.hex(1) } + let(:refund) { client.refunds } let(:body) { File.read("spec/fixtures/refund_response.json") } before { stub_request(:any, //).to_return(body: body, headers: { "Content-Type" => "application/json" }) } @@ -27,7 +28,7 @@ RSpec.describe Yookassa::Refund do let(:payload) { JSON.parse(File.read("spec/fixtures/refund.json")) } let(:url) { "https://api.yookassa.ru/v3/refunds" } - subject { payment.create(payload: payload, idempotency_key: idempotency_key) } + subject { refund.create(payload: payload, idempotency_key: idempotency_key) } it "sends a request" do subject @@ -37,11 +38,11 @@ RSpec.describe Yookassa::Refund do it_behaves_like "returns_refund_object" end - describe "#get_refund_info" do + describe "#find" do let(:payment_id) { "2490ded1-000f-5000-8000-1f64111bc63e" } let(:url) { "https://api.yookassa.ru/v3/refunds/#{payment_id}" } - subject { payment.get_refund_info(payment_id: payment_id) } + subject { refund.find(payment_id: payment_id) } it "sends a request" do subject diff --git a/spec/yookassa_spec.rb b/spec/yookassa_spec.rb index ec16e61..1acdf7c 100644 --- a/spec/yookassa_spec.rb +++ b/spec/yookassa_spec.rb @@ -5,17 +5,40 @@ RSpec.describe Yookassa do expect(Yookassa::VERSION).not_to be nil end - describe ".configure" do - before do - Yookassa.configure do |config| - config.shop_id = 123 - config.api_key = "test_321" - end + before do + Yookassa.configure do |config| + config.shop_id = 123 + config.api_key = "test_321" end + end + describe ".configure" do it "stores settings and provides access to credentials" do expect(Yookassa.config.shop_id).to eq(123) expect(Yookassa.config.api_key).to eq("test_321") end end + + describe ".client" do + context "when no settings are provided" do + before { Yookassa.instance_variable_set(:@config, nil) } + + it "raises an error" do + expect { Yookassa.client }.to raise_error(Yookassa::ConfigError) + end + end + + context "when instance configured" do + it "creates and stores client" do + expect(Yookassa.client).to be_a(Yookassa::Client) + expect(Yookassa.client).to eq(Yookassa.client) + end + end + end + + describe ".payments" do + it "delegates request to client and creates an instance" do + expect(Yookassa.payments).to be_a(Yookassa::Payments) + end + end end