From 114f49d9f48748007418409cbd1642f2baa4f1b3 Mon Sep 17 00:00:00 2001 From: Harry Lewis Date: Mon, 11 Mar 2024 13:28:12 -0400 Subject: [PATCH] Add support for Merchant Report (#54) * Add new API method, Bambora::V1::Reports::Merchants.get_all * Add new API method, Bambora::V1::Reports::Merchants.get * Add custom error classes * Raise error for unauthenticated request * Raise error for invalid request * Raise an error for .get when passing the wrong argument --- lib/bambora/client.rb | 2 + lib/bambora/errors.rb | 36 ++ lib/bambora/v1/reports/merchants.rb | 39 +++ spec/bambora/v1/reports/merchants_spec.rb | 406 ++++++++++++++++++++++ 4 files changed, 483 insertions(+) create mode 100644 lib/bambora/errors.rb create mode 100644 lib/bambora/v1/reports/merchants.rb create mode 100644 spec/bambora/v1/reports/merchants_spec.rb diff --git a/lib/bambora/client.rb b/lib/bambora/client.rb index 0f269a2..de51399 100644 --- a/lib/bambora/client.rb +++ b/lib/bambora/client.rb @@ -13,6 +13,7 @@ require 'bambora/client/version' require 'bambora/credentials' +require 'bambora/errors' # Adapters require 'bambora/adapters/response' @@ -45,6 +46,7 @@ require 'bambora/bank/payment_profile_resource' require 'bambora/bank/batch_report_messages' require 'bambora/bank/batch_report_resource' +require 'bambora/v1/reports/merchants' module Bambora ## diff --git a/lib/bambora/errors.rb b/lib/bambora/errors.rb new file mode 100644 index 0000000..26fdba8 --- /dev/null +++ b/lib/bambora/errors.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Bambora + # All library-specific errors inherit from this error class. This helps make it easy for integrations to rescue + # specifically from errors raised by this library. + class Error < StandardError + attr_reader :payload + + def initialize(message = nil, payload = {}) + @message = message + @payload = payload + + super( + <<~ERROR + #{@message} + + #{JSON.pretty_generate(@payload).gsub("\n", "\n ")} + ERROR + ) + end + end + + # An error returned when the API returns an authentication failure. + class InvalidAuthenticationError < Error + def initialize(payload = {}) + super(nil, payload) + end + end + + # An error returned when the API returns an invalid request. + class InvalidRequestError < Error + def initialize(payload = {}) + super(nil, payload) + end + end +end diff --git a/lib/bambora/v1/reports/merchants.rb b/lib/bambora/v1/reports/merchants.rb new file mode 100644 index 0000000..334c52b --- /dev/null +++ b/lib/bambora/v1/reports/merchants.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Bambora + module V1 + module Reports + class Merchants + def self.get(merchant_id, options = {}) + credentials = options[:credentials] + + raise ArgumentError, "#{self}.get takes an integer argument" unless merchant_id.is_a?(Integer) + + response = + Bambora::Rest::JSONClient + .new(base_url: 'https://api.na.bambora.com', merchant_id: credentials.merchant_id) + .get(path: "/v1/reports/merchants/#{merchant_id}", api_key: credentials.reporting_passcode) + + raise InvalidAuthenticationError, response if response[:message] == 'Authentication failed' + + raise InvalidRequestError, response if response == { Message: 'The request is invalid.' } + + response + end + + def self.get_all(options = {}) + credentials = options[:credentials] + + response = + Bambora::Rest::JSONClient + .new(base_url: 'https://api.na.bambora.com', merchant_id: credentials.merchant_id) + .get(path: '/v1/reports/merchants', api_key: credentials.reporting_passcode) + + raise InvalidAuthenticationError, response if response[:message] == 'Authentication failed' + + response + end + end + end + end +end diff --git a/spec/bambora/v1/reports/merchants_spec.rb b/spec/bambora/v1/reports/merchants_spec.rb new file mode 100644 index 0000000..ad331a1 --- /dev/null +++ b/spec/bambora/v1/reports/merchants_spec.rb @@ -0,0 +1,406 @@ +# frozen_string_literal: true + +RSpec.describe Bambora::V1::Reports::Merchants do + describe '.get' do + context 'when passing nil' do + it 'raises ArgumentError' do + credentials = Bambora::Credentials.new( + merchant_id: '372110000', + reporting_passcode: '28840CEB9H9D3DFC24A445B7461FD8FG', + ) + + expect { described_class.get(nil, credentials: credentials) }.to( + raise_error(ArgumentError), + ) + end + end + + context 'when passing a boolean' do + it 'raises ArgumentError' do + credentials = Bambora::Credentials.new( + merchant_id: '372110000', + reporting_passcode: '28840CEB9H9D3DFC24A445B7461FD8FG', + ) + + expect { described_class.get(true, credentials: credentials) }.to( + raise_error(ArgumentError), + ) + end + end + + context 'when passing a symbol' do + it 'raises ArgumentError' do + credentials = Bambora::Credentials.new( + merchant_id: '372110000', + reporting_passcode: '28840CEB9H9D3DFC24A445B7461FD8FG', + ) + + expect { described_class.get(:symbol, credentials: credentials) }.to( + raise_error(ArgumentError), + ) + end + end + + context 'when passing a number (float)' do + it 'raises ArgumentError' do + credentials = Bambora::Credentials.new( + merchant_id: '372110000', + reporting_passcode: '28840CEB9H9D3DFC24A445B7461FD8FG', + ) + + expect { described_class.get(1.0, credentials: credentials) }.to( + raise_error(ArgumentError), + ) + end + end + + context 'when passing an array' do + it 'raises ArgumentError' do + credentials = Bambora::Credentials.new( + merchant_id: '372110000', + reporting_passcode: '28840CEB9H9D3DFC24A445B7461FD8FG', + ) + + expect { described_class.get([], credentials: credentials) }.to( + raise_error(ArgumentError), + ) + end + end + + context 'when passing a hash' do + it 'raises ArgumentError' do + credentials = Bambora::Credentials.new( + merchant_id: '372110000', + reporting_passcode: '28840CEB9H9D3DFC24A445B7461FD8FG', + ) + + expect { described_class.get({}, credentials: credentials) }.to( + raise_error(ArgumentError), + ) + end + end + + context 'when passing a number (integer), but the API request is not authenticated' do + it 'raises Bambora::InvalidAuthenticationError' do + WebMock + .stub_request(:get, 'https://api.na.bambora.com/v1/reports/merchants/372110001') + .with( + headers: { + 'Authorization' => 'Passcode MzcyMTEwMDAwOjI4ODQwQ0VCOUg5RDNERkMyNEE0NDVCNzQ2MUZEOEZH', + 'Content-Type' => 'application/json', + }, + ) + .to_return( + headers: { + 'Content-Type' => 'application/json', + }, + body: { + code: 21, + category: 4, + message: 'Authentication failed', + }.to_json, + ) + + credentials = Bambora::Credentials.new( + merchant_id: '372110000', + reporting_passcode: '28840CEB9H9D3DFC24A445B7461FD8FG', + ) + + expect { described_class.get(372_110_001, credentials: credentials) }.to( + raise_error(Bambora::InvalidAuthenticationError), + ) + end + end + + context 'when passing a number (integer), but the API request is invalid' do + it 'raises Bambora::InvalidRequestError' do + WebMock + .stub_request(:get, 'https://api.na.bambora.com/v1/reports/merchants/372110001') + .with( + headers: { + 'Authorization' => 'Passcode MzcyMTEwMDAwOjI4ODQwQ0VCOUg5RDNERkMyNEE0NDVCNzQ2MUZEOEZH', + 'Content-Type' => 'application/json', + }, + ) + .to_return( + headers: { + 'Content-Type' => 'application/json', + }, + body: { + Message: 'The request is invalid.', + }.to_json, + ) + + credentials = Bambora::Credentials.new( + merchant_id: '372110000', + reporting_passcode: '28840CEB9H9D3DFC24A445B7461FD8FG', + ) + + expect { described_class.get(372_110_001, credentials: credentials) }.to( + raise_error(Bambora::InvalidRequestError), + ) + end + end + + context 'when passing a number (integer), the API request is authenticated and valid' do + it 'returns merchant data for the merchant' do + WebMock + .stub_request(:get, 'https://api.na.bambora.com/v1/reports/merchants/372110001') + .with( + headers: { + 'Authorization' => 'Passcode MzcyMTEwMDAwOjI4ODQwQ0VCOUg5RDNERkMyNEE0NDVCNzQ2MUZEOEZH', + 'Content-Type' => 'application/json', + }, + ) + .to_return( + headers: { + 'Content-Type' => 'application/json', + }, + body: { + data: [ + [ + { + merchant_id: 372_110_001, + merchant_name: 'ABC Business', + website: '', + address: { + street_address: '700 Main Street', + province: 'New York', + country: 'United States', + postal_code: '82210', + }, + merchant_status: { + status: 'Active', + state: 'Live', + live_date: '2024-01-01', + created_date: '2024-01-01', + authorized_date: '2024-01-05', + temp_disabled_date: '0001-01-01', + last_enabled_date: '0001-01-01', + disabled_date: '0001-01-01', + }, + processor: { + processor_name: 'First Data', + currency: 'USD', + }, + batch: { + batch_limit: 100_000.0, + batch_line_limit: 100_000.0, + eft_credit_lag: 3, + }, + settlement: { + credit_lag: 0, + }, + cards: { + mastercard_enabled: false, + visa_enabled: false, + amex_enabled: false, + discover_enabled: false, + diners_enabled: false, + jcb_enabled: false, + mastercard_debit_enabled: false, + visa_debit_enabled: false, + }, + features: { + checkout_enabled: true, + payment_profile_enabled: false, + recurring_billing_enabled: true, + credit_card_batch_enabled: false, + eft_ach_enabled: true, + interac_online_enabled: false, + visa_src_enabled: false, + }, + }, + ], + ], + }.to_json, + ) + + credentials = Bambora::Credentials.new( + merchant_id: '372110000', + reporting_passcode: '28840CEB9H9D3DFC24A445B7461FD8FG', + ) + + merchants = described_class.get(372_110_001, credentials: credentials) + + expect(merchants[:data][0].length).to eq(1) + expect(merchants[:data][0][0][:merchant_id]).to eq(372_110_001) + expect(merchants[:data][0][0][:merchant_name]).to eq('ABC Business') + end + end + end + + describe '.get_all' do + context 'when the API request is not authenticated' do + it 'raises Bambora::InvalidAuthenticationError' do + WebMock + .stub_request(:get, 'https://api.na.bambora.com/v1/reports/merchants') + .with( + headers: { + 'Authorization' => 'Passcode MzcyMTEwMDAwOjI4ODQwQ0VCOUg5RDNERkMyNEE0NDVCNzQ2MUZEOEZH', + 'Content-Type' => 'application/json', + }, + ) + .to_return( + headers: { + 'Content-Type' => 'application/json', + }, + body: { + code: 21, + category: 4, + message: 'Authentication failed', + }.to_json, + ) + + credentials = Bambora::Credentials.new( + merchant_id: '372110000', + reporting_passcode: '28840CEB9H9D3DFC24A445B7461FD8FG', + ) + + expect { described_class.get_all(credentials: credentials) }.to( + raise_error(Bambora::InvalidAuthenticationError), + ) + end + end + + context 'when the API request is authenticated' do + it 'returns merchant data for all merchants' do + WebMock + .stub_request(:get, 'https://api.na.bambora.com/v1/reports/merchants') + .with( + headers: { + 'Authorization' => 'Passcode MzcyMTEwMDAwOjI4ODQwQ0VCOUg5RDNERkMyNEE0NDVCNzQ2MUZEOEZH', + 'Content-Type' => 'application/json', + }, + ) + .to_return( + headers: { + 'Content-Type' => 'application/json', + }, + body: { + data: [ + [ + { + merchant_id: 372_110_001, + merchant_name: 'ABC Business', + website: '', + address: { + street_address: '700 Main Street', + province: 'New York', + country: 'United States', + postal_code: '82210', + }, + merchant_status: { + status: 'Active', + state: 'Live', + live_date: '2024-01-01', + created_date: '2024-01-01', + authorized_date: '2024-01-05', + temp_disabled_date: '0001-01-01', + last_enabled_date: '0001-01-01', + disabled_date: '0001-01-01', + }, + processor: { + processor_name: 'First Data', + currency: 'USD', + }, + batch: { + batch_limit: 100_000.0, + batch_line_limit: 100_000.0, + eft_credit_lag: 3, + }, + settlement: { + credit_lag: 0, + }, + cards: { + mastercard_enabled: false, + visa_enabled: false, + amex_enabled: false, + discover_enabled: false, + diners_enabled: false, + jcb_enabled: false, + mastercard_debit_enabled: false, + visa_debit_enabled: false, + }, + features: { + checkout_enabled: true, + payment_profile_enabled: false, + recurring_billing_enabled: true, + credit_card_batch_enabled: false, + eft_ach_enabled: true, + interac_online_enabled: false, + visa_src_enabled: false, + }, + }, + { + merchant_id: 372_110_002, + merchant_name: 'XYZ Business', + website: '', + address: { + street_address: '769 1st Street', + province: 'New York', + country: 'United States', + postal_code: '54493', + }, + merchant_status: { + status: 'Active', + state: 'Live', + live_date: '2024-02-01', + created_date: '2024-02-01', + authorized_date: '2024-02-05', + temp_disabled_date: '0001-01-01', + last_enabled_date: '0001-01-01', + disabled_date: '0001-01-01', + }, + processor: { + processor_name: 'First Data', + currency: 'USD', + }, + batch: { + batch_limit: 100_000.0, + batch_line_limit: 100_000.0, + eft_credit_lag: 3, + }, + settlement: { + credit_lag: 0, + }, + cards: { + mastercard_enabled: false, + visa_enabled: false, + amex_enabled: false, + discover_enabled: false, + diners_enabled: false, + jcb_enabled: false, + mastercard_debit_enabled: false, + visa_debit_enabled: false, + }, + features: { + checkout_enabled: true, + payment_profile_enabled: false, + recurring_billing_enabled: true, + credit_card_batch_enabled: false, + eft_ach_enabled: true, + interac_online_enabled: false, + visa_src_enabled: false, + }, + }, + ], + ], + }.to_json, + ) + + credentials = Bambora::Credentials.new( + merchant_id: '372110000', + reporting_passcode: '28840CEB9H9D3DFC24A445B7461FD8FG', + ) + + merchants = described_class.get_all(credentials: credentials) + + expect(merchants[:data][0][0][:merchant_id]).to eq(372_110_001) + expect(merchants[:data][0][0][:merchant_name]).to eq('ABC Business') + expect(merchants[:data][0][1][:merchant_id]).to eq(372_110_002) + expect(merchants[:data][0][1][:merchant_name]).to eq('XYZ Business') + end + end + end +end