File Coverage

blib/lib/Web/MarketReceipt/Verifier/AppStore.pm
Criterion Covered Total %
statement 60 81 74.0
branch 7 16 43.7
condition n/a
subroutine 17 19 89.4
pod 0 1 0.0
total 84 117 71.7


line stmt bran cond sub pod time code
1             package Web::MarketReceipt::Verifier::AppStore;
2 2     2   107854 use 5.010;
  2         14  
3 2     2   749 use Mouse;
  2         30309  
  2         11  
4             extends 'Web::MarketReceipt::Verifier';
5 2     2   1385 use utf8;
  2         17  
  2         10  
6 2     2   60 use Carp;
  2         5  
  2         146  
7              
8 2     2   14 no Mouse;
  2         4  
  2         9  
9              
10 2     2   688 use Web::MarketReceipt;
  2         5  
  2         63  
11 2     2   1000 use Furl;
  2         52516  
  2         64  
12 2     2   1391 use JSON::XS;
  2         8445  
  2         116  
13 2     2   508 use Try::Tiny;
  2         2096  
  2         123  
14 2     2   964 use MIME::Base64;
  2         3318  
  2         1250  
15              
16             sub verify {
17 2     2 0 6841 my ($self, %args) = @_;
18              
19 2         5 my $environment = 'Production';
20 2         4 my $receipt = $args{receipt};
21 2         5 my $password = $args{password};
22 2         4 my $exclude_old_transactions = $args{exclude_old_transactions};
23              
24 2         7 my $hash = {
25             'receipt-data' => $receipt,
26             };
27 2 50       7 if ($password) {
28 0         0 $hash->{password} = $password;
29             }
30 2 50       5 if ($exclude_old_transactions) {
31 0         0 $hash->{'exclude-old-transactions'} = \1;
32             }
33              
34             # try production environment first. cf.
35             # https://developer.apple.com/library/ios/technotes/tn2259/_index.html FAQ16
36             # http://nantekottai.com/2012/08/27/verifying-receipts/
37             my $res_json = $self->_send_request(
38             environment => $environment,
39             hash => $hash,
40             opts => $args{opts},
41 2         10 );
42              
43 2 50       46 if ($res_json->{status} == 21007) {
44 0         0 $environment = 'Sandbox';
45 0         0 $res_json = $self->_send_request(
46             environment => $environment,
47             hash => $hash,
48             );
49             }
50              
51 2         5 my $raw_json = $res_json->{receipt};
52             Web::MarketReceipt->new(
53             is_success => $res_json->{status} == 0 ? 1 : 0,
54             store => 'AppStore',
55             raw => $raw_json,
56             $res_json->{status} == 0 ? (
57             exists $raw_json->{in_app} ? (
58 2 50       14 orders => [ map {$self->_order2hash($_, $environment)} @{ $raw_json->{in_app}}],
  2 100       5  
  1 50       4  
59             ) : (
60             orders => [$self->_order2hash($raw_json, $environment)],
61             ),
62             ) : (),
63             );
64             }
65              
66             sub _order2hash {
67 3     3   7 my ($self, $raw_json, $environment) = @_;
68              
69             return {
70             product_identifier => $raw_json->{product_id},
71             unique_identifier => 'AppStore:' . $raw_json->{original_transaction_id},
72             purchased_epoch => $raw_json->{original_purchase_date_ms},
73             quantity => $raw_json->{quantity},
74 3         51 environment => $environment,
75             state => 'purchased',
76             }
77             }
78              
79             sub _send_request {
80 0     0     my ($self, %args) = @_;
81              
82 0           my $environment = $args{environment};
83             my $url = {
84             Production => 'https://buy.itunes.apple.com/verifyReceipt',
85             Sandbox => 'https://sandbox.itunes.apple.com/verifyReceipt',
86 0           }->{$environment};
87              
88 0           state $json_driver = JSON::XS->new->utf8;
89 0           my $json_str = $json_driver->encode($args{hash});
90              
91 0           state $furl = Furl->new(%{ $args{opts} });
  0            
92 0           my $res = $furl->post($url, [], $json_str);
93              
94 0 0         if ($res->status != 200) {
95 0           Web::MarketReceipt::Verifier::AppStore::Exception->throw(
96             message => sprintf('HTTP Status Error(%s):%s %s', $environment, $res->status, $res->message),
97             response => $res,
98             environment => $environment,
99             );
100             }
101              
102 0 0         my $res_json = eval { $json_driver->decode($res->body) } or Web::MarketReceipt::Verifier::AppStore::Exception->throw(
  0            
103             message => sprintf('body is not json(%s)', $environment),
104             response => $res,
105             environment => $environment,
106             );
107 0           $res_json;
108             }
109              
110             package Web::MarketReceipt::Verifier::AppStore::Exception;
111 2     2   21 use strict;
  2         4  
  2         57  
112 2     2   11 use warnings;
  2         17  
  2         95  
113 2     2   15 use utf8;
  2         12  
  2         11  
114              
115 2     2   587 use parent 'Exception::Tiny';
  2         304  
  2         28  
116              
117             use Class::Accessor::Lite (
118 2         12 ro => [qw/response environment/]
119 2     2   17635 );
  2         4  
120              
121             sub throw {
122 0     0     my ($class, %args) = @_;
123              
124 0           ($args{package}, $args{file}, $args{line}) = caller(2);
125 0           $args{subroutine} = (caller(3))[3];
126              
127 0           die $class->new(%args);
128             }
129              
130             1;