diff --git a/.rubocop.yml b/.rubocop.yml index a3866f8..c266e78 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -46,7 +46,7 @@ Metrics/ModuleLength: Enabled: false Metrics/PerceivedComplexity: - Max: 9 + Max: 10 Naming/AccessorMethodName: diff --git a/README.md b/README.md index ce4d73a..21e1e44 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,16 @@ response = gateway.status(transaction_id: 12345) See `Buckaruby::StatusResponse` for more details. +### Generate + +TODO: describe generate / iDEAL QR + +``` +response = gateway.generate(service: Buckaruby::Service::IDEAL_QR, description: "Kentaa", amount: "12.50", purchase_id: "12345", expires_at: Date.today + 30, image_size: 250) +``` + +See `Buckaruby::DataResponse` for more details. + ### Merchant variables You can send custom variables and additional variables with each request. diff --git a/lib/buckaruby.rb b/lib/buckaruby.rb index 43c2311..a13828a 100644 --- a/lib/buckaruby.rb +++ b/lib/buckaruby.rb @@ -6,6 +6,7 @@ require_relative 'buckaruby/language' require_relative 'buckaruby/operation' require_relative 'buckaruby/payment_method' +require_relative 'buckaruby/service' require_relative 'buckaruby/transaction_status' require_relative 'buckaruby/transaction_type' diff --git a/lib/buckaruby/action.rb b/lib/buckaruby/action.rb index b8cd455..afda16d 100644 --- a/lib/buckaruby/action.rb +++ b/lib/buckaruby/action.rb @@ -6,5 +6,6 @@ module Action PAY_RECURRENT = "PayRecurrent" REFUND = "Refund" EXTRA_INFO = "ExtraInfo" + GENERATE = "Generate" end end diff --git a/lib/buckaruby/gateway.rb b/lib/buckaruby/gateway.rb index 08723d9..6633857 100644 --- a/lib/buckaruby/gateway.rb +++ b/lib/buckaruby/gateway.rb @@ -40,6 +40,15 @@ def issuers(payment_method) Ideal::ISSUERS end + # New generate data request. + def generate(options = {}) + @logger.debug("[generate] options=#{options.inspect}") + + validate_generate_params!(options) + + execute_request(:data, options) + end + # Setup a new transaction. def setup_transaction(options = {}) @logger.debug("[setup_transaction] options=#{options.inspect}") @@ -154,6 +163,26 @@ def validate_required_params!(params, *required) end end + # Validate params for generate request. + def validate_generate_params!(options) + required_params = [:service] + + if options[:service] == Service::IDEAL_QR + required_params << [:amount, :description, :purchase_id, :expires_at] + required_params << [:minimum_amount, :maximum_amount] if options.key?(:amount_changeable) + end + + validate_required_params!(options, required_params) + + if options[:service] == Service::IDEAL_QR + validate_amount!(options) + + # TODO: validate minimum/maximum amount + end + + validate_service!(options, Service::IDEAL_QR) + end + # Validate params for setup transaction. def validate_setup_transaction_params!(options) required_params = [:amount, :payment_method, :invoicenumber] @@ -195,6 +224,13 @@ def validate_payment_method!(options, valid) end end + # Validate the service. + def validate_service!(options, valid) + unless valid.include?(options[:service]) + raise ArgumentError, "Invalid service: #{options[:service]}" + end + end + # Validate the payment issuer when iDEAL is selected as payment method. def validate_payment_issuer!(options) if options[:payment_method] == PaymentMethod::IDEAL || options[:payment_method] == PaymentMethod::IDEAL_PROCESSING @@ -264,6 +300,8 @@ def execute_request(request_type, options = {}) StatusResponse.new(response, config) when :cancel CancelResponse.new(response, config) + when :data + DataResponse.new(response, config) end end @@ -284,6 +322,8 @@ def build_request(request_type) StatusRequest.new(config) when :cancel CancelRequest.new(config) + when :data + DataRequest.new(config) end end end diff --git a/lib/buckaruby/operation.rb b/lib/buckaruby/operation.rb index 03e6fc8..9c57f77 100644 --- a/lib/buckaruby/operation.rb +++ b/lib/buckaruby/operation.rb @@ -2,10 +2,11 @@ module Buckaruby module Operation + CANCEL_TRANSACTION = "CancelTransaction" + DATA_REQUEST = "DataRequest" + REFUND_INFO = "RefundInfo" TRANSACTION_REQUEST = "TransactionRequest" - TRANSACTION_STATUS = "TransactionStatus" TRANSACTION_REQUEST_SPECIFICATION = "TransactionRequestSpecification" - REFUND_INFO = "RefundInfo" - CANCEL_TRANSACTION = "CancelTransaction" + TRANSACTION_STATUS = "TransactionStatus" end end diff --git a/lib/buckaruby/request.rb b/lib/buckaruby/request.rb index 62146c1..46e9a10 100644 --- a/lib/buckaruby/request.rb +++ b/lib/buckaruby/request.rb @@ -267,4 +267,60 @@ def build_request_params(options) params end end + + # Request for a "Data request". + class DataRequest < Request + def execute(options) + super(options.merge(operation: Operation::DATA_REQUEST)) + end + + def build_request_params(options) + params = { brq_payment_method: options[:service] } + + case options[:service] + when Service::IDEAL_QR + params.merge!(build_idealqr_params(options)) + end + + params + end + + private + + def build_idealqr_params(options) + params = { + brq_service_idealqr_action: Action::GENERATE, + brq_service_idealqr_description: options[:description], + brq_service_idealqr_amount: BigDecimal(options[:amount].to_s).to_s("F"), + brq_service_idealqr_purchaseid: options[:purchase_id], + brq_service_idealqr_expiration: options[:expires_at].strftime("%Y-%m-%d") + } + + # These parameters are mandatory, but we provide a default value. + if options.key?(:oneoff) + params[:brq_service_idealqr_isoneoff] = options[:oneoff] + else + params[:brq_service_idealqr_isoneoff] = false + end + + if options.key?(:amount_changeable) + params[:brq_service_idealqr_amountischangeable] = options[:amount_changeable] + else + params[:brq_service_idealqr_amountischangeable] = false + end + + if options.key?(:image_size) + params[:brq_service_idealqr_imagesize] = options[:image_size] + else + params[:brq_service_idealqr_imagesize] = 100 + end + + # Optional parameters. + params[:brq_service_idealqr_isprocessing] = options[:processing] if options.key?(:processing) + params[:brq_service_idealqr_minamount] = options[:minimum_amount] if options.key?(:minimum_amount) + params[:brq_service_idealqr_maxamount] = options[:maximum_amount] if options.key?(:maximum_amount) + + params + end + end end diff --git a/lib/buckaruby/response.rb b/lib/buckaruby/response.rb index 57369f1..afa0394 100644 --- a/lib/buckaruby/response.rb +++ b/lib/buckaruby/response.rb @@ -365,4 +365,21 @@ def custom_parameters @custom_parameters ||= FieldMapper.map_fields(params, :brq_customparameters) end end + + # Response for a data request. + class DataResponse < ApiResponse + def data_request + params[:brq_datarequest] + end + + def service + params[:brq_primary_service].downcase + end + + def qr_image_url + if service == Service::IDEAL_QR + params[:brq_service_idealqr_qrimageurl] + end + end + end end diff --git a/lib/buckaruby/service.rb b/lib/buckaruby/service.rb new file mode 100644 index 0000000..53dab0f --- /dev/null +++ b/lib/buckaruby/service.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Buckaruby + module Service + IDEAL_QR = "idealqr" + end +end diff --git a/spec/buckaruby/gateway_spec.rb b/spec/buckaruby/gateway_spec.rb index c7b31f1..819b1af 100644 --- a/spec/buckaruby/gateway_spec.rb +++ b/spec/buckaruby/gateway_spec.rb @@ -753,4 +753,48 @@ end end end + + describe '#generate' do + before do + stub_request(:post, "https://testcheckout.buckaroo.nl/nvp/?op=DataRequest").to_return(body: File.read("spec/fixtures/responses/generate_idealqr_success.txt")) + end + + it 'raises an exception when parameters are missing' do + expect { + subject.generate + }.to raise_error(ArgumentError) + + expect { + subject.generate(service: Buckaruby::Service::IDEAL_QR) + }.to raise_error(ArgumentError) + + expect { + subject.generate(service: Buckaruby::Service::IDEAL_QR, amount: 10) + }.to raise_error(ArgumentError) + + expect { + subject.generate(service: Buckaruby::Service::IDEAL_QR, amount: 10, description: "test") + }.to raise_error(ArgumentError) + + expect { + subject.generate(service: Buckaruby::Service::IDEAL_QR, amount: 10, description: "test", purchase_id: "12345") + }.to raise_error(ArgumentError) + end + + it 'raises an exception when initiating a data request with invalid service' do + expect { + subject.generate(service: "abc") + }.to raise_error(ArgumentError) + end + + it 'initiates a data request for iDEAL QR' do + response = subject.generate(service: Buckaruby::Service::IDEAL_QR, amount: 10, description: "test", purchase_id: "12345", expires_at: Date.new(2018, 1, 1)) + expect(response).to be_an_instance_of(Buckaruby::DataResponse) + expect(response.status).to eq(Buckaruby::TransactionStatus::SUCCESS) + expect(response.timestamp).to be_an_instance_of(Time) + expect(response.data_request).to eq("41C48B55FA9164E123CC73B1157459E840BE5D24") + expect(response.service).to eq(Buckaruby::Service::IDEAL_QR) + expect(response.qr_image_url).to eq("https://qrcode.ideal.nl/qrcode/12345678-1234-1234-1234-123456789012.png") + end + end end diff --git a/spec/fixtures/responses/generate_idealqr_success.txt b/spec/fixtures/responses/generate_idealqr_success.txt new file mode 100644 index 0000000..4f16e34 --- /dev/null +++ b/spec/fixtures/responses/generate_idealqr_success.txt @@ -0,0 +1 @@ +BRQ_APIRESULT=Success&BRQ_DATAREQUEST=41C48B55FA9164E123CC73B1157459E840BE5D24&BRQ_PRIMARY_SERVICE=IdealQr&BRQ_SERVICE_IDEALQR_QRIMAGEURL=https%3A%2F%2Fqrcode.ideal.nl%2Fqrcode%2F12345678-1234-1234-1234-123456789012.png&BRQ_STATUSCODE=190&BRQ_STATUSCODE_DETAIL=S001&BRQ_STATUSMESSAGE=Transaction+successfully+processed&BRQ_TEST=true&BRQ_TIMESTAMP=2018-02-20+13%3A01%3A27&BRQ_WEBSITEKEY=12345678&BRQ_SIGNATURE=9c91f47ecae81fc4d1ab39c8776daacef5bf38b3 \ No newline at end of file