File Coverage

blib/lib/Business/Tax/Avalara.pm
Criterion Covered Total %
statement 36 181 19.8
branch 1 54 1.8
condition 3 29 10.3
subroutine 11 26 42.3
pod 6 6 100.0
total 57 296 19.2


line stmt bran cond sub pod time code
1             package Business::Tax::Avalara;
2              
3 6     6   108076 use 5.010;
  6         16  
4              
5 6     6   21 use strict;
  6         10  
  6         97  
6 6     6   18 use warnings;
  6         11  
  6         117  
7              
8 6     6   3212 use Try::Tiny;
  6         5946  
  6         266  
9 6     6   27 use Carp;
  6         8  
  6         209  
10 6     6   2751 use LWP;
  6         191434  
  6         183  
11 6     6   3024 use HTTP::Request::Common;
  6         10381  
  6         376  
12 6     6   3367 use Encode qw();
  6         47035  
  6         145  
13 6     6   2852 use Data::Dump;
  6         23586  
  6         338  
14 6     6   4586 use JSON::PP;
  6         58111  
  6         10390  
15              
16              
17             =head1 NAME
18              
19             Business::Tax::Avalara - An interface to Avalara's REST webservice
20              
21             =head1 SYNOPSYS
22              
23             use Business::Tax::Avalara;
24             my $avalara_gateway = Business::Tax::Avalara->new(
25             customer_code => $customer_code,
26             company_code => $company_code,
27             user_name => $user_name,
28             password => $password,
29             origin_address =>
30             {
31             line_1 => '1313 Mockingbird Lane',
32             postal_code => '98765',
33             },
34             );
35            
36             my $tax_results = $avalara_gateway->get_tax(
37             destination_address =>
38             {
39             line_1 => '42 Evergreen Terrace',
40             city => 'Springfield',
41             postal_code => '12345',
42             },
43             cart_lines =>
44             [
45             {
46             sku => '42ACE',
47             quantity => 1,
48             amount => '8.99',
49             },
50             {
51             sku => '9FCE2',
52             quantity => 2,
53             amount => '38.98',
54             }
55             ],
56            
57             );
58            
59              
60             =head1 DESCRIPTION
61              
62             Business::Tax::Avalara is a simple interface to Avalara's REST-based sales tax webservice.
63             It takes in a perl hash of data to send to Avalara, generates the JSON, fetches a response,
64             and converts that back into a perl hash structure.
65              
66             This module only supports the 'get_tax' method at the moment.
67              
68             =head1 VERSION
69              
70             Version 1.2.0
71              
72             =cut
73              
74             our $VERSION = '1.2.0';
75             our $AVALARA_REQUEST_SERVER = 'avatax.avalara.net';
76             our $AVALARA_DEVELOPMENT_REQUEST_SERVER = 'development.avalara.net';
77              
78            
79             =head1 FUNCTIONS
80              
81             =head2 new()
82              
83             Creates a new Business::Tax::Avalara object with various options that do not change
84             between requests.
85              
86             my $avalara_gateway = Business::Tax::Avalara->new(
87             customer_code => $customer_code,
88             company_code => $company_code,
89             user_name => $user_name
90             pasword => $password,
91             is_development => boolean (optional), default 0
92             origin_address => $origin_address (optional),
93             memcached => A Cache::Memcached or Cache::Memcached::Fast object.
94             request_timeout => Request timeout in seconds. Default is 3.
95             debug => 0,
96             );
97            
98             The fields customer_code, company_code, user_name, and password should be
99             provided by your Avalara representative. Account number and License key
100             are synonyms for user_name and password, respectively.
101              
102             is_development should be set to 1 to use the development URL, and 0 for
103             production uses.
104              
105             origin_address can either be set here, or passed into get_tax, depending on if
106             it changes per request, or if you're always shipping from the same location.
107             It is a hash ref, see below for formatting details.
108              
109             If a memcached object is passed in, we can use this so that we don't send the same
110             request over in a certain period of time. This combines below with 'cache_timespan'
111             and 'unique_key' in the get_tax() call.
112              
113             If debug is set to a true value, it will dump out the raw json messages being sent to
114             and coming back from Avalara.
115              
116             Returns a Business::Tax::Avalara object.
117              
118             =cut
119              
120             sub new
121             {
122 1     1 1 73945 my ( $class, %args ) = @_;
123            
124 1         5 my @required_fields = qw( customer_code company_code user_name password );
125 1         3 foreach my $required_field ( @required_fields )
126             {
127 4 50       10 if ( !defined $args{ $required_field } )
128             {
129 0         0 croak "Could not instantiate Business::Tax::Avalara module: Required field >$required_field< is missing.";
130             }
131             }
132            
133             my $self = {
134             customer_code => $args{'customer_code'},
135             company_code => $args{'company_code'},
136             is_development => $args{'is_development'} // 0,
137             user_name => $args{'user_name'},
138             password => $args{'password'},
139             origin_address => $args{'origin_address'},
140             request_timeout => $args{'request_timeout'} // 3,
141 1   50     26 debug => $args{'debug'} // 0,
      50        
      50        
142             };
143            
144 1         3 bless $self, $class;
145 1         4 return $self;
146             }
147              
148              
149             =head2 get_tax()
150              
151             Makes a JSON request using the 'get_tax' method, parses the response, and returns a perl hash.
152              
153             my $tax_results = $avalara_gateway->get_tax(
154             destination_address => $address_hash,
155             origin_address => $address_hash (may be specified in new),
156             document_date => $date (optional), default is current date
157             cart_lines => $cart_line_hash,
158             customer_usage_type => $customer_usage_type (optional),
159             discount => $order_level_discount (optional),
160             purchase_order_number => $purchase_order_number (optional),
161             exemption_number => $exemption_number (optional),
162             detail_level => $detail_level (optional), default 'Tax',
163             document_type => $document_type (optional), default 'SalesOrder'
164             document_code => $document_code (optional), a unique identifier
165             payment_date => $date (optional),
166             reference_code => $reference_code (optional),
167             commit => 1|0, # Default 0, whether this is a 'final' query.
168             unique_key => A unique key for memcache (optional, see below)
169             cache_timespan => The number of seconds to cache results (see below),
170             currency_code => 3 character ISO 4217 compliant currency code (optional),
171             );
172              
173              
174             If you are issuing a refund or credit memo for part of the order (for the full order
175             you may want to void the order using the C method), you may need to specify
176             the date the tax was originally calculated, and you need to specify the amounts as
177             negative values.
178              
179             my $tax_results = $avalara_gateway->get_tax(
180             document_type => 'ReturnInvoice',
181             document_code => $original_order_number (optional), or a unique identifier
182             document_date => $date (optional), default is current date
183             tax_override => {
184             reason => 'Return',
185             tax_override_type => 'TaxDate',
186             tax_date => 'YYYY-MM-DD', # Original date of the order.
187             },
188             # Just the lines being refunded.
189             cart_lines => [
190             {
191             sku => $sku_identifier,
192             quantity => $number_of_units_to_refund,
193             amount => $amount_to_refund, # Negative value.
194             },
195             ],
196             discount => $amount_to_decrease_refund (optional), # Negative value.
197             commit => 1|0, # Default 0, whether this is a 'final' query.
198             # Include other fields as needed.
199             );
200              
201             See below for the definitions of address and cart_line fields. The field origin_address
202             may be specified here if it changes between transactions, or in new if it's largely static.
203              
204             detail level is one of 'Tax', 'Summary', 'Document', 'Line', or 'Diagnostic'.
205             See the Avalara documentation for the distinctions.
206              
207             document_type is one of 'SalesOrder', 'SalesInvoice', 'PurchaseOrder', 'PurchaseInvoice',
208             'ReturnOrder', and 'ReturnInvoice'.
209              
210             document_code is optional, but highly recommended. If you do not include this,
211             Avalara will generate a new internal unique id for each request, and it does not
212             associate the commits to any queries you made along the way.
213              
214             If cache_timespan is set and you passed a memcached object into new(), it will attempt
215             to cache the result based on the unique key passed in.
216              
217             Returns a perl hashref based on the Avalara return.
218             See the Avalara documentation for the full description of the output, but the highlights are:
219              
220             {
221             ResultCode => 'Success',
222             TaxAddresses => [ array of address information ],
223             TaxDate => Date,
224             TaxLines =>
225             {
226             LineNumber => # The value of the line number
227             {
228             Discount => Discount,
229             LineNo => Line Number passed in,
230             Rate => Tax rate used,
231             Tax => Line item tax
232             Taxability => "true" or "false",
233             Taxable => Amount taxable,
234             TaxCalculated => Line item tax
235             TaxCode => Tax Code used in the calculation
236             Tax Details => Details about state, county, city components of the tax
237            
238             },
239             ...
240             },
241             Timestamp => Timestamp,
242             TotalAmount => Total amount before tax
243             TotalDiscount => Total Discount
244             TotalExemption => Total amount exempt
245             TotalTax => Tax for the whole order
246             TotalTaxable => Amount that's taxable
247             }
248             =cut
249              
250             sub get_tax
251             {
252 0     0 1   my ( $self, %args ) = @_;
253            
254 0           my $unique_key = delete $args{'unique_key'};
255 0           my $cache_timespan = delete $args{'cache_timespan'};
256            
257             # Perl output, aka a hash ref, as opposed to JSON.
258 0           my $tax_perl_output = undef;
259            
260 0 0         if ( defined( $cache_timespan ) )
261             {
262             # Cache event.
263 0           $tax_perl_output = $self->get_cache(
264             key => $unique_key,
265             );
266             }
267            
268 0 0         if ( !defined $tax_perl_output )
269             {
270             # It wasn't in the cache or we aren't using cache, go get it.
271             try
272             {
273 0     0     my $request_json = $self->_generate_request_json( %args );
274 0           my $result_json = $self->_make_request_json( $request_json );
275 0           $tax_perl_output = $self->_parse_response_json( $result_json );
276             }
277             catch
278             {
279 0     0     carp( "Failed to fetch Avalara tax information: ", $_ );
280 0           return;
281 0           };
282            
283 0 0         if ( defined( $cache_timespan ) )
284             {
285             # Set it in the cache.
286 0           $self->set_cache(
287             key => $unique_key,
288             value => $tax_perl_output,
289             expire_time => $cache_timespan,
290             );
291             }
292             }
293 0           return $tax_perl_output;
294             }
295              
296              
297             =head2 cancel_tax()
298              
299             Makes a JSON request using the 'cancel_tax' method, parses the response, and returns a perl hash.
300              
301             my $tax_results = $avalara_gateway->cancel_tax(
302             document_type => $document_type, default 'SalesOrder'
303             doc_code => $doc_code,
304             cancel_code => $cancel_code, default 'DocVoided',
305             doc_id => $doc_id,
306             );
307              
308             Either doc_id (which is Avalara's transaction ID returned from get_tax() )
309             or the combination of document_type, doc_coe, and doc_id are required.
310              
311             Returns a perl hashref based on the Avalara return.
312             See the Avalara documentation for the full description of the output, but the highlights are:
313              
314             'CancelTaxResult' =>
315             {
316             ResultCode => 'Success',
317             DocID => SomeDocID,
318             TransactionID => Avalara's ID,
319             }
320              
321             =cut
322              
323             sub cancel_tax
324             {
325 0     0 1   my ( $self, %args ) = @_;
326            
327 0   0       my $document_type = delete $args{'document_type'} // 'SalesOrder';
328 0           my $doc_code = delete $args{'doc_code'};
329 0   0       my $cancel_code = delete $args{'cancel_code'} // 'DocVoided';
330 0           my $doc_id = delete $args{'doc_id'};
331            
332             # We need either doc_id or doc_code and document_type and cancel_code.
333             # But there are defaults on document_type and cancel type, so really
334             # we just need doc_id or doc_code.
335 0 0 0       if ( !defined $doc_id && !defined $doc_code )
336             {
337 0           carp( "Either a doc_id, or the combination of doc_code, cancel_code, and document_type is required." );
338 0           return undef;
339             }
340              
341 0           my %request;
342 0 0         if ( defined $doc_id )
343             {
344 0           $request{'doc_id'} = $doc_id;
345             }
346             else
347             {
348 0           $request{'document_type'} = $document_type;
349 0           $request{'doc_code'} = $doc_code;
350 0           $request{'cancel_code'} = $cancel_code;
351             }
352            
353             my $cancel_output = try
354             {
355 0     0     my $request_json = $self->_generate_cancel_request_json( %request );
356 0           my $result_json = $self->_make_request_json( $request_json, 'cancel' );
357 0           return $self->_parse_response_json( $result_json );
358             }
359             catch
360             {
361 0     0     carp( "Failed to cancel Avalara tax record: ", $_ );
362 0           return undef;
363 0           };
364              
365 0           return $cancel_output;
366             }
367              
368              
369             =head1 INTERNAL FUNCTIONS
370              
371             =head2 _generate_request_json()
372              
373             Generates the json to send to Avalara's web service.
374              
375             Returns a JSON object.
376              
377             =cut
378              
379             sub _generate_request_json
380             {
381 0     0     my ( $self, %args ) = @_;
382            
383             # Add in all the required elements.
384 0           my @now = localtime();
385             my $doc_date = defined $args{'doc_date'}
386 0 0         ? $args{'doc_date'}
387             : sprintf( "%4d-%02d-%02d", $now[5] + 1900, $now[4] + 1, $now[3] );
388              
389             my $request =
390             {
391             DocDate => $doc_date,
392             CustomerCode => $self->{'customer_code'},
393             CompanyCode => $self->{'company_code'},
394 0 0 0       Commit => ( $args{'commit'} // 0 ) ? 'true' : 'false',
395             };
396            
397 0           $request->{'Addresses'} = [ $self->_generate_address_json( $args{'destination_address'}, 1 ) ];
398 0           push @{ $request->{'Addresses'} },
399 0   0       $self->_generate_address_json( $self->{'origin_address'} // $args{'origin_address'}, 2 );
400            
401 0           $request->{'Lines'} = [];
402            
403 0           my $counter = 1;
404 0           foreach my $cart_line ( @{ $args{'cart_lines'} } )
  0            
405             {
406 0           push @{ $request->{'Lines'} }, $self->_generate_cart_line_json( $cart_line, $counter );
  0            
407 0           $counter++;
408             }
409            
410 0           my %optional_nodes =
411             (
412             customer_usage_type => 'CustomerUsageType',
413             discount => 'Discount',
414             purchase_order_number => 'PurchaseOrderNo',
415             exemption_number => 'ExemptionNo',
416             detail_level => 'DetailLevel',
417             document_type => 'DocType',
418             payment_date => 'PaymentDate',
419             reference_code => 'ReferenceCode',
420             document_code => 'DocCode',
421             currency_code => 'CurrencyCode',
422             );
423            
424 0           foreach my $node_name ( keys %optional_nodes )
425             {
426 0 0         next if ( !defined $args{ $node_name } );
427 0           $request->{ $optional_nodes{ $node_name } } = $args{ $node_name };
428             }
429            
430 0           my %tax_override_nodes =
431             (
432             'reason' => 'Reason', # Typical reasons include: 'Return', 'Layaway', 'Imported'.
433             'tax_override_type' => 'TaxOverrideType', # None, TaxAmount, Exemption, or TaxDate.
434             'tax_date' => 'TaxDate', # Date the tax was calculated (if type is TaxDate).
435             'tax_amount' => 'TaxAmount', # The amount of tax to apply (if type is TaxAmount).
436             );
437              
438             # Fill in the TaxOverride values.
439 0 0         if ( defined $args{'tax_override'} )
440             {
441 0           foreach my $node ( keys %tax_override_nodes )
442             {
443 0 0         if ( defined $args{'tax_override'}{ $node } )
444             {
445 0           $request->{'TaxOverride'}{ $tax_override_nodes{ $node } } = $args{'tax_override'}{ $node };
446             }
447             }
448             }
449              
450 0           my $json = JSON::PP->new()->ascii()->pretty()->allow_nonref();
451 0           return $json->encode( $request );
452             }
453              
454              
455             =head2 _generate_cancel_request_json()
456              
457             Generates the json to cancel a tax request to Avalara's web service.
458              
459             Returns a JSON object.
460              
461             =cut
462              
463             sub _generate_cancel_request_json
464             {
465 0     0     my ( $self, %args ) = @_;
466            
467             my $request =
468             {
469 0           CompanyCode => $self->{'company_code'},
470             };
471            
472 0           my %optional_nodes =
473             (
474             document_type => 'DocType',
475             doc_code => 'DocCode',
476             cancel_code => 'CancelCode',
477             doc_id => 'DocId',
478             );
479            
480 0           foreach my $node_name ( keys %optional_nodes )
481             {
482 0 0         next if ( !defined $args{ $node_name } );
483 0           $request->{ $optional_nodes{ $node_name } } = $args{ $node_name };
484             }
485              
486 0           my $json = JSON::PP->new()->ascii()->pretty()->allow_nonref();
487 0           return $json->encode( $request );
488             }
489              
490              
491             =head2 _generate_address_json()
492              
493             Given an address hashref, generates and returns a data structure to be converted to JSON.
494              
495             An address hashref is defined as:
496              
497             my $address = {
498             line_1 => $first_line_of_address,
499             line_2 => $second_line_of_address,
500             line_3 => $third_line_of_address,
501             city => $city,
502             region => $state_or_province,
503             country => $iso_2_code,
504             postal_code => $postal_or_ZIP_code,
505             latitude => $latitude,
506             longitude => $longitude,
507             tax_region_id => $tax_region_id,
508             };
509            
510             All fields are optional, though without enough to identify an address, your results will
511             be less than satisfying.
512              
513             Country coes are ISO 3166-1 (alpha 2) format, such as 'US'.
514              
515             =cut
516              
517             sub _generate_address_json
518             {
519 0     0     my ( $self, $address, $address_code ) = @_;
520            
521 0           my $address_request = {};
522            
523             # Address code is just an internal identifier. In this module, 1 is destination, 2 is origin.
524 0           $address_request->{'AddressCode'} = $address_code;
525            
526 0           my %nodes =
527             (
528             'line_1' => 'Line1',
529             'line_2' => 'Line2',
530             'line_3' => 'Line3',
531             'city' => 'City',
532             'region' => 'Region',
533             'country' => 'Country',
534             'postal_code' => 'PostalCode',
535             'latitude' => 'Latitude',
536             'longitude' => 'Longitude',
537             'tax_region_id' => 'TaxRegionId',
538             );
539            
540 0           foreach my $node ( keys %nodes )
541             {
542 0 0         if ( defined $address->{ $node } )
543             {
544 0           $address_request->{ $nodes{ $node } } = $address->{ $node };
545             }
546             }
547            
548 0           return $address_request;
549             }
550              
551              
552             =head2 _generate_cart_line_json()
553              
554             Generates a data structure from a cart_line hashref. Cart lines are:
555              
556             my $cart_line = {
557             'line_number' => $number (optional, will be generated if omitted.),
558             'item_code' => $item_code
559             'sku' => $sku, # Use sku OR item_code
560             'tax_code' => $tax_code,
561             'customer_usage_type' => $customer_usage_code
562             'description' => $description,
563             'quantity' => $quantity,
564             'amount' => $amount, # Extended price, ie, price * quantity
565             'discounted' => $is_included_in_discount, # Boolean (True or False)
566             'tax_included' => $is_tax_included, # Boolean (True or False)
567             'ref_1' => $reference_1,
568             'ref_2' => $reference_2,
569             }
570            
571             One of item_code or sku, quantity, and amount are required fields.
572              
573             Customer usage type determines the type of item (sometimes called entity or use code). In some
574             states, different types of items have different tax rates.
575              
576             =cut
577              
578             sub _generate_cart_line_json
579             {
580 0     0     my ( $self, $cart_line, $counter ) = @_;
581            
582 0           my $cart_line_request = {};
583              
584 0   0       $cart_line_request->{'LineNo'} = $cart_line->{'line_number'} // $counter;
585            
586             # By convention, destionation is address 1, origin is address 2, in this module.
587             # It doesn't matter in the slightest, the labels just have to match.
588 0           $cart_line_request->{'DestinationCode'} = 1;
589 0           $cart_line_request->{'OriginCode'} = 2;
590            
591 0           my %nodes =
592             (
593             'item_code' => 'ItemCode',
594             'sku' => 'ItemCode', # Use sku OR item_code
595             'tax_code' => 'TaxCode',
596             'customer_usage_type' => 'CustomerUsageType',
597             'description' => 'Description',
598             'quantity' => 'Qty',
599             'amount' => 'Amount', # Extended price, ie, price * quantity
600             'discounted' => 'Discounted', # Boolean
601             'tax_included' => 'TaxIncluded', # Boolean
602             'ref_1' => 'Ref1',
603             'ref_2' => 'Ref2',
604             );
605              
606 0           foreach my $node ( keys %nodes )
607             {
608 0 0         if ( defined $cart_line->{ $node } )
609             {
610 0           $cart_line_request->{ $nodes{ $node } } = $cart_line->{ $node };
611             }
612             }
613              
614 0           my %tax_override_nodes =
615             (
616             'reason' => 'Reason', # Typical reasons include: 'Return', 'Layaway', 'Imported'.
617             'tax_override_type' => 'TaxOverrideType', # None, TaxAmount, Exemption, or TaxDate.
618             'tax_date' => 'TaxDate', # Date the tax was calculated (if type is TaxDate).
619             'tax_amount' => 'TaxAmount', # The amount of tax to apply (if type is TaxAmount).
620             );
621              
622             # Fill in the TaxOverride values.
623 0 0         if ( defined $cart_line->{'tax_override'} )
624             {
625 0           foreach my $node ( keys %tax_override_nodes )
626             {
627 0 0         if ( defined $cart_line->{'tax_override'}{ $node } )
628             {
629 0           $cart_line_request->{'TaxOverride'}{ $tax_override_nodes{ $node } } = $cart_line->{'tax_override'}{ $node };
630             }
631             }
632             }
633            
634 0           return $cart_line_request;
635             }
636              
637              
638             =head2 _make_request_json()
639              
640             Makes the https request to Avalara, and returns the response json.
641              
642             =cut
643              
644             sub _make_request_json
645             {
646 0     0     my ( $self, $request_json, $resource ) = @_;
647            
648 0   0       $resource //= 'get';
649            
650 0 0         my $request_server = $self->{'is_development'}
651             ? $AVALARA_DEVELOPMENT_REQUEST_SERVER
652             : $AVALARA_REQUEST_SERVER;
653 0           my $request_url = 'https://' . $request_server . '/1.0/tax/' . $resource;
654            
655             # Create a user agent object
656 0           my $user_agent = LWP::UserAgent->new();
657 0           $user_agent->agent( "perl/Business-Tax-Avalara/$VERSION" );
658 0           $user_agent->timeout( $self->{'request_timeout'} );
659            
660             # Create a request
661 0           my $request = HTTP::Request::Common::POST(
662             $request_url,
663             );
664            
665             $request->authorization_basic(
666             $self->{'user_name'},
667 0           $self->{'password'},
668             );
669            
670 0           $request->header( content_type => 'text/json' );
671 0           $request->content( $request_json );
672 0           $request->header( content_length => length( $request_json ) );
673            
674 0 0         if ( $self->{'debug'} )
675             {
676 0           carp( 'Request to Avalara: ', Data::Dump::dump( $request->content() ) );
677             }
678            
679             # Pass request to the user agent and get a response back
680 0           my $response = $user_agent->request( $request );
681            
682 0 0         if ( $self->{'debug'} )
683             {
684 0           carp( 'Response from Avalara: ', Data::Dump::dump( $response->content() ) );
685             }
686              
687             # Check the outcome of the response
688 0 0         if ( $response->is_success() )
689             {
690 0           return $response->content();
691             }
692             else
693             {
694 0           carp $response->status_line();
695 0           carp $request->as_string();
696 0           carp $response->as_string();
697 0           carp "Failed to fetch JSON response: " . $response->status_line() . "\n";
698 0           return $response->content();
699             }
700            
701 0           return;
702             }
703              
704              
705             =head2 _parse_response_json()
706              
707             Converts the returned JSON into a perl hash.
708              
709             =cut
710              
711             sub _parse_response_json
712             {
713 0     0     my ( $self, $response_json ) = @_;
714            
715 0           my $json = JSON::PP->new()->ascii()->pretty()->allow_nonref();
716 0           my $perl = $json->decode( $response_json );
717            
718 0           my $lines = delete $perl->{'TaxLines'};
719 0           foreach my $line ( @$lines )
720             {
721 0           $perl->{'TaxLines'}->{ $line->{'LineNo'} } = $line;
722             }
723            
724 0           return $perl;
725             }
726              
727              
728              
729             =head2 get_memcache()
730              
731             Return the database handle tied to the audit object.
732              
733             my $memcache = $avalara_gateway->get_memcache();
734              
735             =cut
736              
737             sub get_memcache
738             {
739 0     0 1   my ( $self ) = @_;
740              
741 0           return $self->{'memcache'};
742             }
743              
744              
745             =head2 get_cache()
746              
747             Get a value from the cache.
748              
749             my $value = $avalara_gateway->get_cache( key => $key );
750              
751             =cut
752              
753             sub get_cache
754             {
755 0     0 1   my ( $self, %args ) = @_;
756 0           my $key = delete( $args{'key'} );
757 0 0         croak 'Invalid argument(s): ' . join( ', ', keys %args )
758             if scalar( keys %args ) != 0;
759            
760             # Check parameters.
761 0 0 0       croak 'The parameter "key" is mandatory'
762             if !defined( $key ) || $key !~ /\w/;
763            
764 0           my $memcache = $self->get_memcache();
765             return
766 0 0         if !defined( $memcache );
767            
768 0           return $memcache->get( $key );
769             }
770              
771              
772             =head2 set_cache()
773              
774             Set a value into the cache.
775              
776             $avalara_gateway->set_cache(
777             key => $key,
778             value => $value,
779             expire_time => $expire_time,
780             );
781              
782             =cut
783              
784             sub set_cache
785             {
786 0     0 1   my ( $self, %args ) = @_;
787 0           my $key = delete( $args{'key'} );
788 0           my $value = delete( $args{'value'} );
789 0           my $expire_time = delete( $args{'expire_time'} );
790 0 0         croak 'Invalid argument(s): ' . join( ', ', keys %args )
791             if scalar( keys %args ) != 0;
792            
793             # Check parameters.
794 0 0 0       croak 'The parameter "key" is mandatory'
795             if !defined( $key ) || $key !~ /\w/;
796            
797 0           my $memcache = $self->get_memcache();
798             return
799 0 0         if !defined( $memcache );
800            
801 0 0         $memcache->set( $key, $value, $expire_time )
802             || carp 'Failed to set cache with key >' . $key . '<';
803            
804 0           return;
805             }
806              
807              
808             =head1 AUTHOR
809              
810             Kate Kirby, C<< >>.
811              
812              
813             =head1 MAINTAINER
814              
815             Nathan Gray Ekolibrie@cpan.orgE
816              
817             =head1 BUGS
818              
819             Please report any bugs or feature requests to C, or through
820             the web interface at L.
821             I will be notified, and then you'll automatically be notified of progress on
822             your bug as I make changes.
823              
824              
825             =head1 SUPPORT
826              
827             You can find documentation for this module with the perldoc command.
828              
829             perldoc Business::Tax::Avalara
830              
831              
832             You can also look for information at:
833              
834             =over 4
835              
836             =item * RT: CPAN's request tracker
837              
838             L
839              
840             =item * AnnoCPAN: Annotated CPAN documentation
841              
842             L
843              
844             =item * CPAN Ratings
845              
846             L
847              
848             =item * Search CPAN
849              
850             L
851              
852             =back
853              
854              
855             =head1 ACKNOWLEDGEMENTS
856              
857             Thanks to ThinkGeek (L) and its corporate overlords
858             at Geeknet (L), for footing the bill while we eat pizza
859             and write code for them!
860              
861              
862             =head1 COPYRIGHT & LICENSE
863              
864             Copyright 2012 Kate Kirby.
865              
866             Copyright (C) 2017 Nathan Gray
867              
868             This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3 as published by the Free Software Foundation.
869              
870             This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
871              
872             You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/
873              
874             =cut
875              
876             1;