From ef8b468214d2917b90f0537130f14c217ea71224 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Mon, 22 Apr 2019 22:09:39 -0400 Subject: [PATCH] Tokenization processing, refinements, tests --- lib/Business/OnlinePayment/Bambora.pm | 117 ++++++----- t/031-payments-card-normal_authorization.t | 107 +++++----- ...payments-card-pre-authorization-complete-void.t | 232 ++++++++++----------- t/041-tokenize-card.t | 167 ++++++++++----- t/051-tokenize-fail.t | 47 +++++ t/052-action-fail.t | 48 +++++ t/TestFixtures.pm | 176 +++++++++++++++- t/junk.t | 0 8 files changed, 602 insertions(+), 292 deletions(-) create mode 100755 t/051-tokenize-fail.t create mode 100755 t/052-action-fail.t delete mode 100755 t/junk.t diff --git a/lib/Business/OnlinePayment/Bambora.pm b/lib/Business/OnlinePayment/Bambora.pm index f0c7916..ea1a698 100755 --- a/lib/Business/OnlinePayment/Bambora.pm +++ b/lib/Business/OnlinePayment/Bambora.pm @@ -79,9 +79,10 @@ sub submit { my $method = $action_dispatch_table{$action}; - $self->submit_action_unsupported() - unless $method - && $self->can($method); + unless ( $method && $self->can($method) ) { + warn $self->error_message( "Action is unsupported ($action)" ); + return $self->is_success(0); + } $self->$method(@_); } @@ -116,33 +117,49 @@ sub submit_normal_authorization { my %post = ( order_number => $self->truncate( $content->{invoice_number}, 30 ), amount => $content->{amount}, - billing => $self->jhref_billing_address, ); - # Credit Card - if ( $content->{card_number} ) { + if ( + $content->{card_token} + || ( $content->{card_number} && $content->{card_number} =~ /^99\d{14}$/ ) + ) { + # Process payment against a stored Payment Profile, whose + # customer_code is used as the card_token + + my $card_token = $content->{card_token} || $content->{card_number}; + + unless ( $card_token =~ /^99\d{14}$/ ) { + $self->error_message( + "Invalid card_token($card_token): Expected 16-digit " + . " beginning with 99" + ); + return $self->is_success(0); + } + + $post{payment_method} = 'payment_profile'; + + $post{payment_profile} = { + customer_code => $card_token, + card_id => 1, + }; + + } elsif ( $content->{card_number} ) { + $post{payment_method} = 'card'; # Add card payment details to %post $post{card} = $self->jhref_card; return if $self->error_message; + # Add billing address to card + $post{billing} = $self->jhref_billing_address; + # Designate recurring payment label $post{card}->{recurring_payment} = $content->{recurring_payment} ? 1 : 0; # Direct API to issue a complete auth, instead of pre-auth $post{card}->{complete} = 1; - # $post{card} = { - # number => $self->truncate( $content->{card_number}, 20 ), - # name => $self->truncate( $content->{owner}, 64 ), - # expiry_month => sprintf( '%02d', $content->{expiry_month} ), - # expiry_year => sprintf( '%02d', $content->{expiry_year} ), - # cvd => $content->{cvv2}, - # recurring_payment => $content->{recurring_payment} ? 1 : 0, - # complete => 1, - # }; - } else { croak 'unknown/unsupported payment method!'; } @@ -156,7 +173,13 @@ sub submit_normal_authorization { } elsif ( $action eq 'authorization only' ) { # Perform pre-authorization $self->path('/v1/payments'); - $post{card}->{complete} = 0; + + # Set the 'complete' flag to false, directing API to perform pre-auth + if ( ref $post{payment_profile} ) { + $post{payment_profile}->{complete} = 0; + } elsif ( ref $post{card} ) { + $post{card}->{complete} = 0; + } } elsif ( $action eq 'post authorization' ) { # Complete a pre-authorization @@ -279,27 +302,33 @@ sub submit_void { ); my $post_body = encode_json( \%post ); + $self->path( sprintf '/v1/payments/%s/returns', $content->{order_number} ); if ( $DEBUG ) { warn Dumper({ + path => $self->path, post => \%post, post_body => $post_body, }); } - $self->path( sprintf '/v1/payments/%s/returns', $content->{order_number} ); my $response = $self->submit_api_request( $post_body ); + return if $self->error_message; + + $self->is_success(1); + + $response; } =head2 submit_tokenize Bambora tokenization is based on the Payment Profile feature of their API. -The token created by this method represnets the Bambora customer_code for the +The token created by this method represents the Bambora customer_code for the Payment Profile. The token resembles a credit card number. It is 16 digits long, beginning with 99. No valid card number can begin with the digits 99. -This method creates the payment profile, then replaces the customer_code -generated by Bambora with the card number resembling token. +This method creates the payment profile and reports the customer_code +as the card_token =cut @@ -420,8 +449,9 @@ sub submit_api_request { } $self->response_decoded( $response ); - # Response returned an error if ( $response->{code} && $response->{code} != 1 ) { + # Response returned an error + $self->is_success( 0 ); $self->result_code( $response->{code} ); @@ -438,16 +468,6 @@ sub submit_api_request { return $response; } -=head2 submit_action_unsupported - -Croak with the error message Action $action unsupported - -=cut - -sub submit_action_unsupported { - croak sprintf 'Action %s unsupported', shift->{_content}{action} -} - =head2 authorization_header Bambora REST requests authenticate via a HTTP header of the format: @@ -485,9 +505,9 @@ representing the RequestBillingAddress for the API sub jhref_billing_address { my $self = shift; - $self->set_province; + $self->parse_province; $self->set_country; - $self->set_phone_number; + $self->parse_phone_number; my $content = $self->{_content}; @@ -636,28 +656,7 @@ sub set_expiration { ); } -=head2 set_payment_method - -Set payment_method value to one of the following strings - - card - token - payment_profile - cash - cheque - interac - apple_pay - android_pay - -=cut - -sub set_payment_method { - # todo - determine correct payment method - warn "set_payment_method() STUB FUNCTION ALWAYS RETURNS card!\n"; - shift->{_content}->{payment_method} = 'card'; -} - -=head2 set_phone_number +=head2 parse_phone_number Set value for field phone_number, from value in field phone @@ -666,7 +665,7 @@ characters =cut -sub set_phone_number { +sub parse_phone_number { my $self = shift; my $content = $self->{_content}; @@ -677,7 +676,7 @@ sub set_phone_number { $content->{phone_number} = $phone; } -=head2 set_province +=head2 parse_province Set value for field province, from value in field state @@ -687,7 +686,7 @@ formatted to upper case, and truncated to 2 characters. =cut -sub set_province { +sub parse_province { my $self = shift; my $content = $self->{_content}; my $country = uc $content->{country}; diff --git a/t/031-payments-card-normal_authorization.t b/t/031-payments-card-normal_authorization.t index f4eb89b..2bb260a 100755 --- a/t/031-payments-card-normal_authorization.t +++ b/t/031-payments-card-normal_authorization.t @@ -4,7 +4,7 @@ use warnings; use Test::More; use lib 't'; -require 'TestFixtures.pm'; +use TestFixtures; use Business::OnlinePayment; my $merchant_id = $ENV{BAMBORA_MERCHANT_ID}; @@ -15,24 +15,12 @@ SKIP: { unless $merchant_id && $api_key; my %content = ( - login => $merchant_id, - password => $api_key, - action => 'Normal Authorization', - amount => '9.99', - - owner => 'Freeside Internet Services', - name => 'Mitch Jackson', - address => '1407 Graymalkin Lane', - city => 'Vancouver', - state => 'BC', - zip => '111 111', - country => 'CA', - - card_number => '4242424242424242', - cvv2 => '111', - expiration => '1122', - phone => '251-300-1300', - email => 'mitch@freeside.biz', + common_content(), + + login => $merchant_id, + password => $api_key, + + action => 'Normal Authorization', ); # Test approved card numbers, @@ -50,31 +38,42 @@ SKIP: { $content{card_number} = $approved_cards{$name}->{card}; $content{cvv2} = $approved_cards{$name}->{cvv2}; - my $tr; - ok( $tr = Business::OnlinePayment->new('Bambora'), 'Instantiatiate $tr' ); - ok( $tr->content( %content ), 'Set transaction content onto $tr' ); - { - local $@; - eval { $tr->submit }; - ok( !$@, "$name Process transaction (expect approve)" ); - } - - for my $attr (qw/ - is_success - message_id - authorization - order_number - txn_date - avs_code - /) { - ok( - defined $tr->$attr(), - sprintf '%s $tr->%s() = %s', - $name, - $attr, - $tr->$attr() - ); - } + my ( $tr, $response ) = make_api_request( \%content ); + + inspect_response( + $response, + { + amount => '9.99', + approved => 1, + auth_code => 'TEST', + message => 'Approved', + message_id => 1, + payment_method => 'CC', + type => 'P', + }, + [qw/ + card + created + order_number + risk_score + id + /], + ); + + inspect_transaction( + $tr, + { + is_success => 1, + authorization => 'TEST', + }, + [qw/ + message_id + order_number + txn_date + avs_code + /], + ); + } # Test declined card numbers, @@ -89,18 +88,18 @@ SKIP: { $content{card_number} = $decline_cards{$name}->{card}; $content{cvv2} = $decline_cards{$name}->{cvv2}; - my $tr; - ok( $tr = Business::OnlinePayment->new('Bambora'), 'Instantiate $tr' ); - ok( $tr->content( %content ), 'Set transaction content onto $tr' ); - { - local $@; - eval { $tr->submit }; - ok( !$@, "$name: Process transaction (expect decline)" ); - } + my ( $tr, $response ) = make_api_request( \%content ); - ok( $tr->is_success == 0, '$tr->is_success == 0' ); + inspect_transaction( + $tr, + { + is_success => 0, + }, + [qw/ + error_message + /], + ); ok( $tr->result_code != 1, '$tr->result_code != 1' ); - ok( $tr->error_message, '$tr->error_message: '.$tr->error_message ); } } diff --git a/t/032-payments-card-pre-authorization-complete-void.t b/t/032-payments-card-pre-authorization-complete-void.t index 7c575c9..863c8e1 100755 --- a/t/032-payments-card-pre-authorization-complete-void.t +++ b/t/032-payments-card-pre-authorization-complete-void.t @@ -4,131 +4,109 @@ use warnings; use Test::More; use lib 't'; -require 'TestFixtures.pm'; +use TestFixtures; use Business::OnlinePayment; my $merchant_id = $ENV{BAMBORA_MERCHANT_ID}; my $api_key = $ENV{BAMBORA_API_KEY}; SKIP: { - skip 'Missing env vars BAMBORA_MERCHANT_ID and BAMBORA_API_KEY', 32 + skip 'Missing env vars BAMBORA_MERCHANT_ID and BAMBORA_API_KEY', 56 unless $merchant_id && $api_key; my %content = ( - login => $merchant_id, - password => $api_key, - action => 'Authorization Only', - amount => '9.99', - - owner => 'Freeside Internet Services', - name => 'Mitch Jackson', - address => '1407 Graymalkin Lane', - city => 'Vancouver', - state => 'BC', - zip => '111 111', - country => 'CA', - - invoice_number => time(), - card_number => '4030000010001234', - cvv2 => '123', - expiration => '1122', - phone => '251-300-1300', - email => 'mitch@freeside.biz', - ); + common_content(), - my $tr; - ok( $tr = Business::OnlinePayment->new('Bambora'), 'Instantiatiate $tr' ); - ok( $tr->content( %content ), 'Set transaction content onto $tr' ); - { - local $@; - eval { $tr->submit }; - ok( !$@, "Submit pre-auth (expect approve)" ); - } - - my $response; - my %expect = ( - amount => '9.99', - approved => 1, - auth_code => 'TEST', - message => 'Approved', - message_id => 1, - payment_method => 'CC', - type => 'PA', - ); - my @expect = qw( - card - created - order_number - risk_score - id + login => $merchant_id, + password => $api_key, + + action => 'Authorization Only', ); - ok( $response = $tr->response_decoded, 'response_decoded' ); + # + # Process a pre-auth + # - for my $k ( keys %expect ) { - ok( - $response->{$k} eq $expect{$k}, - sprintf '$tr->%s == %s', $k, $expect{$k} - ); - } + my ( $tr, $response ) = make_api_request( \%content ); + + inspect_response( + $response, + { + amount => '9.99', + approved => 1, + auth_code => 'TEST', + message => 'Approved', + message_id => 1, + payment_method => 'CC', + type => 'PA', + }, + [qw/ + card + created + order_number + risk_score + id + /], + ); - for my $k ( @expect ) { - ok( - defined $response->{$k}, - sprintf '$r->%s (%s)', - $k, $response->{$k} - ); - } + inspect_transaction( + $tr, + { + is_success => 1, + }, + [qw/ + message_id + authorization + order_number + txn_date + avs_code + /], + ); - %content = ( + # + # Process a post-auth + # + + my %content_pa = ( %content, action => 'Post Authorization', order_number => $tr->order_number, amount => '8.99', # $1 Less than pre-auth ); - my $tr_pa; - ok( $tr_pa = Business::OnlinePayment->new('Bambora'), 'Instantiate $tr_pa' ); - ok( $tr_pa->content( %content ), 'Set transaction content onto $tr_pa' ); - { - local $@; - eval { $tr_pa->submit }; - ok( !$@, "Submit post-auth" ); - warn "Error: $@" if $@; - } - - %expect = ( - amount => '8.99', - approved => '1', - message => 'Approved', - message_id => '1', - type => 'PAC', + my ( $tr_pa, $response_pa ) = make_api_request( \%content_pa ); + + inspect_response( + $response_pa, + { + amount => '8.99', + approved => '1', + message => 'Approved', + message_id => '1', + type => 'PAC', + }, + [qw/ + authorizing_merchant_id + card + created + order_number + id + /], + ); + + inspect_transaction( + $tr_pa, + { + is_success => 1, + }, + [qw/ + message_id + authorization + order_number + txn_date + avs_code + /], ); - @expect = (qw/ - authorizing_merchant_id - card - created - order_number - id - /); - - my $response_pa; - ok( $response_pa = $tr_pa->response_decoded, 'response_decoded' ); - - for my $k ( keys %expect ) { - ok( - $response_pa->{$k} eq $expect{$k}, - sprintf '$tr->%s == %s', $k, $expect{$k} - ); - } - - for my $k ( @expect ) { - ok( - defined $response_pa->{$k}, - sprintf '$r->%s (%s)', - $k, $response_pa->{$k} - ); - } # # Void Transaction @@ -138,34 +116,36 @@ SKIP: { action => 'Void', login => $content{login}, password => $content{password}, + order_number => $tr_pa->order_number, amount => '8.99', ); - my $tr_void; - ok( $tr_void = Business::OnlinePayment->new('Bambora'), 'Instantiate $tr_void' ); - ok( $tr_void->content( %content_void ), 'Set transaction content onto $tr_void' ); - { - local $@; - eval { $tr_void->submit }; - ok( !$@, "Submit void" ); - warn "Error: $@" if $@; - } - - %expect = ( - amount => '8.99', - approved => '1', - message => 'Approved', - message_id => '1', - type => 'R', + my ( $tr_void, $response_void ) = make_api_request( \%content_void ); + + inspect_response( + $response_void, + { + amount => '8.99', + approved => '1', + message => 'Approved', + message_id => '1', + type => 'R', + }, + [qw/ + authorizing_merchant_id + card + created + order_number + id + /], + ); + + inspect_transaction( + $tr_void, + { is_success => 1 }, + [], ); - @expect = (qw/ - authorizing_merchant_id - card - created - order_number - id - /); } diff --git a/t/041-tokenize-card.t b/t/041-tokenize-card.t index f8a1292..161030c 100755 --- a/t/041-tokenize-card.t +++ b/t/041-tokenize-card.t @@ -4,79 +4,142 @@ use warnings; use Test::More; use lib 't'; -require 'TestFixtures.pm'; +use TestFixtures; use Business::OnlinePayment; +use Data::Dumper; + $Data::Dumper::Sortkeys = 1; + $Data::Dumper::Indent = 1; my $merchant_id = $ENV{BAMBORA_MERCHANT_ID}; my $api_key = $ENV{BAMBORA_API_KEY}; SKIP: { - skip 'Missing env vars BAMBORA_MERCHANT_ID and BAMBORA_API_KEY', 32 + skip 'Missing env vars BAMBORA_MERCHANT_ID and BAMBORA_API_KEY', 36 unless $merchant_id && $api_key; my %content = ( - login => $merchant_id, - password => $api_key, - action => 'Tokenize', - amount => '9.99', - - owner => 'Freeside Internet', - name => 'Mitch Jackson', - address => '1407 Graymalkin Lane', - city => 'Vancouver', - state => 'BC', - zip => '111 111', - country => 'CA', - - invoice_number => time(), - card_number => '4030000010001234', - cvv2 => '123', - expiration => '1122', - phone => '251-300-1300', - email => 'mitch@freeside.biz', - ); - - my $tr; - ok( $tr = Business::OnlinePayment->new('Bambora'), 'Instantiatiate $tr' ); - ok( $tr->content( %content ), 'Set transaction content onto $tr' ); - { - local $@; - eval { $tr->submit }; - ok( !$@, "Submit request to create Payment Profile (tokenize)" ); - } + common_content(), - my $response; + login => $merchant_id, + password => $api_key, - my %expect = ( - code => 1, - message => 'Operation Successful', - ); - my @expect = qw( - customer_code + action => 'Tokenize', ); - ok( $response = $tr->response_decoded, 'response_decoded' ); + # + # Create a payment profile with Tokenize + # - for my $k ( keys %expect ) { - ok( - $response->{$k} eq $expect{$k}, - sprintf '$tr->%s == %s', $k, $expect{$k} - ); - } + my ( $tr, $response ) = make_api_request( \%content ); - for my $k ( @expect ) { - ok( - defined $response->{$k}, - sprintf '$r->%s (%s)', - $k, $response->{$k} - ); - } + inspect_response( + $response, + { + code => 1, + message => 'Operation Successful', + }, + [qw/ customer_code /], + ); ok( $response->{customer_code} eq $tr->card_token, '$tr->card_token eq $response->{customer_code}' ); + # + # Create a charge against the payment profile + # with the token set as 'card_number' + # + + my %content_ch1 = ( + %content, + action => 'Normal Authorization', + card_number => $tr->card_token, + amount => '2.95', + ); + + my ( $tr_ch1, $response_ch1 ) = make_api_request( \%content_ch1 ); + + # warn Dumper({ + # response_ch1 => $response_ch1, + # }); + + inspect_response( + $response_ch1, + { + amount => $content_ch1{amount}, + approved => 1, + auth_code => 'TEST', + authorizing_merchant_id => $content{login}, + message => 'Approved', + payment_method => 'CC', + type => 'P', + }, + [qw/ + card + created + order_number + /], + ); + + + # + # Create a charge against the payment profile + # with the token set as 'card_token' + # + + my %content_ch2 = ( + login => $content{login}, + password => $content{password}, + action => 'Normal Authorization', + #card_token => '9915559773829941', + card_token => $tr->card_token, + amount => '7.77', + ); + + my ( $tr_ch2, $response_ch2 ) = make_api_request( \%content_ch2 ); + + # warn Dumper({ + # response_chs => $response_ch2 + # }); + + inspect_response( + $response_ch2, + { + amount => $content_ch2{amount}, + approved => 1, + auth_code => 'TEST', + authorizing_merchant_id => $content{login}, + message => 'Approved', + payment_method => 'CC', + type => 'P', + }, + [qw/ + card + created + order_number + /], + ); + + # + # Attempt charge with a normal credit card number as card_token + # Expect fail + # + + my %content_fail = ( + %content_ch2, + card_token => '4242424242424242', + amount => '24.95', + ); + + my ( $tr_fail, $response_fail ) = make_api_request( \%content_fail ); + + inspect_transaction( + $tr_fail, + { is_success => 0 }, + [qw/ error_message /], + ); + } done_testing; \ No newline at end of file diff --git a/t/051-tokenize-fail.t b/t/051-tokenize-fail.t new file mode 100755 index 0000000..61bafb4 --- /dev/null +++ b/t/051-tokenize-fail.t @@ -0,0 +1,47 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use Test::More; + +use lib 't'; +use TestFixtures; +use Business::OnlinePayment; +use Data::Dumper; + $Data::Dumper::Sortkeys = 1; + $Data::Dumper::Indent = 1; + +my $merchant_id = $ENV{BAMBORA_MERCHANT_ID}; +my $api_key = $ENV{BAMBORA_API_KEY}; + +SKIP: { + skip 'Missing env vars BAMBORA_MERCHANT_ID and BAMBORA_API_KEY', 36 + unless $merchant_id && $api_key; + + # + # Attempt charge with a normal credit card number as card_token + # Expect fail + # + + my %content_fail = ( + login => $merchant_id, + password => $api_key, + + action => 'Normal Authorization', + card_token => '4242424242424242', + amount => '24.95', + ); + + my ( $tr_fail, $response_fail ) = make_api_request( \%content_fail ); + + inspect_transaction( + $tr_fail, + { is_success => 0 }, + [qw/ error_message /], + ); + + ok( $tr_fail->error_message =~ /invalid card_token/i, + 'Saw expected error_message' + ); +} + +done_testing; \ No newline at end of file diff --git a/t/052-action-fail.t b/t/052-action-fail.t new file mode 100755 index 0000000..7c4f08a --- /dev/null +++ b/t/052-action-fail.t @@ -0,0 +1,48 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use Test::More; + +use lib 't'; +use TestFixtures; +use Business::OnlinePayment; +use Data::Dumper; + $Data::Dumper::Sortkeys = 1; + $Data::Dumper::Indent = 1; + +my $merchant_id = $ENV{BAMBORA_MERCHANT_ID}; +my $api_key = $ENV{BAMBORA_API_KEY}; + +SKIP: { + skip 'Missing env vars BAMBORA_MERCHANT_ID and BAMBORA_API_KEY', 6 + unless $merchant_id && $api_key; + + # + # Attempt with invalid action name + # Expect fail + # + + my %content_fail = ( + common_content(), + + login => $merchant_id, + password => $api_key, + + action => 'Norml Authorzatin', + amount => '24.95', + ); + + my ( $tr_fail, $response_fail ) = make_api_request( \%content_fail ); + + inspect_transaction( + $tr_fail, + { is_success => 0 }, + [qw/ error_message /], + ); + + ok( $tr_fail->error_message =~ /action is unsupported/i, + 'Saw expected error_message' + ); +} + +done_testing; \ No newline at end of file diff --git a/t/TestFixtures.pm b/t/TestFixtures.pm index acf2138..947d0b3 100755 --- a/t/TestFixtures.pm +++ b/t/TestFixtures.pm @@ -7,8 +7,182 @@ use Exporter; use vars qw/ @ISA @EXPORT /; @ISA = 'Exporter'; @EXPORT = qw/ - + common_content + inspect_response + inspect_transaction + make_api_request /; +use Business::OnlinePayment; +use Data::Dumper; +use Test::More; + +=head1 NAME + +TestFixtures + +=head1 DESCRIPTION + +Common helper methods for all test units + +=head1 BAMBORA DEVELOPER ACCOUNT + +Do not use live credentials with these test units. Bambora +provides no way to specifiy a test payment gateway. They issue +test accounts instead. See L + +=head1 USAGE + + # set environment variables with credentials + export BAMBORA_MERCHANT_ID=8675309 + export BAMBORA_API_KEY=XOXOXOXOXOXOXOX + + # run all tests + prove -lv t + + # run a single test + prove -lv t/031-payments-card-normal-authorizaiton.t + +=head1 FUNCTIONS + +=head2 common_content + +A basic Business::OnlinePayment content hash, containing a +valid Bambora test card number, with Bambora's specified +correct billing address for their test cards + +See L + +=cut + +sub common_content { + ( + #action => 'Normal Authorization', + + amount => '9.99', + + owner => 'Business OnlinePayment', + name => 'Mitch Jackson', + address => '1407 Graymalkin Lane', + city => 'Vancouver', + state => 'BC', + zip => '111 111', + country => 'CA', + + invoice_number => time(), + card_number => '4030000010001234', + cvv2 => '123', + expiration => '1122', + phone => '251-300-1300', + email => 'mitch@freeside.biz', + ) +} + +=head2 inspect_response $response, \%expect, \@expect + +Given $response, a decoded json api response, check the +response contains the keys/value defined in %expect, and +that response keys exist for keynames defined in @expect + +=cut + +sub inspect_response { + no warnings 'uninitialized'; + + my $response = shift; + my $expect_href = shift || {}; + my $expect_aref = shift || []; + + die 'Expected $response hashref parameter' + unless ref $response; + + for my $k ( keys %{$expect_href} ) { + ok( + $response->{$k} eq $expect_href->{$k}, + sprintf '$response->%s: %s eq %s', + $k, + $response->{$k}, + $expect_href->{$k} + ); + } + + for my $k ( @{$expect_aref} ) { + ok( + defined $response->{$k}, + sprintf '$response->%s defined: %s', + $k, $response->{$k} + ); + } +} + +=head2 inspect_transaction $transaction, \%expect, \@expect + +Given a B::OP $tr, call methods defined as keys within %expect, +and validate the returned values match the values in %expect. +Check the methods defined in @expect return true values + +=cut + +sub inspect_transaction { + no warnings 'uninitialized'; + my $tr = shift; + my $expect_href = shift || {}; + my $expect_aref = shift || []; + + die 'Expected $tr B::OP transaction parameter' + unless ref $tr; + + for my $k ( keys %{$expect_href} ) { + ok( + $tr->can($k) && $tr->$k() eq $expect_href->{$k}, + sprintf '$tr->%s: %s eq %s', + $k, + $tr->can($k) ? $tr->$k() : 'METHOD MISSING', + $expect_href->{$k} + ); + } + + for my $k ( @{$expect_aref} ) { + ok( + $tr->can($k) && defined $tr->$k(), + sprintf '$tr->%s defined: %s', + $k, + $tr->can($k) ? $tr->$k() : 'METHOD MISSING', + ); + } + +} + +=head2 make_api_request \%content + +Given a %content href, create a B::OP transaction and submit it + +Returns the transaction object, and the decoded json response + +=cut + +sub make_api_request { + my $content = shift; + die 'expected href' unless ref $content; + + my $tr; + ok( $tr = Business::OnlinePayment->new('Bambora'), 'Instantiatiate transaction' ); + ok( $tr->content( %$content ), 'Hand %content to transaction' ); + { + local $@; + eval { $tr->submit }; + ok( !$@, "Submit request to create Payment Profile, action: $content->{action}" ); + if ( $@ ) { + warn Dumper({ + content => $content, + error => $@, + }); + } + } + + my $response = $tr->response_decoded || {}; + + return ( $tr, $response ); +} 1; \ No newline at end of file diff --git a/t/junk.t b/t/junk.t deleted file mode 100755 index e69de29..0000000 -- 2.11.0