File Coverage

blib/lib/Finance/Currency/Convert/BCA.pm
Criterion Covered Total %
statement 68 85 80.0
branch 17 30 56.6
condition 2 2 100.0
subroutine 9 9 100.0
pod 2 2 100.0
total 98 128 76.5


line stmt bran cond sub pod time code
1             package Finance::Currency::Convert::BCA;
2              
3             our $DATE = '2021-02-01'; # DATE
4             our $VERSION = '0.155'; # VERSION
5              
6 2     2   35551 use 5.010001;
  2         29  
7 2     2   11 use strict;
  2         4  
  2         47  
8 2     2   9 use warnings;
  2         14  
  2         73  
9 2     2   3810 use Log::ger;
  2         137  
  2         12  
10              
11 2     2   645 use List::Util qw(min);
  2         5  
  2         315  
12              
13 2     2   16 use Exporter 'import';
  2         4  
  2         2844  
14             our @EXPORT_OK = qw(get_currencies convert_currency);
15              
16             our %SPEC;
17              
18             my $url = "https://www.bca.co.id/id/Individu/Sarana/Kurs-dan-Suku-Bunga/Kurs-dan-Kalkulator";
19              
20             $SPEC{':package'} = {
21             v => 1.1,
22             summary => 'Convert currency using BCA (Bank Central Asia)',
23             description => <<"_",
24              
25             This module can extract currency rates from the BCA/KlikBCA (Bank Central Asia's
26             internet banking) website:
27              
28             $url
29              
30             Currently only conversions from a few currencies to Indonesian Rupiah (IDR) are
31             supported.
32              
33             _
34             };
35              
36             $SPEC{get_currencies} = {
37             v => 1.1,
38             summary => 'Extract data from KlikBCA/BCA page',
39             result => {
40             description => <<'_',
41             Will return a hash containing key `currencies`.
42              
43             The currencies is a hash with currency symbols as keys and prices as values.
44              
45             Tha values is a hash with these keys: `buy_bn` and `sell_bn` (Bank Note buy/sell
46             rates), `buy_er` and `sell_er` (e-Rate buy/sell rates), `buy_ttc` and `sell_ttc`
47             (Telegraphic Transfer Counter buy/sell rates).
48              
49             _
50             },
51             };
52             sub get_currencies {
53 2     2 1 3465 require Mojo::DOM;
54 2         452478 require Parse::Date::Month::ID;
55 2         2562 require Parse::Number::ID;
56 2         2562 require Time::Local;
57              
58 2         3721 my %args = @_;
59              
60             #return [543, "Test parse failure response"];
61              
62 2         21 my $page;
63 2 100       22 if ($args{_page_content}) {
64 1         3 $page = $args{_page_content};
65             } else {
66 1         726 require Mojo::UserAgent;
67 1         263500 my $ua = Mojo::UserAgent->new;
68 1         13 my $res = $ua->get($url)->result;
69 1 50       2643918 unless ($res->is_success) {
70 0         0 return [500, "Can't retrieve URL $url: ".$res->code." - ".$res->message];
71             }
72 1         25 $page = $res->body;
73             }
74              
75 2         1019 my $dom = Mojo::DOM->new($page);
76              
77 2         444688 my %currencies;
78 2         17 my $tbody = $dom->find("tbody.text-right")->[0];
79             $tbody->find("tr")->each(
80             sub {
81 30     30   12377 my $row0 = shift;
82             my $row = $row0->find("td")->map(
83 30         80 sub { $_->text })->to_array;
  210         20970  
84             #use DD; dd $row;
85 30 50       1230 return unless $row->[0] =~ /\A[A-Z]{3}\z/;
86 30         97 $currencies{$row->[0]} = {
87             sell_er => Parse::Number::ID::parse_number_id(text=>$row->[1]),
88             buy_er => Parse::Number::ID::parse_number_id(text=>$row->[2]),
89             sell_ttc => Parse::Number::ID::parse_number_id(text=>$row->[3]),
90             buy_ttc => Parse::Number::ID::parse_number_id(text=>$row->[4]),
91             sell_bn => Parse::Number::ID::parse_number_id(text=>$row->[5]),
92             buy_bn => Parse::Number::ID::parse_number_id(text=>$row->[6]),
93             };
94             }
95 2         85314 );
96              
97 2 50       329 if (keys %currencies < 3) {
98 0         0 return [543, "Check: no/too few currencies found"];
99             }
100              
101 2         6 my ($mtime, $mtime_er, $mtime_ttc, $mtime_bn);
102             GET_MTIME_ER:
103             {
104 2 100       6 unless ($page =~ m!<th[^>]*>e-Rate\*?<br />((\d+) (\w+) (\d{4}) / (\d+):(\d+) WIB)</th>!) {
  2         176  
105 1         8 log_warn "Cannot extract last update time for e-Rate";
106 1         5 last;
107             }
108 1 50       8 my $mon = Parse::Date::Month::ID::parse_date_month_id(text=>$3) or do {
109 0         0 log_warn "Cannot recognize month name '$3' in last update time '$1'";
110 0         0 last;
111             };
112 1         40 $mtime_er = Time::Local::timegm(0, $6, $5, $2, $mon-1, $4) - 7*3600;
113             }
114             GET_MTIME_TTC:
115             {
116 2 50       68 unless ($page =~ m!<th[^>]*>TT Counter\*?<br />((\d+) (\w+) (\d{4}) / (\d+):(\d+) WIB)</th>!) {
  2         123  
117 0         0 log_warn "Cannot extract last update time for TT Counter";
118 0         0 last;
119             }
120 2 50       14 my $mon = Parse::Date::Month::ID::parse_date_month_id(text=>$3) or do {
121 0         0 log_warn "Cannot recognize month name '$3' in last update time '$1'";
122 0         0 last;
123             };
124 2         49 $mtime_ttc = Time::Local::timegm(0, $6, $5, $2, $mon-1, $4) - 7*3600;
125             }
126             GET_MTIME_BN:
127             {
128 2 50       98 unless ($page =~ m!<th[^>]*>Bank Notes\*?<br />((\d+) (\w+) (\d{4}) / (\d+):(\d+) WIB)</th>!) {
  2         154  
129 0         0 log_warn "Cannot extract last update time for Bank Note";
130 0         0 last;
131             }
132 2 50       12 my $mon = Parse::Date::Month::ID::parse_date_month_id(text=>$3) or do {
133 0         0 log_warn "Cannot recognize month name '$3' in last update time '$1'";
134 0         0 last;
135             };
136 2         39 $mtime_bn = Time::Local::timegm(0, $6, $5, $2, $mon-1, $4) - 7*3600;
137             }
138              
139 2         66 $mtime = min(grep {defined} ($mtime_er, $mtime_ttc, $mtime_bn));
  6         35  
140              
141 2         2808 [200, "OK", {
142             mtime => $mtime,
143             mtime_er => $mtime_er,
144             mtime_ttc => $mtime_ttc,
145             mtime_bn => $mtime_bn,
146             currencies => \%currencies,
147             }];
148             }
149              
150             # used for testing only
151             our $_get_res;
152              
153             $SPEC{convert_currency} = {
154             v => 1.1,
155             summary => 'Convert currency using BCA',
156             description => <<'_',
157              
158             Currently can only handle conversion `to` IDR. Dies if given other currency.
159              
160             Will warn if failed getting currencies from the webpage.
161              
162             Currency rate is not cached (retrieved from the website every time). Employ your
163             own caching.
164              
165             Will return undef if no conversion rate is available for the requested currency.
166              
167             Use `get_currencies()`, which actually retrieves and scrapes the source web
168             page, if you need the more complete result.
169              
170             _
171             args => {
172             n => {
173             schema=>'float*',
174             req => 1,
175             pos => 0,
176             },
177             from => {
178             schema=>'str*',
179             req => 1,
180             pos => 1,
181             },
182             to => {
183             schema=>'str*',
184             req => 1,
185             pos => 2,
186             },
187             which => {
188             summary => 'Select which rate to use (default is average buy+sell for e-Rate)',
189             schema => ['str*', in=>[map { my $bsa = $_; map {"${bsa}_$_"} qw(bn er ttc) } qw(buy sell avg)]],
190             description => <<'_',
191              
192             {buy,sell,avg}_{bn,er,ttc}.
193              
194             _
195             default => 'avg_er',
196             pos => 3,
197             },
198             },
199             args_as => 'array',
200             result_naked => 1,
201             };
202             sub convert_currency {
203 2     2 1 1525 my ($n, $from, $to, $which) = @_;
204              
205 2   100     12 $which //= 'avg_er';
206              
207 2 50       8 if (uc($to) ne 'IDR') {
208 0         0 die "Currently only conversion to IDR is supported".
209             " (you asked for conversion to '$to')\n";
210             }
211              
212 2 50       6 unless ($_get_res) {
213 0         0 $_get_res = get_currencies();
214 0 0       0 unless ($_get_res->[0] == 200) {
215 0         0 warn "Can't get currencies: $_get_res->[0] - $_get_res->[1]\n";
216 0         0 return undef;
217             }
218             }
219              
220 2 50       9 my $c = $_get_res->[2]{currencies}{uc $from} or return undef;
221              
222 2         4 my $rate;
223 2 100       11 if ($which =~ /\Aavg_(.+)/) {
224 1         7 $rate = ($c->{"buy_$1"} + $c->{"sell_$1"}) / 2;
225             } else {
226 1         4 $rate = $c->{$which};
227             }
228              
229 2         10 $n * $rate;
230             }
231              
232             1;
233             # ABSTRACT: Convert currency using BCA (Bank Central Asia)
234              
235             __END__
236              
237             =pod
238              
239             =encoding UTF-8
240              
241             =head1 NAME
242              
243             Finance::Currency::Convert::BCA - Convert currency using BCA (Bank Central Asia)
244              
245             =head1 VERSION
246              
247             This document describes version 0.155 of Finance::Currency::Convert::BCA (from Perl distribution Finance-Currency-Convert-BCA), released on 2021-02-01.
248              
249             =head1 SYNOPSIS
250              
251             use Finance::Currency::Convert::BCA qw(convert_currency);
252              
253             printf "1 USD = Rp %.0f\n", convert_currency(1, 'USD', 'IDR');
254              
255             =head1 DESCRIPTION
256              
257              
258             This module can extract currency rates from the BCA/KlikBCA (Bank Central Asia's
259             internet banking) website:
260              
261             https://www.bca.co.id/id/Individu/Sarana/Kurs-dan-Suku-Bunga/Kurs-dan-Kalkulator
262              
263             Currently only conversions from a few currencies to Indonesian Rupiah (IDR) are
264             supported.
265              
266             =head1 FUNCTIONS
267              
268              
269             =head2 convert_currency
270              
271             Usage:
272              
273             convert_currency($n, $from, $to, $which) -> any
274              
275             Convert currency using BCA.
276              
277             Currently can only handle conversion C<to> IDR. Dies if given other currency.
278              
279             Will warn if failed getting currencies from the webpage.
280              
281             Currency rate is not cached (retrieved from the website every time). Employ your
282             own caching.
283              
284             Will return undef if no conversion rate is available for the requested currency.
285              
286             Use C<get_currencies()>, which actually retrieves and scrapes the source web
287             page, if you need the more complete result.
288              
289             This function is not exported by default, but exportable.
290              
291             Arguments ('*' denotes required arguments):
292              
293             =over 4
294              
295             =item * B<$from>* => I<str>
296              
297             =item * B<$n>* => I<float>
298              
299             =item * B<$to>* => I<str>
300              
301             =item * B<$which> => I<str> (default: "avg_er")
302              
303             Select which rate to use (default is average buy+sell for e-Rate).
304              
305             {buy,sell,avg}_{bn,er,ttc}.
306              
307              
308             =back
309              
310             Return value: (any)
311              
312              
313              
314             =head2 get_currencies
315              
316             Usage:
317              
318             get_currencies() -> [status, msg, payload, meta]
319              
320             Extract data from KlikBCAE<sol>BCA page.
321              
322             This function is not exported by default, but exportable.
323              
324             No arguments.
325              
326             Returns an enveloped result (an array).
327              
328             First element (status) is an integer containing HTTP status code
329             (200 means OK, 4xx caller error, 5xx function error). Second element
330             (msg) is a string containing error message, or 'OK' if status is
331             200. Third element (payload) is optional, the actual result. Fourth
332             element (meta) is called result metadata and is optional, a hash
333             that contains extra information.
334              
335             Return value: (any)
336              
337              
338             Will return a hash containing key C<currencies>.
339              
340             The currencies is a hash with currency symbols as keys and prices as values.
341              
342             Tha values is a hash with these keys: C<buy_bn> and C<sell_bn> (Bank Note buy/sell
343             rates), C<buy_er> and C<sell_er> (e-Rate buy/sell rates), C<buy_ttc> and C<sell_ttc>
344             (Telegraphic Transfer Counter buy/sell rates).
345              
346             =head1 HOMEPAGE
347              
348             Please visit the project's homepage at L<https://metacpan.org/release/Finance-Currency-Convert-BCA>.
349              
350             =head1 SOURCE
351              
352             Source repository is at L<https://github.com/perlancar/perl-Finance-Currency-Convert-BCA>.
353              
354             =head1 BUGS
355              
356             Please report any bugs or feature requests on the bugtracker website L<https://github.com/perlancar/perl-Finance-Currency-Convert-BCA/issues>
357              
358             When submitting a bug or request, please include a test-file or a
359             patch to an existing test-file that illustrates the bug or desired
360             feature.
361              
362             =head1 AUTHOR
363              
364             perlancar <perlancar@cpan.org>
365              
366             =head1 COPYRIGHT AND LICENSE
367              
368             This software is copyright (c) 2021, 2018, 2017, 2016, 2015, 2014, 2012 by perlancar@cpan.org.
369              
370             This is free software; you can redistribute it and/or modify it under
371             the same terms as the Perl 5 programming language system itself.
372              
373             =cut