├── src └── IBank │ ├── Log.php │ ├── Modules │ ├── BCA │ │ ├── README.md │ │ ├── BCA-target.yml │ │ ├── BCA-reference.yml │ │ ├── BCA-menu.yml │ │ └── BCA.php │ ├── BNI │ │ ├── README.md │ │ ├── BNI-reference.yml │ │ ├── BNI-target.yml │ │ ├── BNI-menu.yml │ │ └── BNI.php │ └── Mandiri │ │ ├── Mandiri-target.yml │ │ ├── README.md │ │ ├── Mandiri-reference.yml │ │ ├── Mandiri-menu.yml │ │ └── Mandiri.php │ ├── WebCrawlerModuleTrait.php │ └── IBank.php ├── .gitignore ├── README.md ├── demo ├── README.md ├── bca-logout.php ├── bca-get-balance.php ├── bni-get-balance.php ├── bca-get-transaction.php └── bni-get-transaction.php ├── composer.json ├── misc └── BNI-http-response-body-maintenance.html └── LICENSE /src/IBank/Log.php: -------------------------------------------------------------------------------- 1 | Tanggal Akhir tidak boleh melebihi tanggal hari ini 16 | 17 | Padahal tanggal akhir sama dengan hari ini, kemungkinan disebabkan pukul 00:00 18 | sd 01:00 masih belum dianggap hari baru oleh server. 19 | -------------------------------------------------------------------------------- /src/IBank/Modules/BCA/BCA-target.yml: -------------------------------------------------------------------------------- 1 | # Target Definitions. 2 | target: 3 | get_balance: 4 | - handler: visit 5 | handler_before: bca_check_session 6 | menu: bca_balance_inquiry_page 7 | visit_before: 8 | - bca_set_referer 9 | - bca_method_post 10 | visit_after: verify 11 | get_transaction: 12 | - handler: visit 13 | handler_before: bca_check_session 14 | menu: bca_account_statement_page 15 | visit_before: 16 | - bca_set_referer 17 | - bca_method_post 18 | visit_after: verify 19 | - handler: visit 20 | menu: bca_account_statement_page_view 21 | visit_after: verify 22 | logout: 23 | - handler: visit 24 | menu: bca_logout 25 | visit_before: 26 | - bca_set_referer 27 | - bca_method_post 28 | - handler: bca_clear_last_visit 29 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ijortengab/ibank", 3 | "description": "Internet Banking Helper for grab information of your internet banking account.", 4 | "type": "project", 5 | "license": "GPL", 6 | "authors": [ 7 | { 8 | "name": "IjorTengab", 9 | "email": "m_roji28@yahoo.com", 10 | "homepage": "http://github.com/ijortengab" 11 | } 12 | ], 13 | "require": { 14 | "fabpot/goutte": "^4.0", 15 | "symfony/console": "^5.2", 16 | "symfony/workflow": "^5.2", 17 | "symfony/yaml": "^5.2", 18 | "symfony/dom-crawler": "^5.2" 19 | }, 20 | "autoload": { 21 | "psr-4": {"IjorTengab\\IBank\\": "src/IBank/"} 22 | }, 23 | 24 | "repositories": [ 25 | { 26 | "type": "vcs", 27 | "url": "https://github.com/ijortengab/logger" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/IBank/Modules/Mandiri/Mandiri-target.yml: -------------------------------------------------------------------------------- 1 | # Target Definitions. 2 | target: 3 | get_balance: 4 | - handler: visit 5 | # handler_before: bca_check_session 6 | menu: mandiri_home_page 7 | # visit_before: 8 | # - bca_set_referer 9 | # - bca_method_post 10 | visit_after: verify 11 | - handler: visit 12 | menu : mandiri_login_form 13 | # get_transaction: 14 | # - handler: visit 15 | # handler_before: bca_check_session 16 | # menu: bca_account_statement_page 17 | # visit_before: 18 | # - bca_set_referer 19 | # - bca_method_post 20 | # visit_after: verify 21 | # - handler: visit 22 | # menu: bca_account_statement_page_view 23 | # visit_after: verify 24 | # logout: 25 | # - handler: visit 26 | # menu: bca_logout 27 | # visit_before: 28 | # - bca_set_referer 29 | # - bca_method_post 30 | # - handler: bca_clear_last_visit 31 | -------------------------------------------------------------------------------- /demo/bca-logout.php: -------------------------------------------------------------------------------- 1 | '; 17 | 18 | // 4. Execute. 19 | $result = IBank::BCA('logout', $information); 20 | echo '$result: ', print_r($result, true), PHP_EOL; 21 | 22 | // 5. Disarankan untuk mengecek log (error/notice/debug). 23 | $log = Log::get(); 24 | echo '$log: ', print_r($log, true), PHP_EOL; 25 | -------------------------------------------------------------------------------- /misc/BNI-http-response-body-maintenance.html: -------------------------------------------------------------------------------- 1 |
2 |

Nasabah Yth,­­

3 |

Untuk meningkatkan kualitas layanan kepada nasabah, saat ini kami sedang melakukan maintenance sistem.

4 |

Selama proses tersebut berlangsung, layanan transaksi melalui Internet Banking untuk sementara tidak dapat digunakan.

5 |

6 |

Kami mohon maaf atas ketidaknyamanan ini.

7 |

8 |

Informasi lebih lanjut, silakan menghubungi BNI Call di 1500046.

9 |

-------------------------------------------------------------------------------------------------------------------------------------------------------------

10 |

11 |

Dear Valued Customer,

12 |

13 |

In order to maintain our quality services, we are currently performing system maintenance.

14 |

During the aforementioned process, transaction services via Internet Banking will be temporarily unavailable.

15 |

16 |

We apologize for any inconvenience.

17 |

18 |

For further information, please call BNI Call at 1500046.

