File Coverage

blib/lib/Verotel/FlexPay.pm
Criterion Covered Total %
statement 71 71 100.0
branch 14 14 100.0
condition 5 5 100.0
subroutine 18 18 100.0
pod 6 7 85.7
total 114 115 99.1


line stmt bran cond sub pod time code
1             package Verotel::FlexPay;
2              
3 1     1   205004 use strict;
  1         2  
  1         29  
4 1     1   3 use warnings;
  1         1  
  1         40  
5 1     1   4 use Digest::SHA qw( sha256_hex sha1_hex );
  1         2  
  1         50  
6 1     1   756 use Params::Validate qw(:all);
  1         9329  
  1         166  
7 1     1   819 use URI;
  1         3176  
  1         44  
8 1     1   8 use Carp;
  1         2  
  1         122  
9 1     1   580 use utf8;
  1         213  
  1         5  
10              
11 1     1   32 use base 'Exporter';
  1         4  
  1         1180  
12              
13             # WARNING: Use X.X.X format for $VERSION -> see: https://rt.cpan.org/Public/Bug/Display.html?id=119713
14             # because 0.20.1 < 0.1 (0.1 is converted to real number)
15             our $VERSION = '4.0.3';
16              
17             our @EXPORT_OK = qw(
18             get_signature
19             get_status_URL
20             get_purchase_URL
21             get_subscription_URL
22             get_upgrade_subscription_URL
23             get_cancel_subscription_URL
24             validate_signature
25             );
26              
27             my $STATUS_URL = 'https://secure.verotel.com/salestatus';
28             my $FLEXPAY_URL = 'https://secure.verotel.com/startorder';
29             my $CANCEL_URL = 'https://secure.verotel.com/cancel-subscription';
30             my $PROTOCOL_VERSION = '3.5';
31              
32             =head1 UNSUPPORTED
33              
34             This library is no longer maintained. Please check other FlexPay libraries at https://github.com/verotel
35              
36             =head1 NAME
37              
38             Verotel::FlexPay
39              
40             =head1 DESCRIPTION
41              
42             This library allows merchants to use Verotel payment gateway and get paid by their users via Credit Card and other payment methods.
43              
44             =head2 get_signature($secret, %params)
45              
46             Returns signature for the given parameters using L<$secret>.
47              
48             Signature is an SHA-256 hash as hexadecimal number generated from L<$secret>
49             followed by the parameters joined with colon (:). Parameters ("$key=$value")
50             are alphabeticaly orderered by their keys. Only the following parameters are
51             considered for signing:
52              
53             =head1 AUTHOR
54              
55             Verotel dev team
56              
57             =head1 SUPPORT
58              
59             Flexpay documentation can be found on https://controlcenter.verotel.com/flexpay-doc/index.html.
60              
61             =over 2
62              
63             version,
64             shopID, saleID, referenceID,
65             priceAmount, priceCurrency,
66             description, name
67             custom1, custom2, custom3
68             subscriptionType
69             period
70             trialAmount, trialPeriod
71             cancelDiscountPercentage
72              
73             =back
74              
75             =head3 Example:
76              
77             get_signature('aaB',
78             shopID => '123',
79             custom1 => 'xyz',
80             custom2 => undef ,
81             ignored => 'bla'
82             );
83              
84             returns the SHA-256 string for "aaB:custom1=xyz:custom2=:shopID=123" converted to lowercase.
85              
86             =cut
87              
88             sub get_signature {
89 13     13 1 205311 my $secret = shift;
90 13         75 my %params = @_;
91 13         44 %params = _filter_params( %params );
92 13         42 return _signature($secret, \%params);
93             }
94              
95              
96             =head2 validate_signature($secret, %params)
97              
98             Returns true if the signature passed in the parameters match the signature computed from B parameters (except for the signature itself).
99              
100             =head3 Example:
101              
102             validate_signature('aaB',
103             shopID => 123,
104             saleID => 345,
105             signature => 'acb4dd91827bc79999a04ac2082d0e43bb018a9ce563dfd3e863fbae32e5f381'
106             );
107              
108             returns true as the signature passed as the parameter is the same as the signature computed for "aaB:saleID=345:shopID=123"
109              
110             Note: It accepts SHA-256 signature, but for now accepts also old SHA-1 signature for backward compatiblity.
111              
112             =cut
113              
114             sub validate_signature {
115 3     3 1 3447 my ($secret, %params) = @_;
116 3         7 my $verified_sign = lc(delete $params{signature});
117 3         7 my $calculated_sign = _signature($secret, \%params);
118 3 100       11 return 1 if $verified_sign eq $calculated_sign;
119              
120 2         5 my $old_sha1_sign = _signature($secret, \%params, \&sha1_hex);
121 2 100       11 return ($verified_sign eq $old_sha1_sign ? 1 : 0);
122             }
123              
124              
125             =head2 get_purchase_URL($secret, %params)
126              
127             Return URL for purchase with signed parameters (only the parameters listed in the description of get_signature() are considered for signing).
128              
129             =head3 Example:
130              
131             get_purchase_URL('mySecret', shopID => 65147, priceAmount => '6.99', priceCurrency => 'USD');
132              
133             returns
134              
135             "https://secure.verotel.com/startorder?priceAmount=6.99&priceCurrency=USD&shopID=65147&type=purchase&version=3.5&signature=37d56280eae410d2e5d6b67ccd29fd84173f2eed5a329c9b2f7fe9a77ad95441"
136              
137             =cut
138              
139             sub get_purchase_URL {
140 5     5 1 10360 my ($secret, %params) = @_;
141 5         19 return _generate_URL($FLEXPAY_URL, $secret, 'purchase', %params);
142             }
143              
144             =head2 get_subscription_URL($secret, %params)
145              
146             Return URL for subscription with signed parameters (only the parameters listed in the description of get_signature() are considered for signing).
147              
148             =head3 Example:
149              
150             get_subscription_URL('mySecret', shopID => 65147, subscriptionType => 'recurring', period => 'P1M');
151              
152             returns
153              
154             "https://secure.verotel.com/startorder?period=P1M&shopID=65147&subscriptionType=recurring&type=subscription&version=3.5&signature=2f2ffd9ba91dec62be74b143d0093ce7cefc62d1dab237aa3a327d76188cf77c"
155              
156             =cut
157              
158             sub get_subscription_URL {
159 3     3 1 6166 my ($secret, %params) = @_;
160 3         11 return _generate_URL($FLEXPAY_URL, $secret, 'subscription', %params);
161             }
162              
163             =head2 get_subscription_URL($secret, %params)
164              
165             Return URL for upgrade subscription with signed parameters (only the parameters listed in the description of get_signature() are considered for signing).
166              
167             =head3 Example:
168              
169             get_upgrade_subscription_URL('mySecret', shopID => 65147, subscriptionType => 'recurring', period => 'P1M');
170              
171             returns
172              
173             "https://secure.verotel.com/startorder?period=P1M&shopID=65147&subscriptionType=recurring&type=upgradesubscription&version=3.5&signature=2276fd3aea2ca4027641515c731c6783ec2def70504c5276c5f4599039129e52"
174              
175             =cut
176              
177             sub get_upgrade_subscription_URL {
178 1     1 0 1948 my ($secret, %params) = @_;
179 1         7 return _generate_URL($FLEXPAY_URL, $secret, 'upgradesubscription', %params);
180             }
181              
182              
183             =head2 get_status_URL($secret, %params)
184              
185             Return URL for status with signed parameters (only the parameters listed in the description of get_signature() are considered for signing).
186              
187             =head3 Example:
188              
189             get_status_URL('mySecret', shopID => '65147', saleID => '1485');
190              
191             returns
192              
193             "https://secure.verotel.com/salestatus?saleID=1485&shopID=65147&version=3.5&signature=1a24a2d189824c6800d85131f11a2fca0ebbc233f31cad6d45e947496e423ff7"
194              
195             =cut
196              
197             sub get_status_URL {
198 3     3 1 6167 my ($secret, %params) = @_;
199 3         11 return _generate_URL($STATUS_URL, $secret, undef, %params);
200             }
201              
202              
203             =head2 get_cancel_subscription_URL($secret, %params)
204              
205             Return URL for cancel subscription with signed parameters (only the parameters listed in the description of get_signature() are considered for signing).
206              
207             =head3 Example:
208              
209             get_cancel_subscription_URL('mySecret', shopID => '65147', saleID => '1485');
210              
211             returns
212              
213             "https://secure.verotel.com/cancel-subscription?saleID=1485&shopID=65147&version=3.5&signature=1a24a2d189824c6800d85131f11a2fca0ebbc233f31cad6d45e947496e423ff7"
214              
215             =cut
216              
217             sub get_cancel_subscription_URL {
218 3     3 1 6149 my ($secret, %params) = @_;
219 3         12 return _generate_URL($CANCEL_URL, $secret, undef, %params);
220             }
221              
222              
223             ################ PRIVATE METHODS ##########################
224              
225              
226             sub _generate_URL {
227 15     15   45 my ($baseURL, $secret, $type, %params) = (@_);
228              
229 15 100       85 if (!$secret) {croak "no secret given"};
  4         59  
230 11 100       25 if (!%params) {croak "no params given"};
  4         47  
231              
232 7         19 $params{version} = $PROTOCOL_VERSION;
233 7 100       20 if (defined $type) {
234 5         13 $params{type} = $type;
235             }
236              
237             # remove empty values:
238 7         44 my @sorted_params = map { (defined($params{$_}) && $params{$_} ne '')
239 51 100 100     196 ? ($_ => $params{$_})
240             : ()
241             } sort keys %params;
242              
243 7         45 my $url = new URI($baseURL);
244 7         9914 my $signature = get_signature($secret, @sorted_params);
245              
246 7         60 $url->query_form(@sorted_params, signature => $signature);
247              
248 7         2038 return $url->as_string();
249             }
250              
251             sub _signature {
252 18     18   32 my ($secret, $params_ref, $algorigthm_func) = @_;
253 18 100       92 my @values = map { $_.'='.(defined $params_ref->{$_} ? $params_ref->{$_} : "") }
  102         301  
254             sort keys %$params_ref;
255 18         59 my $encString = join(":", $secret, @values);
256 18         49 utf8::encode($encString);
257              
258 18   100     90 $algorigthm_func ||= \&sha256_hex;
259 18         174 return lc($algorigthm_func->($encString));
260             }
261              
262             sub _filter_params {
263 13     13   50 my (%params) = @_;
264              
265 13         39 my @keys = grep { m/ ^(
  89         307  
266             version
267             | shopID
268             | price(Amount|Currency)
269             | paymentMethod
270             | description
271             | referenceID
272             | saleID
273             | custom[123]
274             | subscriptionType
275             | period
276             | name
277             | trialAmount
278             | trialPeriod
279             | cancelDiscountPercentage
280             | type
281             | backURL
282             | declineURL
283             | precedingSaleID
284             | upgradeOption
285             )$
286             /x } keys %params;
287              
288 13         30 my %filtered = map { $_ => $params{$_} } @keys;
  75         155  
289              
290 13         102 return %filtered;
291             }
292              
293             1;