19 |
-------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-2021 IjorTengab (http://ijortengab.id) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/IBank/Modules/BCA/BCA-reference.yml: -------------------------------------------------------------------------------- 1 | # Reference 2 | reference: 3 | login_form: 4 | description: 'Menuju home_page dari context anonymous ke authenticated.' 5 | position: prepend 6 | steps: 7 | - handler: visit 8 | menu: bca_login_form 9 | visit_after: verify 10 | home_page: 11 | description: '' 12 | position: prepend 13 | steps: 14 | - handler: visit 15 | menu: bca_home_page 16 | visit_after: verify 17 | 18 | revisit_account_statement_page: 19 | description: '' 20 | position: prepend 21 | steps: 22 | - handler: visit 23 | menu: bca_account_statement_page 24 | visit_before: 25 | - bca_set_referer 26 | - bca_method_post 27 | visit_after: verify 28 | - handler: visit 29 | menu: bca_account_statement_page_view 30 | visit_after: verify 31 | transaction_finishing: 32 | description: '' 33 | position: append 34 | steps: 35 | - handler: bca_transaction_finishing 36 | filter_transaction: 37 | description: '' 38 | position: append 39 | steps: 40 | - handler: bca_filter_transaction 41 | -------------------------------------------------------------------------------- /demo/bca-get-balance.php: -------------------------------------------------------------------------------- 1 | '; 20 | 21 | // 4. Execute. 22 | $result = IBank::BCA('get_balance', $information); 23 | echo '$result: ', print_r($result, true), PHP_EOL; 24 | 25 | // 5. Disarankan untuk mengecek log (error/notice/debug). 26 | $log = Log::get(); 27 | echo '$log: ', print_r($log, true), PHP_EOL; 28 | -------------------------------------------------------------------------------- /demo/bni-get-balance.php: -------------------------------------------------------------------------------- 1 | '; 20 | 21 | // 4. Execute. 22 | $result = IBank::BNI('get_balance', $information); 23 | echo '$result: ', print_r($result, true), PHP_EOL; 24 | 25 | // 5. Disarankan untuk mengecek log (error/notice/debug). 26 | $log = Log::get(); 27 | echo '$log: ', print_r($log, true), PHP_EOL; 28 | -------------------------------------------------------------------------------- /src/IBank/Modules/Mandiri/README.md: -------------------------------------------------------------------------------- 1 | Posisi tidak urut pada bank Mandiri pada jam 00:20 2 | 3 | Contoh: ascending 4 | Tanggal Keterangan Transaksi Debet Kredit 5 | 05/02/2016 SA Monthly Fee MONTHLY CARD CHARGE 0004097662467333749 1.500,00 0,00 6 | 05/02/2016 SA Monthly Fee MONTHLY CARD CHARGE 0004097662467333749 1.500,00 0,00 7 | 05/02/2016 SA Monthly Fee MONTHLY CARD CHARGE 0004097662467333749 1.500,00 0,00 8 | 05/02/2016 SA Monthly Fee MONTHLY CARD CHARGE 0004097662467333749 1.500,00 0,00 9 | 05/02/2016 SA Cash Dep NoBook 0,00 2.000.000,00 10 | 09/02/2016 SA OB SA No Book 1.000.000,00 0,00 11 | 09/02/2016 SA OB SA No Book 100.006,00 0,00 12 | 09/02/2016 SA OB SA No Book 100.005,00 0,00 13 | 09/02/2016 SA OB SA No Book 100.004,00 0,00 14 | 09/02/2016 SA OB SA No Book 100.003,00 0,00 15 | 09/02/2016 SA OB SA No Book 100.002,00 0,00 16 | 09/02/2016 SA OB SA No Book 100.001,00 0,00 17 | 10/02/2016 SA Cash Dep NoBook 0,00 1.050.000,00 18 | 19 | harusnya transfer 100.001,00 lebih dulu dibandingkan 100.006,00 20 | dan ternyata ada keterangannya yakni: 21 | 22 | Keterangan: 23 | Untuk mengetahui posisi Saldo Awal dan Saldo Akhir, 24 | Total Kredit dan Total Debet, silahkan mengakses kembali mutasi rekening 25 | beberapa saat lagi. -------------------------------------------------------------------------------- /src/IBank/Modules/Mandiri/Mandiri-reference.yml: -------------------------------------------------------------------------------- 1 | # Reference 2 | reference: 3 | # login_form: 4 | # description: 'Menuju home_page dari context anonymous ke authenticated.' 5 | # position: prepend 6 | # steps: 7 | # - handler: visit 8 | # menu: bca_login_form 9 | # visit_after: verify 10 | # home_page: 11 | # description: '' 12 | # position: prepend 13 | # steps: 14 | # - handler: visit 15 | # menu: bca_home_page 16 | # visit_after: verify 17 | 18 | # revisit_account_statement_page: 19 | # description: '' 20 | # position: prepend 21 | # steps: 22 | # - handler: visit 23 | # menu: bca_account_statement_page 24 | # visit_before: 25 | # - bca_set_referer 26 | # - bca_method_post 27 | # visit_after: verify 28 | # - handler: visit 29 | # menu: bca_account_statement_page_view 30 | # visit_after: verify 31 | # transaction_finishing: 32 | # description: '' 33 | # position: append 34 | # steps: 35 | # - handler: bca_transaction_finishing 36 | # filter_transaction: 37 | # description: '' 38 | # position: append 39 | # steps: 40 | # - handler: bca_filter_transaction 41 | -------------------------------------------------------------------------------- /demo/bca-get-transaction.php: -------------------------------------------------------------------------------- 1 | '; 22 | 23 | // 4. Execute. 24 | $result = IBank::BCA('get_transaction', $information); 25 | echo '$result: ', print_r($result, true), PHP_EOL; 26 | 27 | // 5. Disarankan untuk mengecek log (error/notice/debug). 28 | $log = Log::get(); 29 | echo '$log: ', print_r($log, true), PHP_EOL; 30 | -------------------------------------------------------------------------------- /demo/bni-get-transaction.php: -------------------------------------------------------------------------------- 1 | '; 22 | 23 | // 4. Execute. 24 | $result = IBank::BNI('get_transaction', $information); 25 | echo '$result: ', print_r($result, true), PHP_EOL; 26 | 27 | // 5. Disarankan untuk mengecek log (error/notice/debug). 28 | $log = Log::get(); 29 | echo '$log: ', print_r($log, true), PHP_EOL; 30 | -------------------------------------------------------------------------------- /src/IBank/Modules/BCA/BCA-menu.yml: -------------------------------------------------------------------------------- 1 | # Menu Definitions. 2 | menu: 3 | bca_home_page: 4 | url: https://ibank.klikbca.com 5 | verify: 6 | home_page_anonymous: bca_parse_home_page_anonymous 7 | bca_login_form: 8 | url: https://ibank.klikbca.com/authentication.do 9 | verify: 10 | home_page_authenticated: bca_parse_home_page_authenticated 11 | home_page_anonymous: bca_error_login 12 | bca_balance_inquiry_page: 13 | url: https://ibank.klikbca.com/balanceinquiry.do 14 | referer: https://ibank.klikbca.com/{language}/account_information_menu.htm 15 | verify: 16 | table_exists: bca_parse_balance_inquiry_page 17 | redirect_to_main: bca_parse_redirect_to_main 18 | bca_account_statement_page: 19 | url: https://ibank.klikbca.com/accountstmt.do?value(actions)=acct_stmt 20 | referer: https://ibank.klikbca.com/{language}/account_information_menu.htm 21 | verify: 22 | select_range_form: bca_parse_select_range_form 23 | redirect_to_main: bca_parse_redirect_to_main 24 | bca_account_statement_page_view: 25 | url: https://ibank.klikbca.com/accountstmt.do?value(actions)=acctstmtview 26 | verify: 27 | table_transaction_page: 28 | - bca_parse_transaction_page 29 | - bca_check_over_range 30 | bca_logout: 31 | url: https://ibank.klikbca.com/authentication.do?value(actions)=logout 32 | referer: https://ibank.klikbca.com/top.htm 33 | -------------------------------------------------------------------------------- /src/IBank/Modules/BNI/BNI-reference.yml: -------------------------------------------------------------------------------- 1 | # Reference 2 | reference: 3 | home_page: 4 | description: 'Dari context anonymous ke authenticated.' 5 | position: prepend 6 | steps: 7 | - handler: visit 8 | menu: bni_login_page 9 | visit_after: verify 10 | - handler: visit 11 | menu: bni_login_form 12 | visit_after: verify 13 | 404_page: 14 | description: 'Dari page 404 menuju home_page.' 15 | position: prepend 16 | steps: 17 | - handler: visit 18 | menu: bni_login_page 19 | visit_after: verify 20 | transaction_next_page: 21 | description: 'Next Page dari halaman transaction history.' 22 | position: prepend 23 | steps: 24 | - handler: visit 25 | menu: bni_transaction_next_page 26 | visit_after: verify 27 | revisit_select_range_page: 28 | description: 'Kembali ke halaman select_range_page untuk mengulang pencarian pada bulan berikutnya.' 29 | position: append 30 | steps: 31 | - handler: visit 32 | menu: bni_select_range_page 33 | visit_after: verify 34 | - handler: bni_transaction_finishing 35 | revisit_select_range_form: 36 | description: 'Mengirim form lagi untuk over range.' 37 | position: prepend 38 | steps: 39 | - handler: visit 40 | menu: bni_select_range_form 41 | visit_after: verify 42 | transaction_finishing: 43 | description: '' 44 | position: append 45 | steps: 46 | - handler: bni_transaction_finishing 47 | -------------------------------------------------------------------------------- /src/IBank/Modules/Mandiri/Mandiri-menu.yml: -------------------------------------------------------------------------------- 1 | # Menu Definitions. 2 | menu: 3 | mandiri_home_page: 4 | url: https://ib.bankmandiri.co.id 5 | verify: 6 | home_page_anonymous: mandiri_parse_home_page_anonymous 7 | mandiri_login_form: 8 | url: https://ib.bankmandiri.co.id/retail/Login.do 9 | # verify: 10 | # home_page_authenticated: mandiri_parse_home_page_authenticated 11 | # home_page_anonymous: mandiri_error_login 12 | # mandiri_balance_inquiry_page: 13 | # url: https://ibank.klikbca.com/balanceinquiry.do 14 | # referer: https://ibank.klikbca.com/{language}/account_information_menu.htm 15 | # verify: 16 | # table_exists: mandiri_parse_balance_inquiry_page 17 | # redirect_to_main: mandiri_parse_redirect_to_main 18 | # mandiri_account_statement_page: 19 | # url: https://ibank.klikbca.com/accountstmt.do?value(actions)=acct_stmt 20 | # referer: https://ibank.klikbca.com/{language}/account_information_menu.htm 21 | # verify: 22 | # select_range_form: mandiri_parse_select_range_form 23 | # redirect_to_main: mandiri_parse_redirect_to_main 24 | # mandiri_account_statement_page_view: 25 | # url: https://ibank.klikbca.com/accountstmt.do?value(actions)=acctstmtview 26 | # verify: 27 | # table_transaction_page: 28 | # - mandiri_parse_transaction_page 29 | # - mandiri_check_over_range 30 | # mandiri_logout: 31 | # url: https://ibank.klikbca.com/authentication.do?value(actions)=logout 32 | # referer: https://ibank.klikbca.com/top.htm 33 | -------------------------------------------------------------------------------- /src/IBank/Modules/BNI/BNI-target.yml: -------------------------------------------------------------------------------- 1 | # Target Definitions. 2 | target: 3 | get_balance: 4 | - handler: visit 5 | menu: bni_home_page 6 | visit_after: verify 7 | - handler: visit 8 | menu: bni_account_page 9 | visit_after: verify 10 | - handler: visit 11 | menu: bni_balance_inquiry_page 12 | visit_after: verify 13 | - handler: visit 14 | menu: bni_account_type_form 15 | visit_after: verify 16 | - handler: visit 17 | menu: bni_account_number_form 18 | visit_after: verify 19 | get_transaction: 20 | - handler: bni_check_range 21 | get_last_transaction: 22 | - handler: visit 23 | menu: bni_home_page 24 | visit_after: verify 25 | - handler: visit 26 | menu: bni_account_page 27 | visit_after: verify 28 | - handler: visit 29 | menu: bni_mini_statement_page 30 | visit_after: verify 31 | - handler: visit 32 | menu: bni_account_number_form 33 | visit_after: verify 34 | get_range_transaction: 35 | - handler: visit 36 | menu: bni_home_page 37 | visit_after: verify 38 | - handler: visit 39 | menu: bni_account_page 40 | visit_after: verify 41 | - handler: visit 42 | menu: bni_transaction_history_page 43 | visit_after: verify 44 | - handler: visit 45 | menu: bni_account_type_form 46 | visit_after: verify 47 | - handler: visit 48 | menu: bni_select_range_form 49 | visit_after: verify 50 | - handler: bni_transaction_finishing 51 | test: 52 | - handler: entahlah 53 | -------------------------------------------------------------------------------- /src/IBank/WebCrawlerModuleTrait.php: -------------------------------------------------------------------------------- 1 | log->get(); 19 | if ($level === null) { 20 | return $log; 21 | } 22 | return array_key_exists($level, $log) ? $log[$level] : []; 23 | } 24 | 25 | /** 26 | * Implements of ModuleInterface::setAction(). 27 | */ 28 | public function setAction($action) 29 | { 30 | $this->target = $action; 31 | } 32 | 33 | /** 34 | * Implements of ModuleInterface::setInformation(). 35 | */ 36 | public function setInformation($information) 37 | { 38 | if (is_string($information)) { 39 | $information = trim($information); 40 | $information = json_decode($information, true); 41 | } 42 | $information = (array) $information; 43 | foreach ($information as $key => $value) { 44 | $this->set($key, $value); 45 | } 46 | } 47 | 48 | /** 49 | * Implements of ModuleInterface::runAction(). 50 | */ 51 | public function runAction() 52 | { 53 | return $this->execute(); 54 | } 55 | 56 | /** 57 | * Implements of ModuleInterface::getResult(). 58 | */ 59 | public function getResult() 60 | { 61 | return $this->result; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/IBank/IBank.php: -------------------------------------------------------------------------------- 1 | parse(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'Mandiri-menu.yml')); 63 | $value += $yaml->parse(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'Mandiri-target.yml')); 64 | $value += $yaml->parse(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'Mandiri-reference.yml')); 65 | return $value; 66 | } catch (ParseException $e) { 67 | $this->log->error('Unable to parse the YAML string: {string}', ['string' => $e->getMessage()]); 68 | } 69 | } 70 | 71 | protected function init() 72 | { 73 | // Default nama file untuk keperluan debug. 74 | $_ = DIRECTORY_SEPARATOR; 75 | $this->configuration('temporary][browser][browser_history', '..' . $_ . 'debug' . $_ . 'history.log'); 76 | $this->configuration('temporary][browser][browser_response_body', '..' . $_ . 'debug' . $_ . 'response_body.html'); 77 | } 78 | 79 | /** 80 | * Override method. 81 | * 82 | * Set property information in object. 83 | * 84 | * @param $property string 85 | * Parameter dapat bernilai sebagai berikut: 86 | * - username 87 | * Username for login. 88 | * - password 89 | * Password for login. 90 | * - account 91 | * Account Number. 92 | * dan property lainnya yang dijelaskan pada parent::set(). 93 | */ 94 | public function set($property, $value) 95 | { 96 | switch ($property) { 97 | case 'username': 98 | case 'password': 99 | case 'account': 100 | case 'range': 101 | case 'sort': 102 | $this->{$property} = $value; 103 | break; 104 | } 105 | return parent::set($property, $value); 106 | } 107 | 108 | protected function executeBefore() 109 | { 110 | parent::executeBefore(); 111 | 112 | // $c = file_get_contents('C:\Users\X220\.ibank\debug\response_body.html'); 113 | 114 | 115 | // $form = ParseHTMLAdvanced::init($c, 'form[name=LoginForm][action=/retail/Login.do]'); 116 | 117 | // $this->html = new ParseHTMLAdvanced($c); 118 | 119 | 120 | // $this->configuration('temporary][form', $form); 121 | 122 | // $this->mandiriParseHomePageAnonymous(); 123 | // throw new ExecuteException; 124 | 125 | 126 | 127 | 128 | 129 | return; 130 | /* 131 | switch ($this->target) { 132 | case 'logout': 133 | break; 134 | 135 | default: 136 | if (null === $this->username) { 137 | $this->log->error('Username belum didefinisikan.'); 138 | throw new ExecuteException; 139 | } 140 | if (null === $this->password) { 141 | $this->log->error('Password belum didefinisikan.'); 142 | throw new ExecuteException; 143 | } 144 | break; 145 | } 146 | switch ($this->target) { 147 | case 'get_transaction': 148 | if (null === $this->range) { 149 | // Mandiri tidak ada mini account statement seperti BNI, maka 150 | // jika null kita anggap saja today. 151 | $this->range = 'today'; 152 | $this->log->notice('Range belum didefinisikan, otomatis mencari transaksi hari ini.'); 153 | } 154 | $this->range = Range::create($this->range); 155 | // Mandiri paling lama adalah awal bulan dari 2 bulan lalu. 156 | $oldest = new \DateTime('first day of 2 months ago'); 157 | if (!$this->range->comparison($oldest, 'less', 'start')) { 158 | // Tapi kalo masih di hari yang sama, ya gpp. 159 | if (!$this->range->isSameDay($oldest, 'start')) { 160 | $this->log->error('Tanggal Awal tidak boleh kurang dari hari pertama dari 2 bulan lalu: {date}', ['date' => $oldest->format('Y-m-d')]); 161 | throw new ExecuteException; 162 | } 163 | } 164 | 165 | // End date tidak boleh lewat dari hari ini. 166 | $now = new \DateTime(); 167 | if (!$this->range->comparison($now, 'greater', 'end')) { 168 | // Tapi kalo masih di hari yang sama, ya gpp. 169 | if (!$this->range->isSameDay($now, 'end')) { 170 | $this->log->error('Tanggal Akhir tidak boleh melebihi Tanggal Hari Ini.'); 171 | throw new ExecuteException; 172 | } 173 | } 174 | switch ($this->sort) { 175 | case 'asc': 176 | case 'desc': 177 | break; 178 | 179 | case 'ASC': 180 | case 'ascending': 181 | case 'ASCENDING': 182 | $this->sort = 'asc'; 183 | break; 184 | 185 | case 'descending': 186 | case 'DESC': 187 | case 'DESCENDING': 188 | $this->sort = 'desc'; 189 | break; 190 | 191 | default: 192 | $this->sort = 'desc'; 193 | $this->log->notice('Transaksi otomatis diurut dengan pola descending.'); 194 | break; 195 | } 196 | 197 | break; 198 | 199 | default: 200 | // Do something. 201 | break; 202 | } 203 | */ 204 | 205 | } 206 | 207 | /** 208 | * Override method. 209 | * 210 | * Set browser as mobile, and not use curl as library request. 211 | */ 212 | protected function browserInit() 213 | { 214 | parent::browserInit(); 215 | $this->browser->curl(false); 216 | } 217 | 218 | protected function checkIndication($indication_name) 219 | { 220 | switch ($indication_name) { 221 | case 'home_page_anonymous': 222 | $form = $this->html->find('form[name=LoginForm][action=/retail/Login.do]'); 223 | $this->configuration('temporary][form', $form); 224 | return ($form->length > 0); 225 | 226 | // case 'home_page_authenticated': 227 | // return ($this->html->find('frameset')->length > 0); 228 | 229 | // case 'table_exists': 230 | // $table = $this->html->find('table'); 231 | // $this->configuration('temporary][table', $table); 232 | // return ($table->length > 0); 233 | 234 | // case 'select_range_form': 235 | // $form = $this->html->find('form[name=iBankForm][action=/accountstmt.do]'); 236 | // $this->configuration('temporary][form', $form); 237 | // return ($form->length > 0); 238 | 239 | // case 'redirect_to_main': 240 | // $text = $this->html->text(); 241 | // return (strpos($text, "window.parent.location.href = 'main.jsp'") === 0); 242 | 243 | // case 'redirect_to_main': 244 | // $text = $this->html->text(); 245 | // return (strpos($text, "window.parent.location.href = 'main.jsp'") === 0); 246 | 247 | // case 'table_transaction_page': 248 | // $table = $this->html->find('body > table')->eq(2)->find('table')->eq(1); 249 | // $this->configuration('temporary][table', $table); 250 | // return ($table->length > 0); 251 | } 252 | } 253 | 254 | protected function visitBefore() 255 | { 256 | parent::visitBefore(); 257 | } 258 | 259 | protected function visitAfter() 260 | { 261 | $this->configuration('bca_last_visit', date('c')); 262 | parent::visitAfter(); 263 | } 264 | 265 | protected function mandiriParseHomePageAnonymous() 266 | { 267 | switch ($this->target) { 268 | default: 269 | $form = $this->configuration('temporary][form'); 270 | $fields = $form->preparePostForm('image'); 271 | $fields['userID'] = $this->username; 272 | $fields['password'] = $this->password; 273 | unset($fields['image']); 274 | $fields += $this->mandiriPopulateImageFields(); 275 | $this->configuration('menu][mandiri_login_form][fields', $fields); 276 | 277 | // $debugname = 'fields'; echo "\r\n
" . __FILE__ . ":" . __LINE__ . "\r\n". 'var_dump(' . $debugname . '): '; var_dump($$debugname); echo "
\r\n"; 278 | 279 | // Cari tahu isian field image.x dan image.y 280 | // Kemungkinan ini adalah manipulasi javascript. 281 | // $image = $this->html->find(); 282 | 283 | 284 | // unset($fields['txtUserId']); 285 | // $fields['value(user_id)'] = $this->username; 286 | // $fields['value(pswd)'] = $this->password; 287 | // $this->addStepFromReference('login_form'); 288 | // $this->configuration('menu][bca_login_form][fields', $fields); 289 | // break; 290 | } 291 | } 292 | 293 | protected function mandiriPopulateImageFields() 294 | { 295 | // Untuk saat ini fix dulu ajah. 296 | return [ 297 | 'image.x' => '0', 298 | 'image.y' => '0', 299 | ]; 300 | 301 | } 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | } 335 | -------------------------------------------------------------------------------- /src/IBank/Modules/BCA/BCA.php: -------------------------------------------------------------------------------- 1 | parse(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'BCA-menu.yml')); 64 | $value += $yaml->parse(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'BCA-target.yml')); 65 | $value += $yaml->parse(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'BCA-reference.yml')); 66 | return $value; 67 | } catch (ParseException $e) { 68 | $this->log->error('Unable to parse the YAML string: {string}', ['string' => $e->getMessage()]); 69 | } 70 | } 71 | 72 | protected function init() 73 | { 74 | // Default nama file untuk keperluan debug. 75 | $_ = DIRECTORY_SEPARATOR; 76 | $this->configuration('temporary][browser][browser_history', '..' . $_ . 'debug' . $_ . 'history.log'); 77 | $this->configuration('temporary][browser][browser_response_body', '..' . $_ . 'debug' . $_ . 'response_body.html'); 78 | } 79 | 80 | /** 81 | * Override method. 82 | * 83 | * Set property information in object. 84 | * 85 | * @param $property string 86 | * Parameter dapat bernilai sebagai berikut: 87 | * - username 88 | * Username for login. 89 | * - password 90 | * Password for login. 91 | * - account 92 | * Account Number. 93 | * dan property lainnya yang dijelaskan pada parent::set(). 94 | */ 95 | public function set($property, $value) 96 | { 97 | switch ($property) { 98 | case 'username': 99 | case 'password': 100 | case 'account': 101 | case 'range': 102 | case 'sort': 103 | $this->{$property} = $value; 104 | break; 105 | } 106 | return parent::set($property, $value); 107 | } 108 | 109 | protected function executeBefore() 110 | { 111 | parent::executeBefore(); 112 | switch ($this->target) { 113 | case 'logout': 114 | break; 115 | 116 | default: 117 | if (null === $this->username) { 118 | $this->log->error('Username belum didefinisikan.'); 119 | throw new ExecuteException; 120 | } 121 | if (null === $this->password) { 122 | $this->log->error('Password belum didefinisikan.'); 123 | throw new ExecuteException; 124 | } 125 | break; 126 | } 127 | switch ($this->target) { 128 | case 'get_transaction': 129 | if (null === $this->range) { 130 | // BCA tidak ada mini account statement seperti BNI, maka 131 | // jika null kita anggap saja today. 132 | $this->range = 'today'; 133 | $this->log->notice('Range belum didefinisikan, otomatis mencari transaksi hari ini.'); 134 | } 135 | $this->range = Range::create($this->range); 136 | // BCA paling lama adalah awal bulan dari 2 bulan lalu. 137 | $oldest = new \DateTime('first day of 2 months ago'); 138 | if (!$this->range->comparison($oldest, 'less', 'start')) { 139 | // Tapi kalo masih di hari yang sama, ya gpp. 140 | if (!$this->range->isSameDay($oldest, 'start')) { 141 | $this->log->error('Tanggal Awal tidak boleh kurang dari hari pertama dari 2 bulan lalu: {date}', ['date' => $oldest->format('Y-m-d')]); 142 | throw new ExecuteException; 143 | } 144 | } 145 | 146 | // End date tidak boleh lewat dari hari ini. 147 | $now = new \DateTime(); 148 | if (!$this->range->comparison($now, 'greater', 'end')) { 149 | // Tapi kalo masih di hari yang sama, ya gpp. 150 | if (!$this->range->isSameDay($now, 'end')) { 151 | $this->log->error('Tanggal Akhir tidak boleh melebihi Tanggal Hari Ini.'); 152 | throw new ExecuteException; 153 | } 154 | } 155 | switch ($this->sort) { 156 | case 'asc': 157 | case 'desc': 158 | break; 159 | 160 | case 'ASC': 161 | case 'ascending': 162 | case 'ASCENDING': 163 | $this->sort = 'asc'; 164 | break; 165 | 166 | case 'descending': 167 | case 'DESC': 168 | case 'DESCENDING': 169 | $this->sort = 'desc'; 170 | break; 171 | 172 | default: 173 | $this->sort = 'desc'; 174 | $this->log->notice('Transaksi otomatis diurut dengan pola descending.'); 175 | break; 176 | } 177 | 178 | break; 179 | 180 | default: 181 | // Do something. 182 | break; 183 | } 184 | 185 | 186 | } 187 | 188 | /** 189 | * Override method. 190 | * 191 | * Set browser as mobile, and not use curl as library request. 192 | */ 193 | protected function browserInit() 194 | { 195 | parent::browserInit(); 196 | } 197 | 198 | protected function checkIndication($indication_name) 199 | { 200 | switch ($indication_name) { 201 | case 'home_page_anonymous': 202 | $form = $this->html->find('form[name=iBankForm][action=/authentication.do]'); 203 | $this->configuration('temporary][form', $form); 204 | return ($form->length > 0); 205 | 206 | case 'home_page_authenticated': 207 | return ($this->html->find('frameset')->length > 0); 208 | 209 | case 'table_exists': 210 | $table = $this->html->find('table'); 211 | $this->configuration('temporary][table', $table); 212 | return ($table->length > 0); 213 | 214 | case 'select_range_form': 215 | $form = $this->html->find('form[name=iBankForm][action=/accountstmt.do]'); 216 | $this->configuration('temporary][form', $form); 217 | return ($form->length > 0); 218 | 219 | case 'redirect_to_main': 220 | $text = $this->html->text(); 221 | return (strpos($text, "window.parent.location.href = 'main.jsp'") === 0); 222 | 223 | case 'redirect_to_main': 224 | $text = $this->html->text(); 225 | return (strpos($text, "window.parent.location.href = 'main.jsp'") === 0); 226 | 227 | case 'table_transaction_page': 228 | $table = $this->html->find('body > table')->eq(2)->find('table')->eq(1); 229 | $this->configuration('temporary][table', $table); 230 | return ($table->length > 0); 231 | } 232 | } 233 | 234 | protected function visitBefore() 235 | { 236 | parent::visitBefore(); 237 | } 238 | 239 | protected function visitAfter() 240 | { 241 | $this->configuration('bca_last_visit', date('c')); 242 | parent::visitAfter(); 243 | } 244 | 245 | /** 246 | * Session expired setelah 8 menit, sesuai dengan informasi pada 247 | * javascript pada BCA. 248 | * Alternative handler for bca_check_session. 249 | */ 250 | protected function bcaCheckSession() 251 | { 252 | $skip = false; 253 | // Menggunakan try catch, karena pembentukan object DateTime kalau gagal 254 | // akan throw Exception. 255 | try { 256 | $last_visit = $this->configuration('bca_last_visit'); 257 | if ($last_visit === null) { 258 | throw new \Exception; 259 | } 260 | $now = new \DateTime; 261 | $expired = new \DateTime($last_visit); 262 | $expired->add(new \DateInterval('PT8M')); 263 | if ($now > $expired) { 264 | throw new \Exception; 265 | } 266 | } 267 | catch (\Exception $e) { 268 | $skip = true; 269 | } 270 | if ($skip) { 271 | $this->resetExecute(); 272 | $this->addStepFromReference('home_page'); 273 | throw new StepException; 274 | } 275 | } 276 | 277 | /** 278 | * Alternative handler for bca_set_referer. 279 | */ 280 | protected function bcaSetReferer() 281 | { 282 | $menu_name = $this->step['menu']; 283 | $referer = $this->configuration("menu][bca_$menu_name][referer"); 284 | if (empty($referer)) { 285 | return; 286 | } 287 | $language = $this->configuration('language'); 288 | $part = null; 289 | switch ($language) { 290 | case 'en': 291 | $part = 'nav_bar_indo'; // Masih belum tahu. 292 | break; 293 | 294 | case 'id': 295 | $part = 'nav_bar_indo'; 296 | break; 297 | } 298 | 299 | (null === $part) or $referer = Log::interpolate($referer, ['language' => $part]); 300 | $this->browser->headers('Referer', $referer); 301 | } 302 | 303 | /** 304 | * Alternative handler for bca_method_post. 305 | */ 306 | protected function bcaMethodPost() 307 | { 308 | $this->browser->options('method', 'POST'); 309 | } 310 | 311 | /** 312 | * Alternative handler for bca_parse_home_page_anonymous. 313 | */ 314 | protected function bcaParseHomePageAnonymous() 315 | { 316 | switch ($this->target) { 317 | default: 318 | $form = $this->configuration('temporary][form'); 319 | $fields = $form->preparePostForm('value(Submit)'); 320 | unset($fields['txtUserId']); 321 | $fields['value(user_id)'] = $this->username; 322 | $fields['value(pswd)'] = $this->password; 323 | $this->addStepFromReference('login_form'); 324 | $this->configuration('menu][bca_login_form][fields', $fields); 325 | break; 326 | } 327 | } 328 | 329 | /** 330 | * Alternative handler for bca_parse_home_page_authenticated. 331 | */ 332 | protected function bcaParseHomePageAuthenticated() 333 | { 334 | switch ($this->target) { 335 | default: 336 | // Cari bahasa 337 | $src = $this->html->find('frameset > frame[name=menu]')->attr('src'); 338 | if (strpos($src, 'nav_bar_indo') === 0) { 339 | $this->configuration('language', 'id'); 340 | } 341 | else { 342 | $this->configuration('language', 'en'); 343 | } 344 | break; 345 | } 346 | } 347 | 348 | /** 349 | * Alternative handler for bca_parse_balance_inquiry_page. 350 | */ 351 | protected function bcaParseBalanceInquiryPage() 352 | { 353 | switch ($this->target) { 354 | default: 355 | $table = $this->configuration('temporary][table'); 356 | $info = $table->eq('2')->extractTable(true); 357 | $balance = isset($info[1][3]) ? $info[1][3] : null; 358 | $this->result = $balance; 359 | break; 360 | } 361 | } 362 | 363 | /** 364 | * Alternative handler for bca_parse_select_range_form. 365 | */ 366 | protected function bcaParseSelectRangeForm() 367 | { 368 | // BCA memiliki keunikan dalam pencarian mutasi rekening. 369 | // Untuk harian, hanya bisa 31 hari terakhir dari hari ini. 370 | // Setelah itu adalah keseluruhan dari awal sampai akhir hari 2 bulan 371 | // lalu, dan 1 bulan lalu. 372 | // Ribet amat, yak. 373 | // 31 hari terakhir itu berarti total hari dari sekarang adalah 31 hari. 374 | // artinya ada jeda 30 hari antara hari ini dengan hari terakhir. 375 | // Bila hari ini adalah 2 Februari 2016, maka jika hari terakhir itu 376 | // 1 Januari 2016, maka tidak valid. tapi jika 2 januari 2016, maka 377 | // valid. 378 | 379 | $form = $this->configuration('temporary][form'); 380 | $fields = $form->preparePostForm('value(submit1)'); 381 | 382 | // Cari tahu rekening. 383 | $options_rekening = $this->html->find('select[name=value(D1)] > option')->getElements(); 384 | $fields['value(D1)'] = $this->bcaSelectAccountGetValueFromOptionsElement($options_rekening); 385 | 386 | $type = null; // daily, monthly. 387 | 388 | // Check apakah ini revisit. 389 | $is_revisit = $this->configuration('temporary][revisit_account_statement_page'); 390 | if ($is_revisit) { 391 | // Copot bulan selanjutnya. 392 | $_month = key($this->range); 393 | $month = array_shift($this->range); 394 | reset($this->range); 395 | // Jika month adalah bulan ini, maka gunakan pencarian tipe harian. 396 | // Jika month adalah bulan kemarin, maka gunakan pencarian tipe bulanan. 397 | $now_month = date('Y-m'); 398 | $last_month = Range::getPrevMonth($now_month); 399 | if ($_month == $now_month) { 400 | $type = 'daily'; 401 | } 402 | elseif ($_month == $last_month) { 403 | $type = 'monthly'; 404 | } 405 | } 406 | else { 407 | // Bukan revisit, maka: 408 | $limit = new \DateTime('31 days ago'); 409 | if ($this->range->isSameDay($limit, 'start') || $this->range->comparison($limit, '<', 'start')) { 410 | $type = 'daily'; 411 | $month = $this->range; 412 | } 413 | else { 414 | $type = 'monthly'; 415 | // Set over range. 416 | $this->configuration('temporary][over_range', true); 417 | // Set need to filter. 418 | $this->configuration('temporary][filter_transaction', true); 419 | // Simpan original range, karena akan dipecah. 420 | $this->configuration('temporary][range', $this->range); 421 | // Atur ulang range. 422 | $this->range = $this->range->splitPerMonth(); 423 | $_month = key($this->range); 424 | reset($this->range); 425 | // Copot bulan pertama. 426 | $month = array_shift($this->range); 427 | // $month bisa merupakan start_date di tanggal 13 dan end date 428 | // di tanggal 31. tapi karena bca selalu di tanggal awal, maka: 429 | // Rebuild ulang, agar tanggal awal menjadi 1 dan tanggal akhir 430 | // menjadi last (28/29/30/31. 431 | $month = Range::create("first day of $_month ~ last day of $_month"); 432 | } 433 | } 434 | 435 | // Simpan informasi $month, diperlukan untuk 436 | // method ::bcaConvertDateTransaction() 437 | $this->configuration('temporary][month', $month); 438 | 439 | // Positioning. 440 | switch ($type) { 441 | case 'daily': 442 | // Pilih field "Mutasi Harian". 443 | $fields['value(r1)'] = '1'; 444 | unset($fields['value(x)']); 445 | $fields['value(startDt)'] = $month->format(self::BCA_DATE_FORMAT_DAILY_DATE, 'start'); 446 | $fields['value(startMt)'] = $month->format(self::BCA_DATE_FORMAT_DAILY_MONTH, 'start'); 447 | $fields['value(startYr)'] = $month->format(self::BCA_DATE_FORMAT_DAILY_YEAR, 'start'); 448 | $fields['value(endDt)'] = $month->format(self::BCA_DATE_FORMAT_DAILY_DATE, 'end'); 449 | $fields['value(endMt)'] = $month->format(self::BCA_DATE_FORMAT_DAILY_MONTH, 'end'); 450 | $fields['value(endYr)'] = $month->format(self::BCA_DATE_FORMAT_DAILY_YEAR, 'end'); 451 | $fields['value(fDt)'] = ''; 452 | $fields['value(tDt)'] = ''; 453 | break; 454 | 455 | case 'monthly': 456 | // Isi field r1 457 | // Pilih field "Mutasi Bulanan". 458 | $fields['value(r1)'] = '2'; 459 | // Isi field x 460 | // Harusnya sudah ada variable $_month (string) dan 461 | // $month (object Range). 462 | $now_month = date('Y-m'); 463 | $last_month = Range::getPrevMonth($now_month); 464 | $two_last_month = Range::getPrevMonth($last_month); 465 | if ($last_month == $_month) { 466 | $fields['value(x)'] = '1'; 467 | } 468 | elseif ($two_last_month == $_month) { 469 | $fields['value(x)'] = '2'; 470 | } 471 | // Isi field fDt & tDt. 472 | $fields['value(fDt)'] = $month->format(self::BCA_DATE_FORMAT, 'start'); 473 | $fields['value(tDt)'] = $month->format(self::BCA_DATE_FORMAT, 'end'); 474 | 475 | // Anomali BCA. 476 | // Kasus seperti ini: milih request bulan desember 2015 477 | // lalu oleh javascriptnya BCA dimodifikasi menjadi tanggal masa 478 | // depan yakni menjadi 01122016 ~ 31122016. dan saat disubmit 479 | // ternyata post fieldnya bener-bener tanggal masa depan yakni 480 | // 01122016 ~ 31122016. Tapi respon yang muncul tetap ke desember 481 | // 2015 (01122015 ~ 31122015). Hadeh, ke-tidakkonsisten-an ini 482 | // mengganggu coding. 483 | // Hack dimulai: 484 | // Modifikasi, tahun apapun menjadi tahun saat ini. 485 | $fields['value(fDt)'] = substr($fields['value(fDt)'], 0, -4) . date('Y'); 486 | $fields['value(tDt)'] = substr($fields['value(tDt)'], 0, -4) . date('Y'); 487 | 488 | unset($fields['value(startDt)']); 489 | unset($fields['value(endDt)']); 490 | unset($fields['value(startMt)']); 491 | unset($fields['value(endMt)']); 492 | unset($fields['value(startYr)']); 493 | unset($fields['value(endYr)']); 494 | break; 495 | 496 | default: 497 | // Do something. 498 | break; 499 | } 500 | // Set ke menu. 501 | $this->configuration("menu][bca_account_statement_page_view][fields", $fields); 502 | 503 | 504 | } 505 | 506 | protected function bcaParseRedirectToMain() 507 | { 508 | switch ($this->target) { 509 | default: 510 | $this->configuration('bca_last_visit', null); 511 | $this->resetExecute(); 512 | $this->addStepFromReference('home_page'); 513 | break; 514 | } 515 | } 516 | 517 | /** 518 | * Jika tidak ditemukan, maka akan throw ke VisitException. 519 | */ 520 | protected function bcaSelectAccountGetValueFromOptionsElement(Array $element_options) 521 | { 522 | $found = false; 523 | while ($each = array_shift($element_options)) { 524 | $extract = ParseHTMLAdvanced::extract($each); 525 | $text = ParseHTMLAdvanced::extractValueOnly($each); 526 | if ($text == $this->account) { 527 | if (isset($extract['a']['value'])) { 528 | $found = $extract['a']['value']; 529 | break; 530 | } 531 | } 532 | } 533 | if (false === $found) { 534 | $this->log->error('Nomor Rekening tidak ditemukan.'); 535 | throw new VisitException; 536 | } 537 | return $found; 538 | } 539 | 540 | 541 | protected function bcaFilterTransactionTable($tables) 542 | { 543 | $ref = IBank::reference('table_header_account_statement'); 544 | $ref = array_flip($ref); 545 | 546 | $transactions = []; 547 | while (!empty($tables)) { 548 | $transaction = []; 549 | $table = array_shift($tables); 550 | if (count($table) == 6) { 551 | list($tgl, $keterangan, $cab, $mutasi_1, $mutasi_2, $saldo) = $table; 552 | $transaction['date'] = $this->bcaConvertDateTransaction($tgl); 553 | $keterangan = implode('', $keterangan); 554 | $keterangan = preg_replace('/<[^>]+>/', ' ', $keterangan); 555 | $keterangan = preg_replace('/\s\s+/', ' ', $keterangan); 556 | $keterangan = trim($keterangan); 557 | $transaction['description'] = $keterangan; 558 | $transaction['bca_branch'] = $cab; 559 | $transaction['bca_date'] = $tgl; 560 | $transaction['amount'] = $mutasi_1; 561 | $transaction['type'] = $mutasi_2; 562 | $transaction['balance'] = $saldo; 563 | $transaction['no'] = null; 564 | $transaction['id'] = null; 565 | } 566 | $transactions[] = array_merge($ref, $transaction); 567 | } 568 | return $transactions; 569 | } 570 | 571 | protected function bcaParseTransactionPage() 572 | { 573 | $table = $this->configuration('temporary][table'); 574 | $info = $table->extractTable(true); 575 | 576 | // Buang baris awal. 577 | array_shift($info); 578 | $transaction = $this->bcaFilterTransactionTable($info); 579 | 580 | $temporary_result = $this->configuration('temporary][result'); 581 | if (null === $temporary_result) { 582 | $temporary_result = []; 583 | } 584 | $temporary_result = array_merge($temporary_result, $transaction); 585 | $this->configuration('temporary][result', $temporary_result); 586 | } 587 | 588 | /** 589 | * bca_error_login 590 | */ 591 | protected function bcaErrorLogin() 592 | { 593 | $elements = $this->html->find('script')->getElements(); 594 | $error = 'Error login from server.'; 595 | foreach ($elements as $element) { 596 | if (strpos($element, 'alert(err);') !== false) { 597 | if (preg_match('/var err=\'(.*)\';/', $element, $m)) { 598 | $error = $m[1]; 599 | } 600 | } 601 | } 602 | $this->log->error($error); 603 | throw new ExecuteException; 604 | } 605 | 606 | /** 607 | * bca_check_over_range 608 | */ 609 | protected function bcaCheckOverRange() 610 | { 611 | $finish = false; 612 | $is_over_range = $this->configuration('temporary][over_range'); 613 | if ($is_over_range) { 614 | if (empty($this->range)) { 615 | // Hapus informasi over_range. 616 | $this->configuration('temporary][over_range', false); 617 | // Tambah step finishing. 618 | $finish = true; 619 | } 620 | else { 621 | $this->addStepFromReference('revisit_account_statement_page'); 622 | $this->configuration('temporary][revisit_account_statement_page', true); 623 | } 624 | } 625 | else { 626 | $finish = true; 627 | } 628 | 629 | if ($finish) { 630 | $this->addStepFromReference('transaction_finishing'); 631 | } 632 | } 633 | 634 | /** 635 | * bca_transaction_finishing 636 | */ 637 | protected function bcaTransactionFinishing() 638 | { 639 | $temporary_result = $this->configuration('temporary][result'); 640 | $this->result = $temporary_result; 641 | 642 | if ($this->configuration('temporary][filter_transaction')) { 643 | $this->addStepFromReference('filter_transaction'); 644 | } 645 | // Hasil BCA selalu ascending. 646 | if ($this->sort == 'desc') { 647 | krsort($this->result); 648 | $this->result = array_values($this->result); 649 | } 650 | } 651 | 652 | protected function bcaClearLastVisit() 653 | { 654 | $this->configuration('bca_last_visit', null); 655 | $this->result = 'Logout Success'; 656 | } 657 | 658 | protected function bcaFilterTransaction() 659 | { 660 | $result = []; 661 | // Get original range. 662 | $range = $this->configuration('temporary][range'); 663 | foreach ($this->result as $each) { 664 | $date = new \DateTime($each['date']); 665 | if ($range->isBetween($date)) { 666 | $result[] = $each; 667 | } 668 | unset($date); 669 | } 670 | $this->result = $result; 671 | } 672 | 673 | /** 674 | * 675 | * Beberapa contoh string yang ada di BCA adalah 676 | * - 12/01 677 | * artinya 12 januari tahun ini. 678 | * - PEND 679 | * artinya **mungkin** tanggal hari ini, mungkin kemarin, karena ini 680 | * muncul pada transaksi yang baru saja terjadi. 681 | */ 682 | protected function bcaConvertDateTransaction($string) 683 | { 684 | $parse = $this->_bcaConvertDateTransaction($string); 685 | if (false === $parse) { 686 | $this->log->error('Gagal mengenali date: {name}', ['name' => $string]); 687 | throw new VisitException; 688 | } 689 | if ('' === $parse) { 690 | return ''; 691 | } 692 | list($d, $m) = $parse; 693 | $is_over_range = $this->configuration('temporary][over_range'); 694 | if ($is_over_range) { 695 | $month = $this->configuration('temporary][month'); 696 | $Y = $month->format('Y', 'end'); 697 | } 698 | else { 699 | // Jika tidak over range, maka tipe transaction 700 | // hanya daily. 701 | $Y = $this->range->format('Y', 'end'); 702 | $end_month = $this->range->format('m', 'end'); 703 | if ($m == '12' && $end_month == '01') { 704 | $Y -= 1; 705 | } 706 | } 707 | return "$Y-$m-$d"; 708 | } 709 | 710 | protected function _bcaConvertDateTransaction($string) 711 | { 712 | $result = false; 713 | $string = trim($string); 714 | if ($string === 'PEND') { 715 | $result = ''; 716 | } 717 | elseif (preg_match('/^(\d{1,2})\/(\d{1,2})$/', $string, $m)) { 718 | // Perlu valid date. 719 | $result = array($m[1], $m[2]); 720 | } 721 | return $result; 722 | } 723 | } 724 | -------------------------------------------------------------------------------- /src/IBank/Modules/BNI/BNI.php: -------------------------------------------------------------------------------- 1 | parse(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'BNI-menu.yml')); 62 | $value += $yaml->parse(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'BNI-target.yml')); 63 | $value += $yaml->parse(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'BNI-reference.yml')); 64 | return $value; 65 | } catch (ParseException $e) { 66 | $this->log->error('Unable to parse the YAML string: {string}', ['string' => $e->getMessage()]); 67 | } 68 | } 69 | 70 | protected function init() 71 | { 72 | // Default nama file untuk keperluan debug. 73 | $_ = DIRECTORY_SEPARATOR; 74 | $this->configuration('temporary][browser][browser_history', '..' . $_ . 'debug' . $_ . 'history.log'); 75 | $this->configuration('temporary][browser][browser_response_body', '..' . $_ . 'debug' . $_ . 'response_body.html'); 76 | } 77 | 78 | /** 79 | * Override method. 80 | * 81 | * Set property information in object. 82 | * 83 | * @param $property string 84 | * Parameter dapat bernilai sebagai berikut: 85 | * - username 86 | * Username for login. 87 | * - password 88 | * Password for login. 89 | * - account 90 | * Account Number. 91 | * dan property lainnya yang dijelaskan pada parent::set(). 92 | */ 93 | public function set($property, $value) 94 | { 95 | switch ($property) { 96 | case 'username': 97 | case 'password': 98 | case 'account': 99 | case 'range': 100 | case 'sort': 101 | $this->{$property} = $value; 102 | break; 103 | } 104 | return parent::set($property, $value); 105 | } 106 | 107 | /** 108 | * Override parent::executeBefore() 109 | * Verifikasi kebutuhan sebelum melanjutkan execute. 110 | * 111 | * Catatan tentang validitas mutasi rekening di BNI. 112 | * 113 | * - Tanggal transaksi paling lama yang bisa diambil adalah 6 bulan dari 114 | * hari ini. Jika sekarang tanggal 25 januari 2016, maka bila start 115 | * date 25-Jul-2015, error yang muncul adalah: "Tanggal Awal tidak boleh 116 | * Melebihi 6 Bulan dari Tanggal Hari Ini" dan akan valid jika start_date 117 | * 24-Jul-2016. 118 | * 119 | * - End date yang lewat dari hari ini, maka error yang muncul adalah: 120 | * "Tanggal Akhir tidak boleh melebihi tanggal hari ini". 121 | * 122 | * - Tiap sekali request, maka interval tidak boleh lebih 31 hari, jika 123 | * lebih dari 31 hari, error yang muncul adalah: "Transaksi anda tidak 124 | * dapat diproses. Periode tanggal yang anda pilih lebih dari 31 hari. 125 | * Silahkan masukkan periode tanggal sesuai ketentuan.". 126 | * 127 | * - Untuk support interval lebih dari 31 hari, maka module BNI melakukan 128 | * split interval, kemudian melakukan request secara looping. 129 | * 130 | * - Tanggal yang tidak valid (contoh 32-Jul-2015) atau format yang tidak 131 | * valid (contoh 1-Aug-2015) maka error yang muncul adalah "Tanggal Akhir 132 | * harus menggunakan format yang telah ditentukan dan tanggal yang 133 | * valid". 134 | */ 135 | protected function executeBefore() 136 | { 137 | parent::executeBefore(); 138 | if (null === $this->username) { 139 | $this->log->error('Username belum didefinisikan.'); 140 | throw new ExecuteException; 141 | } 142 | if (null === $this->password) { 143 | $this->log->error('Password belum didefinisikan.'); 144 | throw new ExecuteException; 145 | } 146 | switch ($this->target) { 147 | case 'get_range_transaction': 148 | if (null === $this->range) { 149 | $this->log->error('Range belum didefinisikan.'); 150 | throw new ExecuteException; 151 | } 152 | switch ($this->range) { 153 | case 'now': 154 | case 'today': 155 | case 'last week': 156 | case 'last month': 157 | break; 158 | 159 | default: 160 | // Verifikasi rangenya. 161 | $this->range = Range::create($this->range); 162 | if (!$this->range->is_start_valid) { 163 | $this->log->notice('Tanggal awal tidak valid. Tanggal otomatis diganti menjadi waktu saat ini.'); 164 | } 165 | if (!$this->range->is_end_valid) { 166 | $this->log->notice('Tanggal awal tidak valid. Tanggal otomatis diganti menjadi waktu saat ini.'); 167 | } 168 | // Start date tidak boleh lebih dari 6 bulan sejak hari 169 | // ini. 170 | $oldest = new \DateTime('6 month ago'); 171 | // Masih kudu dikurangi satu hari lagi agar tidak 172 | // error (lihat catatan pada doc comment fungsi ini). 173 | $oldest->sub(new \DateInterval('P1D')); 174 | if (!$this->range->isSameDay($oldest, 'start') && !$this->range->comparison($oldest, 'less', 'start')) { 175 | $this->log->error('Tanggal Awal tidak boleh kurang dari 6 bulan lalu: {date}', ['date' => $oldest->format('Y-m-d')]); 176 | throw new ExecuteException; 177 | } 178 | 179 | // End date tidak boleh lewat dari hari ini. 180 | $now = new \DateTime(); 181 | if (!$this->range->isSameDay($now, 'end') && !$this->range->comparison($now, 'greater', 'end')) { 182 | $this->log->error('Tanggal Akhir tidak boleh melebihi Tanggal Hari Ini.'); 183 | throw new ExecuteException; 184 | } 185 | } 186 | 187 | case 'get_last_transaction': 188 | switch ($this->sort) { 189 | case 'asc': 190 | case 'desc': 191 | break; 192 | 193 | case 'ASC': 194 | case 'ascending': 195 | case 'ASCENDING': 196 | $this->sort = 'asc'; 197 | break; 198 | 199 | case 'descending': 200 | case 'DESC': 201 | case 'DESCENDING': 202 | $this->sort = 'desc'; 203 | break; 204 | 205 | default: 206 | $this->sort = 'desc'; 207 | $this->log->notice('Transaksi otomatis diurut dengan pola descending.'); 208 | break; 209 | } 210 | break; 211 | 212 | default: 213 | // Do something. 214 | break; 215 | } 216 | 217 | 218 | switch ($this->target) { 219 | 220 | } 221 | } 222 | 223 | protected function executeAfter() 224 | { 225 | parent::executeAfter(); 226 | // Memastikan bahwa url home sudah ada pada configuration. 227 | $url = $this->configuration('menu][bni_home_page][url'); 228 | if (null === $url && null !== $this->html) { 229 | $form = $this->html->find('form'); 230 | $url = $form->attr('action'); 231 | $fields = $form->preparePostForm('__HOME__'); 232 | $this->configuration('menu][bni_home_page][url', $url); 233 | $this->configuration('menu][bni_home_page][fields', $fields); 234 | } 235 | } 236 | 237 | /** 238 | * Override method. 239 | * 240 | * Set browser as mobile, and not use curl as library request. 241 | */ 242 | protected function browserInit() 243 | { 244 | parent::browserInit(); 245 | $user_agent_mobile = $this->configuration('user_agent_mobile'); 246 | if (!$user_agent_mobile) { 247 | $user_agent = $this->browser->getUserAgent('Mobile Browser'); 248 | $this->configuration('user_agent', $user_agent); 249 | $this->configuration('user_agent_mobile', true); 250 | $this->browser->options('user_agent', $user_agent); 251 | } 252 | $this->browser->curl(false); 253 | } 254 | 255 | /** 256 | * 257 | */ 258 | protected function visitAfter() 259 | { 260 | $this->configuration('bni_last_visit', date('c')); 261 | // Untuk semua visit. 262 | // Hapus url, agar tidak tersimpan di configuration. 263 | // Karena url bersifat dinamis. 264 | $menu_name = $this->step['menu']; 265 | $this->configuration("menu][$menu_name][url", null); 266 | // Baru jalankan parent. 267 | parent::visitAfter(); 268 | } 269 | 270 | /** 271 | * Karena visitAfter menghapus url, maka kembalikan default. 272 | */ 273 | protected function resetExecuteAfter() 274 | { 275 | $this->configuration('menu][bni_home_page][url', self::BNI_MAIN_URL); 276 | } 277 | 278 | /** 279 | * Memastikan bahwa halaman mengandung indikasi yang dibutuhkan untuk 280 | * nantinya bisa diparsing sesuai dengan target. 281 | */ 282 | protected function checkIndication($indication_name) 283 | { 284 | switch ($indication_name) { 285 | case '404_page': 286 | $text = $this->html->find('span#Step1')->text(); 287 | $position = strpos($text, '404'); 288 | return is_int($position); 289 | 290 | case 'home_page_authenticated': 291 | return ($this->html->find('span#CurrentProfileDisp')->length > 0); 292 | 293 | case 'home_page_anonymous': 294 | return ($this->html->find('table#Language_table')->length > 0); 295 | 296 | case 'form_exists': 297 | return ($this->html->find('form')->length > 0); 298 | 299 | case 'login_error': 300 | case 'select_range_error': 301 | return ($this->html->find('#Display_MConError')->length > 0); 302 | 303 | case 'table_balance': 304 | return ($this->html->find('table[id~=BalanceDisplayTable]')->eq(1)->length > 0); 305 | 306 | case 'table_account': 307 | return ($this->html->find('table#AccountMenuList_table')->length > 0); 308 | 309 | case 'mini_statement_select_account_number': 310 | return ($this->html->find('select[name=MiniStmt]')->length > 0); 311 | 312 | case 'mini_statement_page': 313 | return ($this->html->find('input[name=PageName][value=OperMiniAccIDSelectRq]')->length > 0); 314 | 315 | case 'select_range_page': 316 | return ($this->html->find('#Search_Criteria_tr')->length > 0); 317 | 318 | case 'table_transaction_page': 319 | return ($this->html->find('input[name=page][value=FullStmtInqRq]')->length > 0); 320 | 321 | case 'session_destroy': 322 | return ($this->html->find('input[name=page][value=SessionErrorMessage]')->length > 0); 323 | } 324 | } 325 | 326 | /** 327 | * Parsing menu "home_page" context "authenticated" sesuai dengan kebutuhan 328 | * pada property $target. 329 | * Alternative handler for bni_parse_home_page_authenticated. 330 | */ 331 | protected function bniParseHomePageAuthenticated() 332 | { 333 | switch ($this->target) { 334 | case 'get_balance': 335 | case 'get_last_transaction': 336 | case 'get_range_transaction': 337 | $url_account_page = $this->html->find('td a')->eq(0)->attr('href'); 338 | if (empty($url_account_page)) { 339 | $this->log->error('Url for menu "account_page" not found.'); 340 | throw new VisitException; 341 | } 342 | $this->configuration('menu][bni_account_page][url', $url_account_page); 343 | break; 344 | } 345 | } 346 | 347 | /** 348 | * Parsing menu "home_page" context "anonymous" sesuai dengan kebutuhan 349 | * pada property $target. 350 | * Alternative handler for bni_parse_home_page_anonymous. 351 | */ 352 | protected function bniParseHomePageAnonymous() 353 | { 354 | switch ($this->target) { 355 | // Apapun targetnya, aktivitasnya sama. 356 | default: 357 | // Belum login, maka tambah langkah baru. 358 | $this->addStepFromReference('home_page'); 359 | 360 | // Cari tahu bahasa situs, penting untuk parsing yang 361 | // terkait bahasa. 362 | if ($this->html->find('span.Languageleftselect')->length) { 363 | $this->configuration('language', 'id'); 364 | } 365 | elseif ($this->html->find('span.Languagerightselect')->length) { 366 | $this->configuration('language', 'en'); 367 | } 368 | 369 | // Cari url untuk ke halaman login_page. 370 | $url_login_page = $this->html->find('#RetailUser')->attr('href'); 371 | if (empty($url_login_page)) { 372 | $this->log->error('Url for menu login_page not found.'); 373 | throw new VisitException; 374 | } 375 | $this->configuration('menu][bni_login_page][url', $url_login_page); 376 | break; 377 | } 378 | } 379 | 380 | /** 381 | * Parsing menu "login_page" sesuai dengan kebutuhan 382 | * pada property $target. 383 | * Alternative handler for bni_parse_login_page. 384 | */ 385 | protected function bniParseLoginPage() 386 | { 387 | switch ($this->target) { 388 | // Apapun targetnya, aktivitasnya sama. 389 | default: 390 | $url = $this->html->find('form')->attr('action'); 391 | if (empty($url)) { 392 | $this->log->error('Url for form "login_form" not found.'); 393 | throw new VisitException; 394 | } 395 | if (empty($this->username) || empty($this->password)) { 396 | $this->log->error('Username and Password required.'); 397 | throw new VisitException; 398 | } 399 | $fields = $this->html->find('form')->extractForm(); 400 | $fields['__AUTHENTICATE__'] = 'Login'; 401 | $fields['CorpId'] = $this->username; 402 | $fields['PassWord'] = $this->password; 403 | $this->configuration('menu][bni_login_form][url', $url); 404 | $this->configuration('menu][bni_login_form][fields', $fields); 405 | break; 406 | } 407 | } 408 | 409 | /** 410 | * Parsing menu "account_page" sesuai dengan kebutuhan 411 | * pada property $target. 412 | * Alternative handler for bni_parse_account_page. 413 | */ 414 | protected function bniParseAccountPage() 415 | { 416 | switch ($this->target) { 417 | case 'get_balance': 418 | $url_balance_inquiry_page = $this->html->find('td a')->eq(0)->attr('href'); 419 | if (empty($url_balance_inquiry_page)) { 420 | $this->log->error('Url for menu "balance_inquiry_page" not found.'); 421 | throw new VisitException; 422 | } 423 | $this->configuration('menu][bni_balance_inquiry_page][url', $url_balance_inquiry_page); 424 | break; 425 | 426 | case 'get_last_transaction': 427 | $url_mini_statement_page = $this->html->find('td a')->eq(1)->attr('href'); 428 | if (empty($url_mini_statement_page)) { 429 | $this->log->error('Url for menu "mini_statement_page" not found.'); 430 | throw new VisitException; 431 | } 432 | $this->configuration('menu][bni_mini_statement_page][url', $url_mini_statement_page); 433 | break; 434 | 435 | case 'get_range_transaction': 436 | $url_transaction_history_page = $this->html->find('td a')->eq(2)->attr('href'); 437 | if (empty($url_transaction_history_page)) { 438 | $this->log->error('Url for menu "transaction_history_page" not found.'); 439 | throw new VisitException; 440 | } 441 | $this->configuration('menu][bni_transaction_history_page][url', $url_transaction_history_page); 442 | break; 443 | } 444 | } 445 | 446 | /** 447 | * Parsing menu "account_type_form" sesuai dengan kebutuhan 448 | * pada property $target. 449 | * Alternative handler for bni_parse_account_type_form. 450 | */ 451 | protected function bniParseAccountTypeForm() 452 | { 453 | switch ($this->target) { 454 | case 'get_balance': 455 | case 'get_last_transaction': 456 | case 'get_range_transaction': 457 | $form = $this->html->find('form'); 458 | $url = $form->attr('action'); 459 | if (empty($url)) { 460 | $this->log->error('Url for form "account_type_form" not found.'); 461 | throw new VisitException; 462 | } 463 | $fields = $form->preparePostForm('AccountIDSelectRq'); 464 | // 465 | $fields['MAIN_ACCOUNT_TYPE'] = $this->account_type; 466 | $this->configuration('menu][bni_account_type_form][url', $url); 467 | $this->configuration('menu][bni_account_type_form][fields', $fields); 468 | break; 469 | } 470 | } 471 | 472 | /** 473 | * Parsing menu "account_number_form" sesuai dengan kebutuhan 474 | * pada property $target. 475 | * Alternative handler for bni_parse_account_number_form. 476 | */ 477 | protected function bniParseAccountNumberForm() 478 | { 479 | switch ($this->target) { 480 | case 'get_balance': 481 | $form = $this->html->find('form'); 482 | $url = $form->attr('action'); 483 | if (empty($url)) { 484 | $this->log->error('Url for form "account_number_form" not found.'); 485 | throw new VisitException; 486 | } 487 | $fields = $form->preparePostForm('BalInqRq'); 488 | // Todo, support multi account number. 489 | // $fields['acc1'] = ''; 490 | $this->configuration('menu][bni_account_number_form][url', $url); 491 | $this->configuration('menu][bni_account_number_form][fields', $fields); 492 | break; 493 | 494 | case 'get_last_transaction': 495 | $form = $this->html->find('form'); 496 | $url = $form->attr('action'); 497 | if (empty($url)) { 498 | $this->log->error('Url for form "account_number_form" not found.'); 499 | throw new VisitException; 500 | } 501 | $fields = $form->preparePostForm('Go'); 502 | // Cari nomor rekening. 503 | $value = null; 504 | foreach ($fields['MiniStmt'] as $number) { 505 | if (strpos($number, $this->account) !== false) { 506 | $value = $number; 507 | } 508 | } 509 | $fields['MiniStmt'] = $value; 510 | $this->configuration('menu][bni_account_number_form][url', $url); 511 | $this->configuration('menu][bni_account_number_form][fields', $fields); 512 | break; 513 | } 514 | } 515 | 516 | /** 517 | * Parsing menu "balance_inquiry_page" sesuai dengan kebutuhan 518 | * pada property $target. 519 | * Alternative handler for bni_parse_balance_inquiry_page. 520 | */ 521 | protected function bniParseBalanceInquiryPage() 522 | { 523 | switch ($this->target) { 524 | case 'get_balance': 525 | // Get Balance. 526 | $indication_table_balance = $this->html->find('table[id~=BalanceDisplayTable]')->eq(1); 527 | $span = $indication_table_balance->find('tr#Row5_5 td#Row5_5_column2 span'); 528 | $balance = $span->text(); 529 | $this->result = $balance; 530 | 531 | // Keep information of home_page 532 | $this->bniSaveUrlHomePage(); 533 | break; 534 | } 535 | } 536 | 537 | // Keep information of home_page 538 | protected function bniSaveUrlHomePage() 539 | { 540 | $form = $this->html->find('form'); 541 | $url = $form->attr('action'); 542 | $fields = $form->preparePostForm('__HOME__'); 543 | $this->configuration('menu][bni_home_page][url', $url); 544 | $this->configuration('menu][bni_home_page][fields', $fields); 545 | } 546 | 547 | /** 548 | * Parsing menu "404_page" sesuai dengan kebutuhan 549 | * pada property $target. 550 | * Alternative handler for bni_parse_404_page. 551 | */ 552 | protected function bniParse404Page() 553 | { 554 | switch ($this->target) { 555 | default: 556 | // 404 terjadi, maka tambah langkah baru. 557 | // Temukan url login. 558 | $url = $this->html->find('a#Login')->attr('href'); 559 | if (empty($url)) { 560 | $this->log->error('Url for menu "login_page" not found.'); 561 | throw new VisitException; 562 | } 563 | $this->configuration('menu][bni_login_page][url', $url); 564 | $this->addStepFromReference('404_page'); 565 | break; 566 | } 567 | } 568 | 569 | /** 570 | * Alternative handler for bni_parse_login_form_error. 571 | */ 572 | protected function bniParseLoginFormError() 573 | { 574 | $text = $this->html->find('#Display_MConError')->text(); 575 | $text = preg_replace('/\s\s+/', ' ', $text); 576 | $text = trim($text); 577 | $this->log->error('Login failed. Message: {text}', ['text' => $text]); 578 | throw new VisitException; 579 | } 580 | 581 | /** 582 | * Alternative handler for bni_parse_select_range_error. 583 | */ 584 | protected function bniParseSelectRangeError() 585 | { 586 | $text = $this->html->find('#Display_MConError')->text(); 587 | $text = preg_replace('/\s\s+/', ' ', $text); 588 | $text = trim($text); 589 | $this->log->error('Select Range failed. Message: {text}', ['text' => $text]); 590 | throw new VisitException; 591 | } 592 | 593 | /** 594 | * Alternative handler for bni_parse_mini_statement_page. 595 | */ 596 | protected function bniParseMiniStatementPage() 597 | { 598 | switch ($this->target) { 599 | case 'get_last_transaction': 600 | $language = $this->configuration('language'); 601 | $tables = $this->html->find('div#TitleBar > table')->extractTable(true); 602 | if (empty($tables)) { 603 | $this->log->error('Table for Statement not found.'); 604 | throw new VisitException; 605 | } 606 | $transactions = []; 607 | while ($table = array_shift($tables)) { 608 | if (isset($table[0]) && isset($table[1])) { 609 | $info = $language == 'id' ? $this->bniTranslate($table[0]) : $table[0]; 610 | $value = $table[1]; 611 | switch ($info) { 612 | case 'Transaction Date': 613 | $transaction = []; 614 | $transaction['date'] = $value; 615 | break; 616 | 617 | case 'Transaction Remarks': 618 | $transaction['detail'] = $value; 619 | break; 620 | 621 | case 'Amount type': 622 | $transaction['type'] = $value; 623 | break; 624 | 625 | case 'Amount': 626 | $transaction['amount'] = $value; 627 | break; 628 | 629 | case 'Account Balance': 630 | $transaction['balance'] = $value; 631 | $transactions[] = $transaction; 632 | break; 633 | } 634 | } 635 | } 636 | // Sort. 637 | if ($this->sort == 'asc') { 638 | krsort($transactions); 639 | $transactions = array_values($transactions); 640 | } 641 | // Set to result. 642 | $this->result = $transactions; 643 | 644 | // Keep information of home_page. 645 | $this->bniSaveUrlHomePage(); 646 | // Tapi ada yang perlu diedit, karena isian pilih rekening 647 | // ternyata element select sehingga perlu kita ganti. 648 | $fields = $this->configuration('menu][bni_home_page][fields'); 649 | $value = null; 650 | foreach ($fields['MiniStmt'] as $number) { 651 | if (strpos($number, $this->account) !== false) { 652 | $value = $number; 653 | } 654 | } 655 | $fields['MiniStmt'] = $value; 656 | $this->configuration('menu][bni_home_page][fields', $fields); 657 | break; 658 | } 659 | } 660 | 661 | /** 662 | * Parsing menu "select_range_page" sesuai dengan kebutuhan 663 | * pada property $target. 664 | * Alternative handler for bni_parse_select_range_page. 665 | */ 666 | protected function bniParseSelectRangePage() 667 | { 668 | switch ($this->target) { 669 | case 'get_range_transaction': 670 | $form = $this->html->find('form'); 671 | $url = $form->attr('action'); 672 | $fields = $form->preparePostForm('FullStmtInqRq'); 673 | // Untuk kasus range yang spesifik dan sering digunakan, maka 674 | // tidak perlu diconvert ke object Range. Langsung aja, 675 | // gak pake lama. 676 | $fields['Search_Option'] = 'TxnPrd'; 677 | switch ($this->range) { 678 | case 'now': 679 | case 'today': 680 | $fields['TxnPeriod'] = 'Today'; 681 | break; 682 | 683 | case 'last week': 684 | $fields['TxnPeriod'] = 'LastWeek'; 685 | break; 686 | 687 | case 'last month': 688 | $fields['TxnPeriod'] = 'LastMonth'; 689 | break; 690 | 691 | default: 692 | $fields['TxnPeriod'] = '-1'; 693 | $fields['Search_Option'] = 'Date'; 694 | 695 | // Aturan BNI adalah sekali request, maka total maksimal 696 | // 31 hari, berarti antara start dan end ada 30 hari. 697 | if ($this->range->isSameMonth() || $this->range->diff()->days <= 30) { 698 | // tidak perlu dipecah. 699 | $fields['txnSrcFromDate'] = $this->range->format(self::BNI_DATE_FORMAT, 'start'); 700 | $fields['txnSrcToDate'] = $this->range->format(self::BNI_DATE_FORMAT, 'end'); 701 | } 702 | else { 703 | // Untuk interval lebih dari 30 hari, maka kita perlu 704 | // pecah menjadi per bulan. 705 | $this->configuration('temporary][over_range', true); 706 | $this->range = $this->range->splitPerMonth(); 707 | // Hasil split per month adalah asc, maka sesuaikan 708 | if ($this->sort == 'desc') { 709 | krsort($this->range); 710 | } 711 | 712 | $first = array_shift($this->range); 713 | $fields['txnSrcFromDate'] = $first->format(self::BNI_DATE_FORMAT, 'start'); 714 | $fields['txnSrcToDate'] = $first->format(self::BNI_DATE_FORMAT, 'end'); 715 | } 716 | break; 717 | } 718 | $this->configuration('menu][bni_select_range_form][url', $url); 719 | $this->configuration('menu][bni_select_range_form][fields', $fields); 720 | break; 721 | } 722 | } 723 | 724 | /** 725 | * Alternative handler for bni_parse_transaction_page. 726 | */ 727 | protected function bniParseTransactionPage() 728 | { 729 | switch ($this->target) { 730 | case 'get_range_transaction': 731 | $tables = $this->html->extractTable(true); 732 | $transaction = $this->bniFilterTransactionTable($tables); 733 | $temporary_result = $this->configuration('temporary][result'); 734 | if (null === $temporary_result) { 735 | $temporary_result = []; 736 | } 737 | $temporary_result = array_merge($temporary_result, $transaction); 738 | $this->configuration('temporary][result', $temporary_result); 739 | break; 740 | } 741 | } 742 | 743 | /** 744 | * Untuk range yang lebih dari 31 hari, maka perlu dilakukan split. 745 | * Alternative handler for 746 | * bni_parse_if_over_range_then_save_select_range_page_location. 747 | */ 748 | protected function bniParseIfOverRangeThenSaveSelectRangePageLocation() 749 | { 750 | $is_over_range = $this->configuration('temporary][over_range'); 751 | if ($is_over_range) { 752 | // Cek lagi apakah sudah selesai loopingnya. 753 | // looping akan dikurangi oleh handler 754 | // bni_parse_select_range_page_revisited 755 | if (empty($this->range)) { 756 | // Hapus informasi over_range. 757 | $this->configuration('temporary][over_range', false); 758 | return; 759 | } 760 | 761 | $form = $this->html->find('form'); 762 | $url = $form->attr('action'); 763 | 764 | $fields = $form->preparePostForm('__BACK__'); 765 | $this->configuration('menu][bni_select_range_page][url', $url); 766 | $this->configuration('menu][bni_select_range_page][fields', $fields); 767 | } 768 | } 769 | 770 | /** 771 | * Alternative handler for 772 | * bni_parse_if_has_next_page_then_visit_transaction_next_page_prepend. 773 | */ 774 | protected function bniParseIfHasNextPageThenVisitTransactionNextPagePrepend() 775 | { 776 | // cari link berikutnya 777 | try { 778 | $url_raw = $this->html->find('a#NextData')->attr('href'); 779 | if (null === $url_raw) { 780 | throw new \Exception; 781 | } 782 | preg_match('/^javascript\:fnCallAJAX\(\'(.*)\'\)$/', $url_raw, $m); 783 | if (empty($m) || !array_key_exists(1, $m)) { 784 | throw new \Exception; 785 | } 786 | $url = $m[1]; 787 | $this->addStepFromReference('transaction_next_page'); 788 | $this->configuration('menu][bni_transaction_next_page][url', $url); 789 | } 790 | catch (\Exception $e) { 791 | // Stop. 792 | } 793 | } 794 | 795 | /** 796 | * Alternative handler for 797 | * bni_parse_if_over_range_then_visit_select_range_page_append. 798 | */ 799 | protected function bniParseIfOverRangeThenVisitSelectRangePageAppend() 800 | { 801 | $is_over_range = $this->configuration('temporary][over_range'); 802 | if ($is_over_range) { 803 | $this->addStepFromReference('revisit_select_range_page'); 804 | } 805 | } 806 | 807 | /** 808 | * Alternative handler for: bni_parse_select_range_page_revisited 809 | */ 810 | protected function bniParseSelectRangePageRevisited() 811 | { 812 | $next = array_shift($this->range); 813 | $form = $this->html->find('form'); 814 | $url = $form->attr('action'); 815 | $fields = $form->preparePostForm('FullStmtInqRq'); 816 | $fields['TxnPeriod'] = '-1'; 817 | $fields['Search_Option'] = 'Date'; 818 | $fields['txnSrcFromDate'] = $next->format(self::BNI_DATE_FORMAT, 'start'); 819 | $fields['txnSrcToDate'] = $next->format(self::BNI_DATE_FORMAT, 'end'); 820 | $this->addStepFromReference('revisit_select_range_form'); 821 | $this->configuration('menu][bni_select_range_form][url', $url); 822 | $this->configuration('menu][bni_select_range_form][fields', $fields); 823 | 824 | } 825 | 826 | /** 827 | * Mendapatkan array transaksi dengan format yang sudah rapih. 828 | */ 829 | protected function bniFilterTransactionTable($tables) 830 | { 831 | $ref = IBank::reference('table_header_account_statement'); 832 | $ref = array_flip($ref); 833 | 834 | $language = $this->configuration('language'); 835 | $language = null === $language ? 'id' : $language; 836 | 837 | $transactions = []; 838 | while (!empty($tables)) { 839 | $table = array_shift($tables); 840 | 841 | if (isset($table[0]) && isset($table[1])) { 842 | $info = $language == 'id' ? $this->bniTranslate($table[0]) : $table[0]; 843 | $value = $table[1]; 844 | switch ($info) { 845 | case 'Transaction Date': 846 | $transaction = ['no' => null, 'id' => null]; 847 | $transaction['date'] = $value; 848 | break; 849 | 850 | case 'Transaction Remarks': 851 | $transaction['detail'] = $value; 852 | break; 853 | 854 | case 'Amount type': 855 | $transaction['type'] = $value; 856 | break; 857 | 858 | case 'Amount': 859 | $transaction['amount'] = $value; 860 | break; 861 | 862 | case 'Account Balance': 863 | $transaction['balance'] = $value; 864 | $transactions[] = array_merge($ref, $transaction); 865 | break; 866 | } 867 | } 868 | } 869 | return $transactions; 870 | } 871 | 872 | /** 873 | * Alternative handler for bni_transaction_finishing. 874 | */ 875 | protected function bniTransactionFinishing() 876 | { 877 | $temporary_result = $this->configuration('temporary][result'); 878 | $this->configuration('temporary][result', null); 879 | 880 | // Secara default, bni menggunakan sort secara desc. 881 | switch ($this->sort) { 882 | case 'desc': 883 | break; 884 | 885 | case 'asc': 886 | krsort($temporary_result); 887 | break; 888 | } 889 | if (null === $this->result) { 890 | $this->result = []; 891 | } 892 | $this->result = array_merge($this->result, $temporary_result); 893 | } 894 | 895 | /** 896 | * Translate dari indonesia ke inggiris. 897 | */ 898 | protected function bniTranslate($string) 899 | { 900 | return isset($this->bniString()[$string]) ? $this->bniString()[$string] : $string; 901 | } 902 | 903 | /** 904 | * Kamus. 905 | */ 906 | protected function bniString() 907 | { 908 | return [ 909 | 'Tanggal Transaksi' => 'Transaction Date', 910 | 'Uraian Transaksi' => 'Transaction Remarks', 911 | 'Tipe' => 'Amount type', 912 | 'Jumlah Pembayaran' => 'Amount', 913 | 'Nominal' => 'Amount', 914 | 'Saldo' => 'Account Balance', 915 | ]; 916 | } 917 | 918 | protected function bniCheckRange() 919 | { 920 | if (null === $this->range) { 921 | $this->changeTarget('get_last_transaction'); 922 | } 923 | else { 924 | $this->changeTarget('get_range_transaction'); 925 | } 926 | } 927 | 928 | 929 | 930 | 931 | 932 | 933 | } --------------------------------------------------------------------------------