├── .gitignore ├── BillTech.php ├── BillTechPaymentsUpdater.php ├── README.md ├── UNINSTALL.md ├── bin ├── billtech-clear-logs.php ├── billtech-update-links.php ├── billtech-update-payments.php └── timestamp.sh ├── cron ├── billtech-update-clear-logs-cron ├── billtech-update-links-cron └── billtech-update-payments-cron ├── doc ├── lms.mysql └── lms.pgsql ├── handlers ├── BillTechInitHandler.php ├── BillTechLinkInsertHandler.php └── BillTechPaymentCashImportHandler.php ├── index.php ├── install.sh ├── lib ├── BillTechApiClientFactory.php ├── BillTechLink.php ├── BillTechLinkApiService.php ├── BillTechLinksManager.php ├── LinkShortenerApiService.php └── upgradedb │ ├── mysql.2017112400.php │ ├── mysql.2020091900.php │ ├── mysql.2021041400.php │ ├── mysql.2022011200.php │ ├── mysql.2022100400.php │ ├── postgres.2017112400.php │ ├── postgres.2020091900.php │ ├── postgres.2021041400.php │ ├── postgres.2022011200.php │ └── postgres.2022100400.php ├── license.md ├── modules ├── billtechconfig.php ├── billtechpaymentconfirmed.php └── billtechpaymentlist.php └── templates ├── billtechconfig.html ├── billtechpaymentlist.html └── button ├── bclean ├── billtechbalancebutton.html └── billtechrowbutton.html ├── billtechemailbutton.html └── default ├── billtechbalancebutton.html ├── billtechrowbutton.html ├── customerbilltechbutton.html └── customerotheripbilltechbutton.html /.gitignore: -------------------------------------------------------------------------------- 1 | lms.pem 2 | lms.pub 3 | .idea/ -------------------------------------------------------------------------------- /BillTech.php: -------------------------------------------------------------------------------- 1 | handlers = array( 29 | 'menu_initialized' => array( 30 | 'class' => 'BillTechInitHandler', 31 | 'method' => 'menuBillTech' 32 | ), 33 | 'modules_dir_initialized' => array( 34 | 'class' => 'BillTechInitHandler', 35 | 'method' => 'modulesBillTech' 36 | ), 37 | 'smarty_initialized' => array( 38 | 'class' => 'BillTechInitHandler', 39 | 'method' => 'smartyBillTech' 40 | ), 41 | 'userpanel_smarty_initialized' => array( 42 | 'class' => 'BillTechInitHandler', 43 | 'method' => 'smartyBillTech' 44 | ), 45 | 'invoice_email_before_send' => array( 46 | 'class' => 'BillTechLinkInsertHandler', 47 | 'method' => 'addButtonToInvoiceEmail' 48 | ), 49 | 'notify_parse_customer_data' => array( 50 | 'class' => 'BillTechLinkInsertHandler', 51 | 'method' => 'notifyCustomerDataParse' 52 | ), 53 | 'customer_before_display' => array( 54 | 'class' => 'BillTechLinkInsertHandler', 55 | 'method' => 'addButtonToCustomerView' 56 | ), 57 | 'customer_otherip_before_display' => array( 58 | 'class' => 'BillTechLinkInsertHandler', 59 | 'method' => 'addButtonToCustomerOtherIPView' 60 | ), 61 | 'userpanel_finances_main_before_module_display' => array( 62 | 'class' => 'BillTechLinkInsertHandler', 63 | 'method' => 'addButtonsToFinancesView' 64 | ), 65 | 'cashimport_after_commit' => array( 66 | 'class' => 'BillTechPaymentCashImportHandler', 67 | 'method' => 'processCashImport' 68 | ), 69 | 'messageadd_variable_parser' => array( 70 | 'class' => 'BillTechLinkInsertHandler', 71 | 'method' => 'messageaddCustomerDataParse' 72 | ), 73 | ); 74 | } 75 | 76 | public static function toMap($callback, array $array) 77 | { 78 | $map = array(); 79 | foreach ($array as $item) { 80 | $map[$callback($item)] = $item; 81 | } 82 | return $map; 83 | } 84 | 85 | /** 86 | * @param string $str 87 | * @param $separator 88 | * @param int $repeatCount 89 | * @return string 90 | */ 91 | public static function repeatWithSeparator($str, $separator, $repeatCount) 92 | { 93 | return implode($separator, array_fill(0, $repeatCount, $str)); 94 | } 95 | 96 | /** 97 | * @param int $rowCount 98 | * @param int $valuesCount 99 | * @return string 100 | */ 101 | public static function prepareMultiInsertPlaceholders($rowCount, $valuesCount) 102 | { 103 | return BillTech::repeatWithSeparator("(" . BillTech::repeatWithSeparator("?", ",", $valuesCount) . ")", ',', $rowCount); 104 | } 105 | 106 | public static function measureTime($callback, $verbose) 107 | { 108 | $start = microtime(true); 109 | $callback(); 110 | $time_elapsed_secs = microtime(true) - $start; 111 | if ($verbose) { 112 | echo "Update took " . $time_elapsed_secs . " s\n"; 113 | } 114 | } 115 | 116 | public static function lock($lockName, $callback) 117 | { 118 | $fp = fopen('/tmp/billtech-lock-' . $lockName, 'w+'); 119 | 120 | try { 121 | if (flock($fp, LOCK_EX | LOCK_NB)) { 122 | echo "Lock acquired\n"; 123 | $callback(); 124 | flock($fp, LOCK_UN); 125 | } else { 126 | exit("Could not acquire lock. Another process is running.\n"); 127 | } 128 | } finally { 129 | fclose($fp); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /BillTechPaymentsUpdater.php: -------------------------------------------------------------------------------- 1 | linksManager = new BillTechLinksManager($verbose); 11 | $this->verbose = $verbose; 12 | } 13 | 14 | public function checkForUpdate() 15 | { 16 | if (!ConfigHelper::getConfig('billtech.api_secret')) { 17 | if ($this->verbose) { 18 | echo "Missing api_secret\n"; 19 | } 20 | return; 21 | } 22 | 23 | $this->checkExpired(); 24 | $this->updatePayments(); 25 | $this->clearOldLogs(); 26 | } 27 | 28 | private function checkExpired() 29 | { 30 | global $DB, $LMS; 31 | $expiration = ConfigHelper::getConfig('billtech.payment_expiration', 5); 32 | 33 | if ($expiration == 'never') return; 34 | 35 | if ($this->verbose) { 36 | echo "Checking expired payments\n"; 37 | } 38 | 39 | $DB->BeginTrans(); 40 | $payments = $DB->GetAll("SELECT id, customerid, amount, cdate, cashid FROM billtech_payments WHERE closed = 0 AND ?NOW? > cdate + $expiration * 86400"); 41 | 42 | if ($this->verbose && $payments) { 43 | echo "Closing " . count($payments) . " expired payments\n"; 44 | } 45 | 46 | if (is_array($payments) && sizeof($payments)) { 47 | foreach ($payments as $payment) { 48 | $cash = $LMS->GetCashByID($payment['cashid']); 49 | if ($cash && strpos($cash['comment'], BillTech::CASH_COMMENT) !== false) { 50 | $DB->Execute("UPDATE billtech_payments SET closed = 1, cashid = NULL WHERE id = ?", array($payment['id'])); 51 | $LMS->DelBalance($payment['cashid']); 52 | } 53 | } 54 | } 55 | if (is_array($DB->GetErrors()) && sizeof($DB->GetErrors())) { 56 | foreach ($DB->GetErrors() as $error) { 57 | echo $error['query'] . PHP_EOL; 58 | echo $error['error'] . PHP_EOL; 59 | } 60 | $DB->RollbackTrans(); 61 | } else { 62 | $DB->CommitTrans(); 63 | } 64 | } 65 | 66 | private 67 | function updatePayments() 68 | { 69 | global $DB, $LMS; 70 | 71 | if ($this->verbose) { 72 | echo "Updating payments\n"; 73 | } 74 | 75 | $now = time(); 76 | $last_sync = $this->getLastSync(); 77 | 78 | $client = BillTechApiClientFactory::getClient(); 79 | $path = "/pay/v1/payments/search" . "?fromDate=" . (ConfigHelper::checkConfig("billtech.debug") ? 0 : ($last_sync - 3600)); 80 | 81 | $response = $client->get($path); 82 | 83 | if ($response->getStatusCode() != 200) { 84 | $DB->Execute("INSERT INTO billtech_log (cdate, type, description) VALUES (?NOW?, ?, ?)", array('ERROR', "/pay/v1/payments returned code " . $response->getStatusCode() . "\n" . $response->getBody())); 85 | return; 86 | } 87 | if (ConfigHelper::checkConfig("billtech.debug")) { 88 | file_put_contents('/var/tmp/billtech_debug.txt', print_r($response->getBody(), true)); 89 | } 90 | 91 | $payments = json_decode($response->GetBody()); 92 | $DB->Execute("INSERT INTO billtech_log (cdate, type, description) VALUES (?NOW?, ?, ?)", array('SYNC_SUCCESS', '')); 93 | $DB->BeginTrans(); 94 | $DB->Execute("UPDATE billtech_info SET keyvalue = ? WHERE keytype = 'current_sync'", array($now)); 95 | $customers = array(); 96 | 97 | if ($this->verbose) { 98 | echo "Found " . count($payments) . " new payments\n"; 99 | } 100 | 101 | if (!is_array($payments)) { 102 | return; 103 | } 104 | 105 | foreach ($payments as $payment) { 106 | $payment->paidAt = strtotime($payment->paidAt); 107 | 108 | if (!$payment->userId) { 109 | $DB->Execute("INSERT INTO billtech_log (cdate, type, description) VALUES (?NOW?, ?, ?)", array('ERROR', json_encode($payment))); 110 | continue; 111 | } 112 | 113 | $ids = BillTechPaymentsUpdater::getBillTechPaymentsFromDb($payment); 114 | 115 | if (!$ids || !count($ids)) { 116 | $addbalance = array( 117 | 'value' => $payment->amount, 118 | 'type' => 100, 119 | 'userid' => null, 120 | 'customerid' => $payment->userId, 121 | 'comment' => BillTech::CASH_COMMENT.' za: '.$payment->title, 122 | 'time' => $payment->paidAt, 123 | 'currency' => 'PLN', 124 | ); 125 | 126 | $cashid = $this->addBalanceReturnCashIdOrFalse($addbalance); 127 | if ($cashid) { 128 | $title = isset($payment->title) ? $payment->title : ''; 129 | $invoiceNumber = isset($payment->invoiceNumber) ? $payment->invoiceNumber : ''; 130 | 131 | $amount = str_replace(',', '.', $payment->amount); 132 | 133 | $DB->Execute("INSERT INTO billtech_payments (cashid, ten, document_number, customerid, amount, title, reference_number, cdate, closed, token) " 134 | . "VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)", 135 | array($cashid, '', $invoiceNumber, $payment->userId, $amount, $title, $payment->referenceNumber, $payment->paidAt, $payment->token)); 136 | 137 | $customers[$payment->userId] = $payment->userId; 138 | } 139 | } 140 | } 141 | 142 | if ($this->verbose) { 143 | echo "Updating " . count($customers) . " customers\n"; 144 | } 145 | 146 | foreach ($customers as $customerid) { 147 | if (ConfigHelper::checkConfig('billtech.manage_cutoff')) { 148 | $this->checkCutoff($customerid); 149 | } 150 | 151 | $this->linksManager->updateCustomerBalance($customerid); 152 | } 153 | 154 | 155 | $DB->Execute("UPDATE billtech_info SET keyvalue = ? WHERE keytype='last_sync'", array($now)); 156 | 157 | if (is_array($DB->GetErrors()) && sizeof($DB->GetErrors())) { 158 | $DB->RollbackTrans(); 159 | } else { 160 | $DB->CommitTrans(); 161 | } 162 | } 163 | 164 | private 165 | function getBillTechPaymentsFromDb($payment) { 166 | global $DB; 167 | 168 | if ($payment->integrationVersion == 'V1') { 169 | return $DB->GetCol("SELECT id FROM billtech_payments WHERE token=?", array($payment->token)); 170 | } else { //integrationVersion == 'V0' 171 | return $DB->GetCol("SELECT id FROM billtech_payments WHERE reference_number=?", array($payment->referenceNumber)); 172 | } 173 | } 174 | 175 | private 176 | function checkCutoff($customerid) 177 | { 178 | global $DB, $LMS; 179 | $excluded_customergroups = ConfigHelper::getConfig('cutoff.excluded_customergroups', ''); 180 | $customergroups = ConfigHelper::getConfig('cutoff.customergroups', ''); 181 | if ($excluded_customergroups) { 182 | $excluded = $DB->GetOne("SELECT count(*) FROM customerassignments 183 | WHERE customerassignments.customerid = ? 184 | AND customerassignments.customergroupid IN ($excluded_customergroups)", array($customerid)); 185 | if ($excluded) return; 186 | } 187 | 188 | if ($customergroups) { 189 | $included = $DB->GetOne("SELECT count(*) FROM customerassignments 190 | WHERE customerassignments.customerid = ? 191 | AND customerassignments.customergroupid IN ($customergroups)", array($customerid)); 192 | if (!$included) return; 193 | } 194 | 195 | 196 | $extend_deadline = ConfigHelper::getConfig('cutoff.extend_deadline', 7); 197 | 198 | $extended_balance = $this->getCustomerDueBalance($customerid, $extend_deadline); 199 | $balance = $this->getCustomerDueBalance($customerid, 0); 200 | 201 | $cutoff_limit = ConfigHelper::getConfig('cutoff.limit', 0); 202 | 203 | if ($extended_balance >= $cutoff_limit) { 204 | $LMS->NodeSetU($customerid, TRUE); 205 | 206 | $cutoffstop = $DB->Execute("SELECT cutoffstop FROM customers WHERE id = ?", array($customerid)); 207 | if ($cutoffstop > time() || $balance >= 0) { 208 | $LMS->NodeSetWarnU($customerid, FALSE); 209 | } 210 | 211 | $expiration = ConfigHelper::getConfig('billtech.payment_expiration', 5); 212 | $new_cutoffstop = time() + (int)$expiration * 86400; 213 | 214 | if ($new_cutoffstop > $cutoffstop) { 215 | $DB->Execute("UPDATE customers SET cutoffstop = ? WHERE id = ?", array($new_cutoffstop, $customerid)); 216 | } 217 | } 218 | 219 | } 220 | 221 | private 222 | function getCustomerDueBalance($customerid, $extend_deadline) 223 | { 224 | global $DB; 225 | $filter = "((d.paytime > 0 AND cdate + ((d.paytime + $extend_deadline) * 86400) < ?NOW?) 226 | OR d.paytime = 0 227 | OR d.paytime IS NULL)"; 228 | 229 | return $balance = $DB->GetOne("SELECT SUM(c.value) FROM cash c 230 | LEFT JOIN documents d ON d.id = c.docid 231 | WHERE $filter AND c.customerid = ?", array($customerid)); 232 | } 233 | 234 | private 235 | function readBillTechInfo() 236 | { 237 | global $DB; 238 | $billTechInfo = array(); 239 | $rows = $DB->GetAll("SELECT keytype, keyvalue FROM billtech_info"); 240 | 241 | if (!is_array($rows)) { 242 | return array(); 243 | } 244 | 245 | foreach ($rows as $row) { 246 | $billTechInfo[$row['keytype']] = $row['keyvalue']; 247 | } 248 | return $billTechInfo; 249 | } 250 | 251 | private function clearOldLogs() 252 | { 253 | global $DB; 254 | 255 | if ($this->verbose) { 256 | echo "Clearing old logs\n"; 257 | } 258 | 259 | $DB->Execute("DELETE FROM billtech_log WHERE type='SYNC_SUCCESS' AND cdate<(?NOW? - ?);", 260 | array(ConfigHelper::getConfig('billtech.log_retention_days', 7) * 24 * 3600)); 261 | } 262 | 263 | /** 264 | * @return mixed 265 | */ 266 | private function getLastSync() 267 | { 268 | $billTechInfo = $this->readBillTechInfo(); 269 | return $billTechInfo['last_sync']; 270 | } 271 | 272 | public static function addBalanceReturnCashIdOrFalse($addbalance) { 273 | global $DB, $LMS; 274 | 275 | $cashid = $LMS->AddBalance($addbalance); 276 | if ($cashid && $cashid == 1) { 277 | $cashid = $DB->GetLastInsertID('cash'); 278 | } 279 | return $cashid; 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wtyczka Bill Gateway dla LMS od BillTech 2 | 3 | ## Opis 4 | Bill Gateway to usługa, która pozwala Dostawcom na wygodne pobieranie należności od swoich klientów. 5 | Po wystawieniu faktury Dostawca generuje link do płatności, który może dostarczyć swoim klientom różnymi kanałami, 6 | np. wysłać w wiadomości e-mail, sms lub pokazać w panelu klienta (userpanel). 7 | Klient (użytkownik) po kliknięciu w taki link, zostaje przekierowany na ekran podsumowania płatności. 8 | Informacja o wykonanej płatności natychmiast trafia do Dostawcy, 9 | dzięki czemu możliwe jest szybkie uregulowanie salda klienta oraz ewentualne zdjęcie blokady usług. 10 | 11 | Więcej o korzyściach [Bill Gateway na stronie BillTech](https://billtech.pl/bill-gateway/). 12 | 13 | Wtyczka umożliwia integrację z usługą Bill Gateway poprzez: 14 | * Dodanie przycisku *Opłać teraz* do panelu klienta w sekcji finanse przy saldzie oraz indywidualnych 15 | fakturach pozwalając na wykonanie płatności online poprzez platformę BillTech, 16 | * Dodanie przycisku *Opłać teraz* do wiadomości e-mail z fakturą oraz notyfikacji, 17 | * Wstawienie informacji o płatności do nagłówków wiadomości e-mail z fakturą, 18 | * Dodanie przycisku *Opłać teraz* do ekranu blokady internetu, 19 | * Przekazanie informacji o płatności wykonanej na platformie BillTech do LMS. 20 | 21 | Szczegółowa [dokumentacja API](https://docs.billtech.pl/) produktu Bill Gateway. 22 | 23 | > #### Uwaga 24 | > Wtyczka do działania wymaga aktualizacji odpowiedniej wersji LMS. W przypadku posiadania najnowszej wersji 25 | lmsgit nie jest konieczne dodatkowe działanie. W przeciwnym wypadku zapraszamy do kontaktu, chętnie pomożemy 26 | z wprowadzeniem odpowiednich zmian również do innych wersji. 27 | 28 | ## Licencja 29 | Wtyczka udostepniana jest na licencji MPL (Mozilla Public License) z klauzulą Commons Clause. Więcej informacji znajduje się w pliku license.md. 30 | Firma Billtech prowadzi odpłatną pomoc techniczną - jeśli jesteś zainteresowany wykup konstultację w aplikacji rezerwacyjnej https://billtech.trafft.com/ lub skontaktuj się z nami mailowo pod adresem . 31 | 32 | ## Instalacja 33 | * Umieść zawartość tego repozytorium w katalogu *plugins/BillTech* w katalogu instalacyjnym LMSa, 34 | * W katalogu projektu uruchom skrypt install.sh. W celu jego poprawnego działania plik lms.ini powinien znajdować się w katalogu `/etc/lms` i mieć wypełnione pole `sys_dir`, 35 | * Zaloguj się do panelu admininistracyjnego LMS, 36 | * Przejdź do zakładki *Konfiguracja -> Wtyczki*, 37 | * Kliknij żarówkę po prawej stronie w wierszu z wtyczką BillTech, aby ją włączyć, 38 | * W szablonie wiadomości e-mail z powiadomieniem o wystawieniu nowej faktury dodaj `%billtech_btn` i/lub `%billtech_balance_btn` w miejscu, 39 | w którym powinny pojawić się przyciski do opłacenia odpowiednio indywidualnej faktury i/lub salda. 40 | 41 | ## Odinstalowanie wtyczki 42 | Proces kompletnego odinstalowania wtyczki został opisany w oddzielnym pliku [UNINSTALL.md](../master/UNINSTALL.md). 43 | Odinstalowanie jest procesem nieodwracalnym, jeśli potrzebujesz zdezaktywować wtyczkę na pewien okres czasu zalecamy 44 | skorzystanie z przełącznika aktywności wtyczki BillTech znajdującego się w panelu administracyjnym LMS w zakładce: *Konfiguracja->Wtyczki*. 45 | 46 | ## Konfiguracja 47 | W panelu administracyjnym wejdź w zakładkę *Konfiguracja -> BillTech* i wpisz wartości zmiennych konfiguracyjnych otrzymanych od . 48 | Podane wartości można również wprowadzić w zakładce *Konfiguracja -> Interfejs użytkownika* w sekcji billtech. 49 | 50 | ## Dodatkowe informacje 51 | ### Obsługa płatności po stronie klienta 52 | Operacje kasowe, które powstają po wykonaniu płatności BillTech, to tzw. wpłaty tymczasowe. Są tworzone po to, aby użytkownik oraz administrator systemu widzieli wykonaną płatność. Wpłaty tymczasowe umożliwiają natychmiastowe odblokowanie usług w przypadku blokady z powodu nieuregulowania opłat. 53 | Wpłaty tymczasowe przestają być potrzebne w momencie zaksięgowania opłat z wykazu bankowego - mogą wtedy zostać rozliczone (zamknięte), po czym przestają być widoczne (znikają zarówno w panelu administracyjnym jak i w panelu klienta). 54 | Istnieją 3 możliwości rozliczania wpłat tymczasowych: 55 | 56 | 1. Automatyczne rozliczanie poprzez mapowanie odpowiednich transakcji pochodzących z wyciągu bankowego (tzw. cashimport). 57 | 58 | Aby włączyć automatyczne rozliczanie opłat tymczasowych poprzez cashimport, należy ustawić wartość zmiennej `billtech.cashimport_enabled=true`. 59 | 60 | 2. Po upływie określonej liczby dni (domyślnie jest to 5 dni), wpłaty tymczasowe są automatycznie zamykane jako rozliczone. Odpowiada za to zmienna środowiskowa `billtech.payment_expiration`. 61 | 62 | Aby wpłaty tymczasowe nigdy nie wygasały po upływie zadanego czasu, należy ustawić zmienną `billtech.payment_expiration=never`. 63 | Takie ustawienie jest wskazane, gdy rozliczanie wpłat tymczasowych poprzez cashimport jest włączone (punkt pierwszy). 64 | 65 | 3. Wpłaty tymczasowe można rozliczać manualnie poprzez panel Płatności BillTech. 66 | 67 | W tym celu należy zaznaczyć opłaty do rozliczenia i kliknąć przycisk *Zaznacz/Odznacz jako rozliczone*. 68 | W przypadku pomyłki, proces ten jest w pełni odwracalny poprzez wskazanie wyszarzonych (rozliczonych) wpłat tymczasowych, a następnie kliknięcie przycisku *Zaznacz/Odznacz jako rozliczone*. 69 | 70 | ### Spis zmiennych konfiguracyjnych w sekcji billtech (billtech.): 71 | 72 | ##### Zmienne związane z łączeniem się z BillTech (umożliwiające dostęp do API systemu płatności BillTech) 73 | 74 | | nazwa zmiennej | wartości | przykład | opis | 75 | |---------------- |---------- |---------------------------------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 76 | | api_key | string | Lg8C6zy851WCMSx8d2hctoWIFAwPGlbk | Parametr wykorzystywany do uwierzytelnienia HTTP BASIC. | 77 | | api_secret | string | fYA9FuqVjMQ4bJIEtNloBMUni1qAKNVi | Parametr wykorzystywany do uwierzytelnienia HTTP BASIC. Otrzymywany po podaniu parametru PIN i kliknięciu przycisku Generuj API secret w zakładce *Konfiguracja -> BillTech*. | 78 | | api_url | string | https://api.test.billtech.pl | Adres do komunikacji z platformą BillTech. | 79 | 80 | ##### Zmienne związane z obsługą dokonanej płatności 81 | > #### Uwaga 82 | > 83 | >W przypadku braku któregokolwiek z paramterów opcjonalnych typu boolean (aka flag) LMS traktuje je jakby miały wartość false. 84 | 85 | | nazwa zmiennej | wartości | przykład | opis | 86 | |------------------------- |------------ |---------------------------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 87 | | payment_expiration | int/string | 5 | Liczba dni po których wpłata tymczasowa BillTech znika z systemu. Dla wartości `never` mechanizm ten zostaje wyłączony -- taka powinna być wartość w przypadku korzystania z rozliczania wpłat tymczasowych poprzez cashimport (`cashimport_enabled=true`). | 88 | | isp_id | string | nazwa_dostawcy | Id dostawcy w systemie BillTech. | 89 | | bankaccount | string | 61109010140000071219812874 | Opcjonalny parametr. Odpowiada za globalny numer rachunku bankowego wykorzystywany do generowania linków dla wszystkich klientów. W przypadku niepodania tego parametru linki są tworzone na podstawie indywidualnych rachunków bankowych klientów. | 90 | | manage_cutoff | boolean | true | Opcjonalny parametr. Powinien być ustawiony na wartość true w przypadku włączonego mechanizmu blokady usług w LMS (dla niepłacących klientów). Wartość początkowa: true | 91 | | append_customer_info | boolean | true | Opcjonalny parametr. Odpowiada za dodanie danych osobowych podczas procesu tworzenia linków. Skutkuje wygenerowaniem skróconych linków do płatności, które mogą mieć zastosowanie np. w wiadomościach SMS. Wartość początkowa: true | 92 | | cashimport_enabled | boolean | true | Opcjonalny parametr. Umożliwia automatyczne rozliczanie opłat tymczasowych poprzez wyciąg bankowy. Wartość początkowa: true | 93 | | balance_button_disabled | boolean | true | Opcjonalny parametr. Umożliwia schowanie przycisku opłacenia salda w panelu klienta. Wartość początkowa: false | 94 | | row_buttons_disabled | boolean | true | Opcjonalny parametr. Umożliwia schowanie przycisków opłacenia przy każdej fakturze w panelu klienta. Wartość początkowa: false | 95 | 96 | ## Change Log 97 | 98 | #### Wersja 1.0 (obecna) 99 | * Dane na temat płatności są generowane w momencie ich powstawania w systemie LMS i identyfikowane w BillTech poprzez token, który jest główną częścią nowego, krótszego linku. 100 | Linki są zapisywane w bazie LMS w tabeli billtech_payment_links. 101 | Istnieją 2 możliwości podania danych identyfikujących użytkownika dokonującego płatności: 102 | * dane mogą zostać dodane do linku poprzez parametry zapytania (np. ?name=Jan&surname=Kowalski&email=email@example.com), 103 | * dane mogą zostać podane przy tworzeniu linku do płatności w body zapytania. 104 | Wtedy dane zostaną zapisane w bazie BillTech oraz umożliwią utworzenie skróconego linku. 105 | * Przechowywanie linków do płatności w bazie powoduje wyeliminowanie problemów ze spójnością salda. 106 | * Integracja z ekosystemem BillTech (poprzez alternatywne metody płatności na bramce płatniczej): 107 | * połączenia z bankami i aplikacjami, 108 | * przypomnienia o nadchodzących i przeterminowanych płatnościach, 109 | * płatności jednym kliknięciem z zapisanej karty, 110 | * autopłatności, 111 | * odraczanie płatności. 112 | * Dodanie nowych tabel billtech_payment_links, billtech_customer_info oraz aktualizacja istniejących poprzez skrypty migracyjne. 113 | * Przeniesienie mechanizmu aktualizowania informacji nt. wpłat łączącego się z BillTech co 5 minut do skryptu cron. 114 | * Zmiana możliwych wartości zmiennej `billtech.payment_expiration`. Aby wyłączyć mechanizm automatycznego zamykania się wpłat tymczasowych należy podać wartość `never` zamiast `0`. 115 | * Naprawienie błędu wynikającego z korzystania ze starszej wersji systemu zarządzania bazą danych (MySQL lub PostgreSQL) polegającego na tworzeniu rekordów tabeli billtech_payments z niepoprawną wartości pola cashid (wynoszącą 1 dla wszystkich wpisów). 116 | 117 | #### Wersja 1.1 (nadchodząca) 118 | * Rozróżnienie czy dany użytkownik jest w ekosystemie BillTech. 119 | * Generowanie linków przez API tylko dla użytkowników, którzy są w ekosystemie. 120 | Dla pozostałych użytkowników dane o saldzie zakodowane są w parametrach zapytania - przekazanie danych następuje dopiero w momencie kliknięcia w link. 121 | 122 | # FAQ 123 | 124 | ## Brak linków do opłacenia na wygenerowanych fakturach 125 |
126 | Sprawdzenie CRONów 127 | 128 | Należy zweryfikować, czy CRONy odpowiedzialne za generowanie wtyczki są uruchomione (komenda `crontab -e`). Poprawnie powinny być uruchomione trzy crony: 129 | 130 | ```bash 131 | 0 0 1 * * bash -c ${sys_dir}/plugins/BillTech/bin/billtech-clear-logs.php 132 | 0,5,10,15,20,25,30,35,40,45,50,55 * * * * bash -c ${sys_dir}/plugins/BillTech/bin/billtech-update-links.php | ${sys_dir}/plugins/BillTech/bin/timestamp.sh >> /var/log/billtech/`date +%Y%m%d`-update-links.log 2>&1 133 | * * * * * bash -c ${sys_dir}/plugins/BillTech/bin/billtech-update-payments.php | ${sys_dir}/plugins/BillTech/bin/timestamp.sh >> /var/log/billtech/`date +%Y%m%d`-update-payments.log 2>&1 134 | ``` 135 |
136 | 137 |
138 | Sprawdzenie logów 139 | 140 | Należy sprawdzić logi odpowiadające za generowanie faktur: `/var/log/billtech/*-update-links.log`. Najczęstsze błędy to: 141 | 142 | - **"Could not acquire lock. Another process is running."** 143 | - Jeśli błąd widoczny jest dłużej niż kilka minut, oznacza to, że istnieje inny proces uruchomiony przez CRON, który nie został zamknięty. 144 | - Rozwiązanie: Znalezienie procesu na maszynie, zabicie go oraz usunięcie pliku `/tmp/billtech-lock-update-links-*`. 145 | 146 | - **"Validation of the request failed"** 147 | - Błędy mogą wynikać z niepoprawnych danych przesyłanych do systemu, np.: 148 | - Nieprawidłowe znaki w tytułach faktur 149 | - Puste lub za długie tytuły faktur 150 | - Pusty numer konta 151 | 152 | Aby debugować błąd, można dodać linię `print_r($apiRequests);` w pliku `lib/BilltechLinkApiService.php` w funkcji `generatePaymentLinks`. Jeśli w LMS dane są poprawne, może być konieczna edycja zapytań SQL w `getLinkData()`. 153 |
154 | 155 | ## Brak linku w wiadomości e-mail/SMS 156 |
157 | Sprawdzenie opóźnień 158 | 159 | Poza sprawdzeniem problemów opisanych w punkcie 1, należy uwzględnić odstęp czasowy od generowania faktur do wysyłki. 160 | - Wtyczka generuje maksymalnie 100 linków na minutę. 161 | - Przy generowaniu kilku tysięcy linków proces może potrwać dłużej. 162 |
163 | 164 | ## Duplikaty wpłat bankowych na liście użytkowników 165 |
166 | Sprawdzenie mechanizmu mapowania 167 | 168 | Każdy przelew tymczasowy posiada `reference_number`, który służy do mapowania z realną wpłatą bankową. 169 | 170 | - Należy zweryfikować działanie funkcji `processCashImport` w pliku `handlers/BillTechPaymentCashImportHandler.php`. 171 | - Jeśli stosowane są indywidualne mechanizmy importu, trzeba sprawdzić ich kompatybilność ze standardowym systemem `cashimport`. 172 | - Zmienna konfiguracyjna `billtech.cashimport_enabled` powinna być ustawiona na `true`. 173 |
174 | 175 | ## Brak wpłat tymczasowych użytkowników 176 |
177 | Sprawdzenie CRONów i logów 178 | 179 | - Sprawdzić poprawne uruchomienie CRONów (`crontab -e`). 180 | - Przejrzeć logi `/var/log/billtech/*-update-payments.log`. 181 | - Jeśli pojawia się błąd **"Could not acquire lock. Another process is running."**, oznacza to, że inny proces CRON nie został zamknięty. 182 | - Rozwiązanie: Znalezienie procesu na maszynie, zabicie go oraz usunięcie pliku `/tmp/billtech-lock-update-payments-*`. 183 |
184 | 185 | ## Kontakt 186 | Więcej informacji na temat naszego API można znaleźć na stronie . Po dane do połączenia prosimy o wysyłanie wiadomości na adres 187 | 188 | Jeżeli chciałbyś przetestować wtyczkę, zobaczyć jak wygląda proces płatności, rozpocząć współpracę lub dowiedzieć się więcej, prosimy o wiadomość na adres 189 | -------------------------------------------------------------------------------- /UNINSTALL.md: -------------------------------------------------------------------------------- 1 | # Odinstalowanie wtyczki BillTech dla LMS 2 | 3 | > #### Uwaga 4 | > Przed usunięciem wtyczki wykonaj backup zarówno plików na serwerze jak i bazy danych. 5 | > Nie odpowiadamy za utracone dane w skutek procesu odinstalowania. 6 | 7 | ### 1. Operacje na plikach 8 | 9 | #### 1.1. Usunięcie plików wtyczki 10 | 11 | Wtyczka znajduje się w katalogu *{sys_dir}/plugins/Billtech/*. 12 | Należy usunąć ten katalog wraz ze wszystkimi plikami oraz podkatalogami. 13 | 14 | #### 1.2. Usunięcie plików logów 15 | 16 | Logi domyślnie znajdują się w katalogu */var/log/billtech/*. 17 | Należy usunąć cały katalog /billtech/ wraz ze znajdującymi się tam plikami. 18 | 19 | ### 2. Operacje na bazie danych 20 | 21 | #### 2.1. Usunięcie tabeli 22 | 23 | Należy usunąć poniższe tabele, dedykowane dla wtyczki, które zostały dodane w trakcie procesu instalacji: 24 | - billtech_customer_info 25 | - billtech_info 26 | - billtech_log 27 | - billtech_payment_links 28 | - billtech_payments 29 | 30 | Można to wykonać za pomocą poniższego zapytania SQL: 31 | 32 | ``` 33 | DROP TABLE IF EXISTS billtech_customer_info,billtech_info,billtech_log,billtech_payment_links,billtech_payments; 34 | ``` 35 | Należy również usunąć sekwencje: 36 | - billtech_payments_id_seq, 37 | - billtech_payment_links_id_seq 38 | 39 | Można to wykonać za pomocą poniższego zapytania SQL: 40 | 41 | ``` 42 | DROP SEQUENCE IF EXISTS billtech_payments_id_seq, billtech_payment_links_id_seq; 43 | ``` 44 | 45 | #### 2.2. Usunięcie zmiennych konfiguracyjnych 46 | 47 | W pliku instalacyjnym Readme.md wymieniona jest lista wszystkich zmiennych konfiguracyjnych wykorzystywanych przez wtyczkę. 48 | Posiadają one prefix 'billtech.' przez co znaduja się w jeden sekcji. Należy je usunąć. 49 | 50 | Możliwe jest usunięcię poszczególnych zmiennych poprzez panel administracyjny LMS w zakładce *Ustawienia->Konfiguracja*. 51 | Należy wybrać z listy sekcję billtech, a nastepnie zaznaczyć wszystkie wiersze i je usunąć. 52 | 53 | Alternatywna opcja to zapytanie do bazy danych. Poniższy kod SQL usuwa zmienne konfiguracyjne powiązane z wtyczką BillTech 54 | z tabeli *uiconfig*: 55 | 56 | ``` 57 | DELETE FROM uiconfig WHERE section = 'billtech'; 58 | ``` 59 | 60 | Dodatkowo należy usunąć wtyczkę z listy na podstronie panelu LMS Konfiguracja -> Wtyczki. Można to wykonać poniższym 61 | kodem SQL: 62 | 63 | ``` 64 | DELETE FROM uiconfig WHERE section = 'phpui' and var = 'plugins' and value = 'BillTech'; 65 | ``` 66 | 67 | > #### Uwaga 68 | > Należy sprawdzić także czy w pliku lms.ini nie zostały dodane ręcznie zmienne konfiguracyjne wtyczki. 69 | > W takim przypadku należy także ręcznie je usunąć. 70 | 71 | 72 | #### 2.3. Usunięcie rekordu dbinfo 73 | 74 | W tabeli dbinfo znajduje się rekord dotyczący wersji aktualnej bazy danych wtyczki. Należy usunąć rekord dla którego wartość kolumny keytype = 'dbversion_BillTech'. 75 | 76 | ``` 77 | DELETE FROM dbinfo WHERE keytype = 'dbversion_BillTech'; 78 | ``` 79 | 80 | ### CRON 81 | 82 | Należy usunąć rekordy z crontab'a. Bezpośrednio związane z wtyczką są 3 pliki: 83 | - billtech-clear-logs.php 84 | - billtech-update-links.php 85 | - billtech-update-payments.php 86 | 87 | 88 | Aby edytować crontab, należy wykonać poniższą komendę i usunąć w edytorze linijki powiązane z powyższymi plikami. 89 | 90 | ``` 91 | crontab -e 92 | ``` 93 | -------------------------------------------------------------------------------- /bin/billtech-clear-logs.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getCTime() >= 60*60*24*60){ 9 | unlink($logDirectory.$file->getFilename()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /bin/billtech-update-links.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 'C:', 8 | 'quiet' => 'q', 9 | 'help' => 'h', 10 | 'version' => 'v' 11 | ); 12 | 13 | $long_to_shorts = array(); 14 | foreach ($parameters as $long => $short) { 15 | $long = str_replace(':', '', $long); 16 | if (isset($short)) { 17 | $short = str_replace(':', '', $short); 18 | } 19 | $long_to_shorts[$long] = $short; 20 | } 21 | 22 | $options = getopt( 23 | implode( 24 | '', 25 | array_filter( 26 | array_values($parameters), 27 | function ($value) { 28 | return isset($value); 29 | } 30 | ) 31 | ), 32 | array_keys($parameters) 33 | ); 34 | 35 | foreach (array_flip(array_filter($long_to_shorts, function ($value) { 36 | return isset($value); 37 | })) as $short => $long) { 38 | if (array_key_exists($short, $options)) { 39 | $options[$long] = $options[$short]; 40 | unset($options[$short]); 41 | } 42 | } 43 | 44 | if (array_key_exists('version', $options)) { 45 | print <<getMessage(), E_USER_WARNING); 116 | // can't work without database 117 | die("Fatal error: cannot connect to database!" . PHP_EOL); 118 | } 119 | 120 | // Include required files (including sequence is important) 121 | 122 | require_once(LIB_DIR . DIRECTORY_SEPARATOR . 'common.php'); 123 | require_once(LIB_DIR . DIRECTORY_SEPARATOR . 'language.php'); 124 | include_once(LIB_DIR . DIRECTORY_SEPARATOR . 'definitions.php'); 125 | 126 | $SYSLOG = SYSLOG::getInstance(); 127 | 128 | // Initialize Session, Auth and LMS classes 129 | 130 | $AUTH = null; 131 | $LMS = new LMS($DB, $AUTH, $SYSLOG); 132 | 133 | $plugin_manager = new LMSPluginManager(); 134 | $LMS->setPluginManager($plugin_manager); 135 | 136 | $linksManager = new BillTechLinksManager(!$quiet); 137 | 138 | BillTech::measureTime(function () use ($linksManager, $CONFIG) { 139 | BillTech::lock("update-links-".$CONFIG['database']['database'], function () use ($linksManager) { 140 | $linksManager->cancelPaymentLinksIfManuallyDeletedLiability(); 141 | $linksManager->updateForAll(); 142 | }); 143 | }, !$quiet); 144 | -------------------------------------------------------------------------------- /bin/billtech-update-payments.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 'C:', 8 | 'quiet' => 'q', 9 | 'help' => 'h', 10 | 'version' => 'v' 11 | ); 12 | 13 | $long_to_shorts = array(); 14 | foreach ($parameters as $long => $short) { 15 | $long = str_replace(':', '', $long); 16 | if (isset($short)) { 17 | $short = str_replace(':', '', $short); 18 | } 19 | $long_to_shorts[$long] = $short; 20 | } 21 | 22 | $options = getopt( 23 | implode( 24 | '', 25 | array_filter( 26 | array_values($parameters), 27 | function ($value) { 28 | return isset($value); 29 | } 30 | ) 31 | ), 32 | array_keys($parameters) 33 | ); 34 | 35 | foreach (array_flip(array_filter($long_to_shorts, function ($value) { 36 | return isset($value); 37 | })) as $short => $long) { 38 | if (array_key_exists($short, $options)) { 39 | $options[$long] = $options[$short]; 40 | unset($options[$short]); 41 | } 42 | } 43 | 44 | if (array_key_exists('version', $options)) { 45 | print <<getMessage(), E_USER_WARNING); 120 | // can't work without database 121 | die("Fatal error: cannot connect to database!" . PHP_EOL); 122 | } 123 | 124 | // Include required files (including sequence is important) 125 | 126 | require_once(LIB_DIR . DIRECTORY_SEPARATOR . 'common.php'); 127 | require_once(LIB_DIR . DIRECTORY_SEPARATOR . 'language.php'); 128 | include_once(LIB_DIR . DIRECTORY_SEPARATOR . 'definitions.php'); 129 | 130 | $SYSLOG = SYSLOG::getInstance(); 131 | 132 | // Initialize Session, Auth and LMS classes 133 | 134 | $AUTH = null; 135 | $LMS = new LMS($DB, $AUTH, $SYSLOG); 136 | 137 | $plugin_manager = new LMSPluginManager(); 138 | $LMS->setPluginManager($plugin_manager); 139 | 140 | $paymentsUpdater = new BillTechPaymentsUpdater(!$quiet); 141 | 142 | BillTech::measureTime(function () use ($paymentsUpdater, $CONFIG) { 143 | BillTech::lock("update-payments-".$CONFIG['database']['database'], function () use ($paymentsUpdater) { 144 | $paymentsUpdater->checkForUpdate(); 145 | }); 146 | }, !$quiet); -------------------------------------------------------------------------------- /bin/timestamp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | while read x; do 3 | echo -n `date +%d/%m/%Y\ %H:%M:%S`; 4 | echo -n " "; 5 | echo $x; 6 | done -------------------------------------------------------------------------------- /cron/billtech-update-clear-logs-cron: -------------------------------------------------------------------------------- 1 | 0 0 1 * * bash -c ${sys_dir}/plugins/BillTech/bin/billtech-clear-logs.php 2 | -------------------------------------------------------------------------------- /cron/billtech-update-links-cron: -------------------------------------------------------------------------------- 1 | 0,5,10,15,20,25,30,35,40,45,50,55 * * * * bash -c ${sys_dir}/plugins/BillTech/bin/billtech-update-links.php | ${sys_dir}/plugins/BillTech/bin/timestamp.sh >> /var/log/billtech/\`date +\%Y\%m\%d\`-update-links.log 2>&1 2 | -------------------------------------------------------------------------------- /cron/billtech-update-payments-cron: -------------------------------------------------------------------------------- 1 | * * * * * bash -c ${sys_dir}/plugins/BillTech/bin/billtech-update-payments.php | ${sys_dir}/plugins/BillTech/bin/timestamp.sh >> /var/log/billtech/\`date +\%Y\%m\%d\`-update-payments.log 2>&1 2 | -------------------------------------------------------------------------------- /doc/lms.mysql: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------- 2 | # $Id$ 3 | # -------------------------------------------------------- 4 | 5 | SET SESSION sql_mode = ''; 6 | DROP TABLE IF EXISTS billtech_payments; 7 | DROP TABLE IF EXISTS billtech_info; 8 | 9 | CREATE TABLE billtech_payments 10 | ( 11 | id INT AUTO_INCREMENT PRIMARY KEY, 12 | ten VARCHAR(16) DEFAULT '' NOT NULL, 13 | customerid INT NULL, 14 | amount DECIMAL(9, 2) DEFAULT 0 NOT NULL, 15 | title TEXT DEFAULT '' NOT NULL, 16 | document_number VARCHAR(255) NULL, 17 | reference_number VARCHAR(255) NULL, 18 | cdate INT DEFAULT 0 NOT NULL, 19 | closed INT(1) DEFAULT 0 NOT NULL, 20 | cashid INT(11) NULL 21 | ); 22 | 23 | CREATE TABLE billtech_info 24 | ( 25 | keytype VARCHAR(255) PRIMARY KEY, 26 | keyvalue VARCHAR(255) 27 | ); 28 | 29 | CREATE TABLE billtech_log 30 | ( 31 | cdate INT DEFAULT 0 NOT NULL, 32 | type VARCHAR(255) DEFAULT '' NOT NULL, 33 | description TEXT NOT NULL 34 | ); 35 | 36 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'isp_id', ''); 37 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'payment_url', ''); 38 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'api_url', ''); 39 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'api_key', ''); 40 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'api_secret', ''); 41 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'payment_expiration', 'never'); 42 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'private_key', 'plugins/BillTech/lms.pem'); 43 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'cashimport_enabled', true); 44 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'manage_cutoff', true); 45 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'append_customer_info', true); 46 | 47 | INSERT INTO billtech_info (keytype, keyvalue) VALUES ('last_sync', 0); 48 | INSERT INTO billtech_info (keytype, keyvalue) VALUES ('current_sync', 0); 49 | 50 | create table billtech_payment_links ( 51 | id serial primary key, 52 | customer_id integer not null references customers(id) on delete cascade, 53 | src_cash_id integer references cash(id) on delete set null, 54 | src_document_id integer references documents(id) on delete set null, 55 | type varchar(255) not null, 56 | link varchar(2000) not null, 57 | short_link varchar(160), 58 | token varchar(1000) not null, 59 | amount numeric(9,2) not null 60 | ); 61 | 62 | create index billtech_payment_links__customer_id on billtech_payment_links (customer_id); 63 | create index billtech_payment_links__src_cash_id on billtech_payment_links (src_cash_id); 64 | create index billtech_payment_links__token on billtech_payment_links (token); 65 | 66 | 67 | alter table billtech_payments add column token varchar(1000); 68 | 69 | create index billtech_payments__reference_number on billtech_payments(reference_number); 70 | create index billtech_payments__closed_cdate on billtech_payments(closed, cdate); 71 | create index billtech_payments__token on billtech_payments(token); 72 | 73 | create table billtech_customer_info 74 | ( 75 | customer_id int primary key, 76 | last_cash_id int 77 | ); 78 | 79 | create index billtech_customer_info__customer_id on billtech_customer_info (customer_id); 80 | 81 | 82 | INSERT INTO dbinfo (keytype, keyvalue) VALUES ('dbversion_BillTech', '2022012000'); 83 | COMMIT; -------------------------------------------------------------------------------- /doc/lms.pgsql: -------------------------------------------------------------------------------- 1 | /* $Id$ */ 2 | 3 | BEGIN; 4 | DROP TABLE IF EXISTS billtech_payments; 5 | DROP TABLE IF EXISTS billtech_info; 6 | DROP TABLE IF EXISTS billtech_log; 7 | DROP TABLE IF EXISTS billtech_payment_links; 8 | DROP TABLE IF EXISTS billtech_customer_info; 9 | 10 | DROP SEQUENCE IF EXISTS billtech_payments_id_seq; 11 | DROP SEQUENCE IF EXISTS billtech_payment_links_id_seq; 12 | 13 | CREATE TABLE billtech_payments ( 14 | id serial PRIMARY KEY, 15 | ten varchar(16) DEFAULT '' NOT NULL, 16 | customerid integer, 17 | amount numeric(9, 2) DEFAULT 0 NOT NULL, 18 | title text DEFAULT '' NOT NULL, 19 | document_number varchar(255) DEFAULT '', 20 | reference_number varchar(255) DEFAULT '', 21 | cdate integer DEFAULT 0 NOT NULL, 22 | closed smallint DEFAULT 0 NOT NULL, 23 | cashid integer 24 | CONSTRAINT billtech_payment__cashid_fkey REFERENCES cash(id) ON DELETE SET NULL, 25 | token varchar(1000) 26 | ); 27 | 28 | CREATE TABLE billtech_info ( 29 | keytype varchar(255) PRIMARY KEY, 30 | keyvalue varchar(255) 31 | ); 32 | 33 | CREATE TABLE billtech_log ( 34 | cdate integer DEFAULT 0 NOT NULL, 35 | type varchar(255) DEFAULT '' NOT NULL, 36 | description TEXT NOT NULL 37 | ); 38 | 39 | CREATE TABLE billtech_payment_links ( 40 | id serial PRIMARY KEY, 41 | customer_id integer NOT NULL 42 | CONSTRAINT billtech_payment_links__customer_id_fkey REFERENCES customers(id) ON DELETE CASCADE, 43 | src_cash_id integer 44 | CONSTRAINT billtech_payment_links__src_cash_id_fkey REFERENCES cash(id) ON DELETE SET NULL, 45 | src_document_id integer 46 | CONSTRAINT billtech_payment_links__src_document_id_fkey REFERENCES documents(id) ON DELETE SET NULL, 47 | type varchar(255) NOT NULL, 48 | link varchar(2000) NOT NULL, 49 | short_link varchar(160), 50 | token varchar(1000) NOT NULL, 51 | amount numeric(9, 2) NOT NULL 52 | ); 53 | 54 | CREATE TABLE billtech_customer_info ( 55 | customer_id integer PRIMARY KEY, 56 | last_cash_id integer 57 | ); 58 | 59 | CREATE INDEX billtech_payment_links__customer_id ON billtech_payment_links (customer_id); 60 | CREATE INDEX billtech_payment_links__src_cash_id ON billtech_payment_links (src_cash_id); 61 | CREATE INDEX billtech_payment_links__token ON billtech_payment_links (token); 62 | 63 | CREATE INDEX billtech_payments__reference_number ON billtech_payments(reference_number); 64 | CREATE INDEX billtech_payments__closed_cdate ON billtech_payments(closed, cdate); 65 | CREATE INDEX billtech_payments__token ON billtech_payments(token); 66 | 67 | CREATE INDEX billtech_customer_info__customer_id ON billtech_customer_info (customer_id); 68 | 69 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'isp_id', ''); 70 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'payment_url', ''); 71 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'api_url', ''); 72 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'api_key', ''); 73 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'api_secret', ''); 74 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'payment_expiration', 'never'); 75 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'private_key', 'plugins/BillTech/lms.pem'); 76 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'cashimport_enabled', true); 77 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'manage_cutoff', true); 78 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'append_customer_info', true); 79 | INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'log_retention_days', 7); 80 | INSERT INTO billtech_info (keytype, keyvalue) VALUES ('last_sync', 0); 81 | INSERT INTO billtech_info (keytype, keyvalue) VALUES ('current_sync', 0); 82 | 83 | INSERT INTO dbinfo (keytype, keyvalue) VALUES ('dbversion_BillTech', '2023091200'); 84 | 85 | COMMIT; 86 | -------------------------------------------------------------------------------- /handlers/BillTechInitHandler.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | 8 | class BillTechInitHandler 9 | { 10 | 11 | public function modulesBillTech($modules_dirs) 12 | { 13 | $plugin_modules = PLUGINS_DIR . 14 | DIRECTORY_SEPARATOR . 15 | BillTech::PLUGIN_DIRECTORY_NAME . 16 | DIRECTORY_SEPARATOR . 'modules'; 17 | array_unshift($modules_dirs, $plugin_modules); 18 | return $modules_dirs; 19 | } 20 | 21 | public function menuBillTech($menu) 22 | { 23 | array_push($menu['finances']['submenu'], array( 24 | 'name' => 'Płatności BillTech', 25 | 'link' => '?m=billtechpaymentlist', 26 | 'tip' => 'Płatności BillTech', 27 | 'prio' => 160 28 | )); 29 | 30 | array_push($menu['config']['submenu'], array( 31 | 'name' => 'BillTech', 32 | 'link' => '?m=billtechconfig', 33 | 'tip' => 'Konfiguracja integracji z BillTech', 34 | 'prio' => 120 35 | )); 36 | return $menu; 37 | } 38 | 39 | public function smartyBillTech(Smarty $smarty) 40 | { 41 | //$smarty->clearCompiledTemplate(); 42 | $template_dirs = $smarty->getTemplateDir(); 43 | $plugin_templates = PLUGINS_DIR . 44 | DIRECTORY_SEPARATOR . 45 | BillTech::PLUGIN_DIRECTORY_NAME . 46 | DIRECTORY_SEPARATOR . 'templates'; 47 | array_unshift($template_dirs, $plugin_templates); 48 | $smarty->setTemplateDir($template_dirs); 49 | 50 | return $smarty; 51 | } 52 | } -------------------------------------------------------------------------------- /handlers/BillTechLinkInsertHandler.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | class BillTechLinkInsertHandler 10 | { 11 | private $linksManager; 12 | 13 | private function getLinksManager() 14 | { 15 | if (!isset($this->linksManager)) { 16 | $this->linksManager = new BillTechLinksManager(); 17 | } 18 | return $this->linksManager; 19 | } 20 | 21 | private function getPaymentLink($doc, $customerId, $params = array()) 22 | { 23 | global $DB; 24 | $linksManager = $this->getLinksManager(); 25 | 26 | if ($doc == 'balance') { 27 | $link = $linksManager->getBalanceLink($customerId, $params); 28 | } else { 29 | $link = $linksManager->getCashLinkByDocumentId($doc, $params); 30 | } 31 | 32 | return $link ? $link->link : ''; 33 | } 34 | 35 | private function getShortPaymentLink($doc, $customerId, $params = array()) 36 | { 37 | global $DB; 38 | $linksManager = $this->getLinksManager(); 39 | 40 | if ($doc == 'balance') { 41 | $link = $linksManager->getBalanceLink($customerId, $params); 42 | } else { 43 | $link = $linksManager->getCashLinkByDocumentId($doc, $params); 44 | } 45 | 46 | return $link ? $link->shortLink : ''; 47 | } 48 | 49 | public function addButtonToInvoiceEmail(array $hook_data = array()) 50 | { 51 | global $DB; 52 | $linksManager = $this->getLinksManager(); 53 | 54 | $linksManager->updateCustomerBalance($hook_data['doc']['customerid']); 55 | $cashLink = $linksManager->getCashLinkByDocumentId($hook_data['doc']['id'], ['utm_medium' => 'email']) ? 56 | $linksManager->getCashLinkByDocumentId($hook_data['doc']['id'], ['utm_medium' => 'email'])->link : ''; 57 | $balanceLink = $linksManager->getBalanceLink($hook_data['doc']['customerid'], ['utm_medium' => 'email']) ? 58 | $linksManager->getBalanceLink($hook_data['doc']['customerid'], ['utm_medium' => 'email'])->link : ''; 59 | $cashBtnCode = $this->createEmailButton($hook_data['mail_format'], $cashLink); 60 | $balanceBtnCode = $this->createEmailButton($hook_data['mail_format'], $balanceLink); 61 | 62 | $hook_data['body'] = preg_replace('/%billtech_btn/', $cashBtnCode, $hook_data['body']); 63 | $hook_data['body'] = preg_replace('/%billtech_balance_btn/', $balanceBtnCode, $hook_data['body']); 64 | 65 | $hook_data['headers'] = $this->fillEmailHeaders($hook_data['doc'], $hook_data['headers']); 66 | 67 | return $hook_data; 68 | } 69 | 70 | public function notifyCustomerDataParse(array $hook_data = array()) 71 | { 72 | $data = $hook_data['data']; 73 | $format = isset($hook_data['format']) && $hook_data['format'] == 'html' ? 'html' : 'text'; 74 | $customerid = $hook_data['customer']['id']; 75 | $link = self::getPaymentLink('balance', $customerid, ['utm_medium' => 'email']); 76 | 77 | $phone = ''; 78 | if (isset($hook_data['customer']['phone']) && $hook_data['customer']['phone'] != '') { 79 | $phone = $hook_data['customer']['phone']; 80 | } elseif (isset($hook_data['customer']['phones']) && $hook_data['customer']['phones'] != '') { 81 | $phone = $hook_data['customer']['phones']; 82 | } 83 | 84 | if($phone != '') { 85 | $shortLink = self::getShortPaymentLink('balance', $customerid); 86 | $link = $shortLink != '' ? $shortLink : $link; 87 | } 88 | 89 | $hook_data['data'] = preg_replace('/%billtech_balance_btn/', 90 | $this->createEmailButton($format, $link), $data); 91 | 92 | return $hook_data; 93 | } 94 | 95 | public function messageaddCustomerDataParse(array $hook_data = array()) 96 | { 97 | $customerid = $hook_data['data']['id']; 98 | $appendCustomerInfoEnabled = ConfigHelper::getConfig('billtech.append_customer_info', true); 99 | 100 | $amount = sprintf('%01.2f', -$hook_data['data']['balance']); 101 | $btnPatterns = ['/%billtech_balance_btn/', '/'.$amount.'illtech_balance_btn/']; 102 | 103 | if(isset($hook_data['data']['phone'])) { 104 | $phone = $hook_data['data']['phone']; 105 | }else if(isset($hook_data['data']['phones'])) { 106 | $phone = $hook_data['data']['phones']; 107 | } 108 | 109 | if (isset($phone)) { 110 | $link = self::getShortPaymentLink('balance', $customerid); 111 | if ($appendCustomerInfoEnabled) { 112 | $hook_data['body'] = preg_replace($btnPatterns, $link, $hook_data['body']); 113 | } else { 114 | $hook_data['body'] = preg_replace($btnPatterns, '', $hook_data['body']); 115 | } 116 | } else { 117 | $link = self::getPaymentLink('balance', $customerid, ['utm_medium' => 'email']); 118 | if (isset($hook_data['data']['contenttype']) && $hook_data['data']['contenttype'] == 'text/html') { 119 | $mail_format = ConfigHelper::getConfig('sendinvoices.mail_format', 'html'); 120 | $balanceBtnCode = $this->createEmailButton($mail_format, $link); 121 | $hook_data['body'] = preg_replace($btnPatterns, $balanceBtnCode, $hook_data['body']); 122 | } else { 123 | $hook_data['body'] = preg_replace($btnPatterns, $this->createEmailButton('txt', $link), $hook_data['body']); 124 | } 125 | } 126 | 127 | return $hook_data; 128 | } 129 | 130 | public function addButtonToCustomerView(array $hook_data = array()) 131 | { 132 | global $LMS; 133 | $smarty = $hook_data['smarty']; 134 | $customerid = $hook_data['customerid']; 135 | 136 | $customerinfo = $LMS->GetCustomer($customerid); 137 | if ($customerinfo['balance'] < 0) { 138 | $smarty->assign('billtech_balance_link', self::getPaymentLink('balance', $customerid, ['utm_medium' => 'cutoffpage'])); 139 | $billtech_balance_button = $smarty->fetch('button' . DIRECTORY_SEPARATOR . 'default' . DIRECTORY_SEPARATOR . 'customerbilltechbutton.html'); 140 | } else { 141 | $billtech_balance_button = ''; 142 | } 143 | 144 | $custombalancedata = $smarty->getTemplateVars('custombalancedata') . $billtech_balance_button; 145 | $smarty->assign('custombalancedata', $custombalancedata); 146 | } 147 | 148 | public function addButtonToCustomerOtherIPView(array $hook_data = array()) 149 | { 150 | global $LMS; 151 | $smarty = $hook_data['smarty']; 152 | $customerid = $hook_data['customerid']; 153 | 154 | $customerinfo = $LMS->GetCustomer($customerid); 155 | if ($customerinfo['balance'] < 0) { 156 | $smarty->assign('billtech_balance_link', self::getPaymentLink('balance', $customerid, ['utm_medium' => 'cutoffpage'])); 157 | $billtech_balance_button = $smarty->fetch('button' . DIRECTORY_SEPARATOR . 'default' . DIRECTORY_SEPARATOR . 'customerotheripbilltechbutton.html'); 158 | } else { 159 | $billtech_balance_button = ''; 160 | } 161 | 162 | $custombalancedata = $smarty->getTemplateVars('custombalancedata') . $billtech_balance_button; 163 | $smarty->assign('custombalancedata', $custombalancedata); 164 | } 165 | 166 | public function addButtonsToFinancesView(array $hook_data = array()) 167 | { 168 | global $LMS, $SESSION; 169 | $linksManager = $this->getLinksManager(); 170 | $smarty = $hook_data['smarty']; 171 | $userinfo = $LMS->GetCustomer($SESSION->id); 172 | $customerId = $userinfo['id']; 173 | $linksManager->updateCustomerBalance($customerId); 174 | 175 | $style = ConfigHelper::getConfig('userpanel.style', 'default'); 176 | 177 | if (!ConfigHelper::checkConfig('billtech.row_buttons_disabled')) { 178 | $balancelist = $smarty->getTemplateVars('balancelist'); 179 | 180 | if (isset($balancelist) && isset($balancelist['list'])) { 181 | $paymentLinks = $linksManager->getCustomerPaymentLinks($customerId); 182 | $paymentLinksMap = BillTech::toMap(function ($link) { 183 | /* @var $link BillTechLink */ 184 | return $link->getKey(); 185 | }, $paymentLinks); 186 | 187 | foreach ($balancelist['list'] as &$item) { 188 | /* @var $link BillTechLink */ 189 | $link = isset($paymentLinksMap[$item['docid']]) ? $paymentLinksMap[$item['docid']] : null; 190 | if (!isset($link) && isset($paymentLinksMap['cash_' . $item['id']])) { 191 | $link = $paymentLinksMap['cash_' . $item['id']]; 192 | } 193 | if (isset($link)) { 194 | $customlinks = $item['customlinks']; 195 | if (!isset($customlinks)) { 196 | $customlinks = array(); 197 | } 198 | $button = $this->createRowButton($smarty, $link->link, $style); 199 | array_push($customlinks, array( 200 | 'extra' => $button 201 | )); 202 | $item['customlinks'] = $customlinks; 203 | } 204 | } 205 | } 206 | $smarty->assign('balancelist', $balancelist); 207 | 208 | } 209 | 210 | if (!ConfigHelper::checkConfig('billtech.balance_button_disabled')) { 211 | $balanceLink = $linksManager->getBalanceLink($customerId, ['utm_medium' => 'userpanel']) 212 | ? $linksManager->getBalanceLink($customerId, ['utm_medium' => 'userpanel'])->link : ''; 213 | if($balanceLink != '') { 214 | $smarty->assign('billtech_balance_link', $balanceLink); 215 | $billtech_balance_button = $smarty->fetch('button' . DIRECTORY_SEPARATOR . $style . DIRECTORY_SEPARATOR . 'billtechbalancebutton.html'); 216 | 217 | $smarty->assign('custom_content', $smarty->getTemplateVars('custom_content') . $billtech_balance_button); 218 | } 219 | } 220 | return $hook_data; 221 | } 222 | 223 | function fillEmailHeaders($doc, $headers) 224 | { 225 | global $LMS; 226 | 227 | $doc_content = $LMS->GetInvoiceContent($doc['id']); 228 | $document_number = (!empty($doc['template']) ? $doc['template'] : '%N/LMS/%Y'); 229 | $document_number = docnumber(array( 230 | 'number' => $doc['number'], 231 | 'template' => $document_number, 232 | 'cdate' => $doc['cdate'] + date('Z'), 233 | 'customerid' => $doc['customerid'], 234 | )); 235 | 236 | $nrb = iban_account('PL', 26, $doc_content['customerid'], $doc_content['account']); 237 | 238 | if ($nrb == "" && !empty($doc_content['bankaccounts'])) { 239 | $nrb = $doc_content['bankaccounts'][0]; 240 | } 241 | 242 | $headers['X-BillTech-ispId'] = ConfigHelper::getConfig('billtech.isp_id'); 243 | $headers['X-BillTech-customerId'] = $doc_content['customerid']; 244 | $headers['X-BillTech-invoiceNumber'] = $document_number; 245 | $headers['X-BillTech-nrb'] = $nrb; 246 | $headers['X-BillTech-amount'] = $doc_content['total']; 247 | $headers['X-BillTech-paymentDue'] = $doc_content['pdate']; 248 | 249 | return $headers; 250 | } 251 | 252 | private function createRowButton(Smarty $smarty, $link, $style = 'default') 253 | { 254 | $smarty->assign('link', $link); 255 | return $smarty->fetch('button' . DIRECTORY_SEPARATOR . $style . DIRECTORY_SEPARATOR . 'billtechrowbutton.html'); 256 | } 257 | 258 | function createEmailButton($mail_format, $link) 259 | { 260 | global $SMARTY; 261 | if (!$link) { 262 | return ''; 263 | } else if (isset($SMARTY) && isset($mail_format) && $mail_format == 'html') { 264 | $SMARTY->assign('link', $link); 265 | return $SMARTY->fetch('button/billtechemailbutton.html'); 266 | } else { 267 | return 'Opłać teraz: ' . $link; 268 | } 269 | } 270 | } -------------------------------------------------------------------------------- /handlers/BillTechPaymentCashImportHandler.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | 8 | class BillTechPaymentCashImportHandler 9 | { 10 | function processCashImport(array $hookdata = array()) 11 | { 12 | global $DB, $LMS; 13 | 14 | foreach ($hookdata['cashimports'] as $import) { 15 | $description = $import['description']; 16 | 17 | if (ConfigHelper::checkConfig('billtech.cashimport_enabled') && $description) { 18 | $description = preg_replace('/[,|]/', '', $description); 19 | preg_match('/ref[: ](\d{8}-\d{4,6})/i', $description, $matches); 20 | if (isset($matches[1])) { 21 | $reference_number = $matches[1]; 22 | $payments = $DB->GetAll("SELECT id, amount, cashid FROM billtech_payments WHERE reference_number=? AND closed=0", array($reference_number)); 23 | if (is_array($payments) && !empty($payments)) { 24 | foreach ($payments as $payment) { 25 | $cash = $LMS->GetCashByID($payment['cashid']); 26 | if ($cash && strpos($cash['comment'], BillTech::CASH_COMMENT) !== false) { 27 | $DB->Execute("UPDATE billtech_payments SET closed = 1, cashid = NULL WHERE id = ?", array($payment['id'])); 28 | $LMS->DelBalance($payment['cashid']); 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | return $hookdata; 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | getMessage(), E_USER_WARNING); 71 | // can't working without database 72 | die("Fatal error: cannot connect to database!
"); 73 | } 74 | 75 | $_SERVER['REMOTE_ADDR'] = str_replace("::ffff:", "", $_SERVER['REMOTE_ADDR']); 76 | 77 | $AUTH = null; 78 | $SYSLOG = SYSLOG::getInstance(); 79 | if ($SYSLOG) { 80 | $SYSLOG->NewTransaction('userpanel'); 81 | } 82 | 83 | $LMS = new LMS($DB, $AUTH, $SYSLOG); 84 | 85 | // Include required files (including sequence is important) 86 | require_once(LIB_DIR . DIRECTORY_SEPARATOR . 'common.php'); 87 | require_once(LIB_DIR . DIRECTORY_SEPARATOR . 'language.php'); 88 | include_once(LIB_DIR . DIRECTORY_SEPARATOR . 'definitions.php'); 89 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sys_dir=$(awk -F "=" '/^sys_dir/ {gsub(/[ \t]/, "", $2); print $2}' /etc/lms/lms.ini) 3 | 4 | if [ ! -e ${sys_dir}/plugins/BillTech/lms.pem ]; then 5 | openssl genrsa -out lms.pem 2048 6 | openssl rsa -in lms.pem -pubout > lms.pub 7 | fi 8 | 9 | cd ../../ 10 | composer require guzzlehttp/guzzle:^6.0 11 | composer dump-autoload 12 | 13 | cd plugins/BillTech 14 | chmod 0644 cron/* 15 | chmod +x bin/* 16 | mkdir /var/log/billtech 17 | (crontab -l ; eval "echo \"$(cat cron/*)\"") | crontab - -------------------------------------------------------------------------------- /lib/BillTechApiClientFactory.php: -------------------------------------------------------------------------------- 1 | 9 | self::endsWith($api_url, '/') ? 10 | substr_replace($api_url, '', -1, 1) : $api_url, 11 | 'auth' => [ 12 | ConfigHelper::getConfig('billtech.api_key'), 13 | ConfigHelper::getConfig('billtech.api_secret') 14 | ] 15 | ]); 16 | } 17 | 18 | private static function endsWith($haystack, $needle) { 19 | $length = strlen($needle); 20 | if(!$length) { 21 | return false; 22 | } 23 | return substr($haystack, -$length) === $needle; 24 | } 25 | } -------------------------------------------------------------------------------- /lib/BillTechLink.php: -------------------------------------------------------------------------------- 1 | customerId = $customerId; 18 | $this->srcCashId = $cashId; 19 | $this->srcDocumentId = $documentId; 20 | $this->amount = $amount; 21 | } 22 | 23 | public function getKey() 24 | { 25 | return isset($this->srcDocumentId) ? $this->srcDocumentId : 'cash_' . $this->srcCashId; 26 | } 27 | 28 | /** 29 | * @param $customerId 30 | * @param $cashId 31 | * @param $documentId 32 | * @param $amount 33 | * @return BillTechLink 34 | */ 35 | public static function linked($customerId, $cashId, $documentId, $amount) 36 | { 37 | $instance = new self($customerId, $cashId, $documentId, $amount); 38 | $instance->type = 'linked'; 39 | return $instance; 40 | } 41 | 42 | public static function fromRow(array $row) 43 | { 44 | $instance = new self(null, null, null, null); 45 | $instance->id = $row['id']; 46 | $instance->customerId = $row['customer_id']; 47 | $instance->srcCashId = $row['src_cash_id']; 48 | $instance->srcDocumentId = $row['src_document_id']; 49 | $instance->type = $row['type']; 50 | $instance->link = $row['link']; 51 | $instance->shortLink = $row['short_link']; 52 | $instance->token = $row['token']; 53 | $instance->amount = floatval($row['amount']); 54 | return $instance; 55 | } 56 | } -------------------------------------------------------------------------------- /lib/BillTechLinkApiService.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | 8 | 9 | use GuzzleHttp\Exception\ClientException; 10 | 11 | class BillTechLinkApiService 12 | { 13 | const BASE_PATH = '/pay/v1/payments'; 14 | 15 | /** 16 | * @param BillTechLink[] $linkRequests 17 | * @return GeneratedBilltechLink[] 18 | * @throws Exception 19 | */ 20 | public static function generatePaymentLinks($linkRequests) 21 | { 22 | $isp_id = ConfigHelper::getConfig('billtech.isp_id'); 23 | $client = BillTechApiClientFactory::getClient(); 24 | 25 | $linkDataList = array(); 26 | $apiRequests = array(); 27 | 28 | foreach ($linkRequests as $linkRequest) { 29 | array_push($linkDataList, $linkData = self::getLinkData($linkRequest)); 30 | array_push($apiRequests, self::createApiRequest($linkData)); 31 | } 32 | 33 | try { 34 | $response = $client->post(self::BASE_PATH, [ 35 | 'json' => [ 36 | 'providerCode' => ConfigHelper::getConfig('billtech.isp_id'), 37 | 'payments' => $apiRequests 38 | ], 39 | 'exceptions' => FALSE 40 | ]); 41 | } catch (ClientException $e) { 42 | $response = $e->GetResponse(); 43 | if ($response) { 44 | self::handleBadResponse($response, self::BASE_PATH); 45 | } else { 46 | throw $e; 47 | } 48 | } 49 | 50 | if ($response->getStatusCode() != 201) { 51 | self::handleBadResponse($response, self::BASE_PATH); 52 | } 53 | 54 | $json = json_decode($response->getBody()); 55 | 56 | $result = array(); 57 | 58 | foreach ($json as $idx => $link) { 59 | $linkData = $linkDataList[$idx]; 60 | $link->link = $link->link . 61 | '?email=' . urlencode($linkData['email']) . 62 | '&name=' . urlencode(self::getNameOrSurname($linkData['name'])) . 63 | '&surname=' . urlencode(self::getNameOrSurname($linkData['lastname'])) . 64 | '&utm_content=' . urlencode($isp_id) . 65 | '&utm_source=isp'; 66 | array_push($result, $link); 67 | } 68 | 69 | 70 | return $result; 71 | } 72 | 73 | /** @throws Exception 74 | * @var string $token 75 | * @var string $resolution 76 | */ 77 | public static function cancelPaymentLink($token, $resolution = "CANCELLED") 78 | { 79 | $client = BillTechApiClientFactory::getClient(); 80 | $path = self::BASE_PATH . '/' . $token . '/cancel'; 81 | $response = $client->post($path, [ 82 | "json" => [ 83 | "resolution" => $resolution 84 | ], 85 | "exceptions" => FALSE 86 | ]); 87 | 88 | if (!in_array($response->getStatusCode(), [204, 404, 409])) { 89 | throw new Exception($path . " returned code " . $response->getStatusCode() . "\n" . $response->getBody()); 90 | } 91 | } 92 | 93 | /** 94 | * @param BillTechLink $linkRequest 95 | * @return array|bool 96 | */ 97 | public static function getLinkData($linkRequest) 98 | { 99 | global $DB; 100 | 101 | if ($linkRequest->srcDocumentId) { 102 | $linkData = $DB->GetRow("select" . ($DB->GetDbType() == "postgres" ? " distinct on (c.id)" : "") . 103 | " d.customerid, d.number, d.fullnumber, d.comment, COALESCE(NULLIF(d.div_account,''), di.account) as div_account, d.id, d.name as fullname, c.lastname, 104 | c.name, d.cdate, d.paytime, di.id as division_id, di.shortname as division_name, cc.contact as email 105 | from documents d 106 | left join customers c on d.customerid = c.id 107 | left join customercontacts cc on cc.customerid = c.id and (cc.type & 8) > 1 108 | left join divisions di on c.divisionid = di.id where d.id = ?" . ($DB->GetDbType() == "mysql" ? " group by c.id" : ""), [$linkRequest->srcDocumentId]); 109 | if (!$linkData) { 110 | throw new Exception("Could not fetch link data by document id: " . $linkRequest->srcDocumentId); 111 | } 112 | $linkData['title'] = self::getDocumentTitle($linkData['fullnumber'], $linkData['comment'], $linkData['fullname']); 113 | } else { 114 | $linkData = $DB->GetRow("select" . ($DB->GetDbType() == "postgres" ? " distinct on (cu.id)" : "") . 115 | " ca.customerid, ca.docid, cu.lastname, cu.name, d.cdate, d.paytime, ca.comment as title, di.id as division_id, di.shortname as division_name, cc.contact as email, di.account as div_account from cash ca 116 | left join customers cu on ca.customerid = cu.id 117 | left join customercontacts cc on cc.customerid = cu.id and (cc.type & 8) > 1 118 | left join documents d on d.id = ca.docid 119 | left join divisions di on cu.divisionid = di.id 120 | where ca.id = ?" . ($DB->GetDbType() == "mysql" ? " group by cu.id" : ""), [$linkRequest->srcCashId]); 121 | if (!$linkData) { 122 | throw new Exception("Could not fetch link data by cash id: " . $linkRequest->srcCashId); 123 | } 124 | } 125 | 126 | $linkData['key'] = $linkRequest->getKey(); 127 | $linkData['amount'] = $linkRequest->amount; 128 | $linkData['pdate'] = $linkData['cdate'] + ($linkData['paytime'] * 86400); 129 | return $linkData; 130 | } 131 | 132 | private static function getDocumentTitle($fullnumber, $comment, $name) 133 | { 134 | if ($fullnumber != '' || $comment != '') { 135 | return $comment != '' ? $fullnumber . ' ' . $comment : $fullnumber; 136 | } else { 137 | return 'Faktura ' . $name; 138 | } 139 | } 140 | 141 | /** 142 | * @param $response 143 | * @throws Exception 144 | */ 145 | public static function handleBadResponse($response, $path) 146 | { 147 | $message = $path . " returned code " . $response->getStatusCode() . "\n" . $response->getBody(); 148 | echo $message; 149 | throw new Exception($message); 150 | } 151 | 152 | /** 153 | * @param $customerId 154 | * @param $divisionBankAccount 155 | * @return string 156 | */ 157 | private static function getBankAccount($customerId, $divisionBankAccount) 158 | { 159 | global $DB; 160 | 161 | $alternativeBankAccounts = $DB->GetAll( 162 | 'SELECT contact FROM customercontacts WHERE customerid = ? AND (type & ?) = ?', 163 | array( 164 | $customerId, 165 | (CONTACT_BANKACCOUNT | CONTACT_INVOICES | CONTACT_DISABLED), 166 | (CONTACT_BANKACCOUNT | CONTACT_INVOICES) 167 | ) 168 | ); 169 | 170 | if (!empty($alternativeBankAccounts)) { 171 | return iban_account('PL', 26, $customerId, $alternativeBankAccounts[0]['contact']); 172 | } 173 | 174 | return iban_account('PL', 26, $customerId, $divisionBankAccount); 175 | } 176 | 177 | /** 178 | * @param $linkData 179 | * @return array 180 | * @throws Exception 181 | */ 182 | private static function createApiRequest($linkData) 183 | { 184 | $request = array( 185 | 'userId' => $linkData['customerid'], 186 | 'operationId' => $linkData['key'], 187 | 'amount' => $linkData['amount'], 188 | 'nrb' => ConfigHelper::getConfig('billtech.bankaccount', self::getBankAccount($linkData['customerid'], $linkData['div_account'])), 189 | 'paymentDue' => (new DateTime('@' . ($linkData['pdate'] ?: time())))->format('Y-m-d'), 190 | 'title' => self::getTitle($linkData['title']) 191 | ); 192 | 193 | if (ConfigHelper::checkConfig("billtech.append_customer_info")) { 194 | $request = array_merge_recursive($request, array( 195 | 'name' => self::getNameOrSurname($linkData['name']), 196 | 'surname' => self::getNameOrSurname($linkData['lastname']), 197 | 'email' => $linkData['email'] ? trim($linkData['email']) : null, 198 | )); 199 | } 200 | 201 | if (ConfigHelper::checkConfig("billtech.branding_enabled")) { 202 | $request = array_merge_recursive($request, array( 203 | 'recipient' => array( 204 | 'id' => $linkData['division_id'], 205 | 'name' => self::getRecipientName($linkData['division_name']) 206 | ) 207 | )); 208 | } 209 | 210 | return $request; 211 | } 212 | 213 | /** 214 | * @param string $title 215 | * @return string 216 | */ 217 | private static function getTitle($title) 218 | { 219 | return substr(preg_replace("/[^ A-Za-z0-9#&_\-',.\\/\x{00c0}-\x{02c0}]/u", " ", $title), 0, 105) ?: ""; 220 | } 221 | 222 | private static function getNameOrSurname($nameOrSurname) 223 | { 224 | return substr(preg_replace("/[^ A-Za-z0-9\-,.\x{00c0}-\x{02c0}]/u", " ", $nameOrSurname), 0, 100) ?: null; 225 | } 226 | 227 | private static function getRecipientName($divisionName) 228 | { 229 | return substr(preg_replace("/[^ A-Za-z0-9\-,.\x{00c0}-\x{02c0}]+/u", " ", $divisionName), 0, 35); 230 | } 231 | } 232 | 233 | class GeneratedBilltechLink 234 | { 235 | public $token; 236 | public $link; 237 | public $shortLink; 238 | } 239 | -------------------------------------------------------------------------------- /lib/BillTechLinksManager.php: -------------------------------------------------------------------------------- 1 | verbose = $verbose; 12 | $this->linkShortener = new LinkShortenerApiService(); 13 | } 14 | 15 | /** @return BillTechLink[] 16 | * @var string $customerId 17 | */ 18 | public function getCustomerPaymentLinks($customerId) 19 | { 20 | global $DB; 21 | $rows = $DB->GetAll("select bpl.*, c.docid 22 | from billtech_payment_links bpl 23 | left join cash c on c.id = bpl.src_cash_id 24 | left join billtech_payments bp on bpl.token = bp.token 25 | where bp.id is null 26 | and customer_id = ?", array($customerId)); 27 | if (!is_array($rows)) { 28 | return array(); 29 | } 30 | return array_map(function ($row) { 31 | return BillTechLink::fromRow($row); 32 | }, $rows); 33 | } 34 | 35 | public function getCashLinkByDocumentId($documentId, $params) 36 | { 37 | global $DB; 38 | $row = $DB->GetRow("select bpl.* from billtech_payment_links bpl 39 | left join cash c on bpl.src_cash_id = c.id 40 | left join documents d on c.docid=d.id 41 | where d.id=?", array($documentId)); 42 | 43 | if (!$row) { 44 | return null; 45 | } 46 | $link = BillTechLink::fromRow($row); 47 | 48 | $this->addParamsToLink($params, $link); 49 | return $link; 50 | } 51 | 52 | public function getBalanceLink($customerId, $params) 53 | { 54 | global $DB; 55 | $row = $DB->GetRow("select l.*, c.docid from billtech_payment_links l 56 | left join cash c on l.src_cash_id = c.id 57 | left join billtech_payments bp on l.token = bp.token 58 | where customer_id = ? and bp.id is null 59 | order by c.time desc limit 1", array($customerId)); 60 | if (!$row) { 61 | return null; 62 | } else { 63 | $balanceLink = BillTechLink::fromRow($row); 64 | $this->addParamsToLink(array_merge($params, ['type' => 'balance']), $balanceLink); 65 | return $balanceLink; 66 | } 67 | } 68 | 69 | public function updateForAll() 70 | { 71 | global $DB; 72 | $actions = array( 73 | 'add' => array(), 74 | 'update' => array(), 75 | 'close' => array(), 76 | ); 77 | $this->addMissingCustomerInfo(); 78 | $customerIds = $this->getCustomerIdsForUpdate(); 79 | 80 | if ($this->verbose) { 81 | echo "Found " . count($customerIds) . " customers to update\n"; 82 | } 83 | 84 | if (!is_array($customerIds)) { 85 | return; 86 | } 87 | 88 | $maxCashId = $DB->GetOne("select max(id) from cash"); 89 | 90 | foreach ($customerIds as $idx => $customerId) { 91 | echo "Collecting actions for customer " . ($idx + 1) . " of " . count($customerIds) . "\n"; 92 | $actions = array_merge_recursive($actions, $this->getCustomerUpdateBalanceActions($customerId)); 93 | } 94 | 95 | if ($this->verbose) { 96 | echo "Adding " . count($actions['add']) . " links\n"; 97 | echo "Updating " . count($actions['update']) . " links\n"; 98 | echo "Cancelling " . count($actions['close']) . " links\n"; 99 | } 100 | 101 | if ($this->performActions($actions)) { 102 | $this->updateCustomerInfos($customerIds, $maxCashId); 103 | } 104 | } 105 | 106 | public function updateCustomerBalance($customerId) 107 | { 108 | global $DB; 109 | $this->addMissingCustomerInfo(); 110 | 111 | $customerInfo = $DB->GetRow("select bci.*, max(c.id) as new_last_cash_id from billtech_customer_info bci 112 | left join cash c on c.customerid = bci.customer_id 113 | where bci.customer_id = ? 114 | group by bci.customer_id", array($customerId)); 115 | 116 | if ($customerInfo['new_last_cash_id'] > $customerInfo['last_cash_id']) { 117 | $actions = $this->getCustomerUpdateBalanceActions($customerId); 118 | if ($this->performActions($actions)) { 119 | $DB->Execute("update billtech_customer_info set last_cash_id = ? where customer_id = ?", array($customerInfo['new_last_cash_id'], $customerId)); 120 | } 121 | } 122 | } 123 | 124 | /** 125 | * @param $cashItems array 126 | * @return BillTechLink[] 127 | */ 128 | public function getTargetLinks(array $cashItems) 129 | { 130 | $balance = array_reduce($cashItems, function ($carry, $item) { 131 | return $carry + $item['value']; 132 | }, 0.0); 133 | $liabilities = array(); 134 | 135 | if (!is_array($cashItems)) { 136 | return array(); 137 | } 138 | 139 | foreach ($cashItems as $cash) { 140 | $intCashValue = self::moneyToInt($cash['value']); 141 | $intBalance = self::moneyToInt($balance); 142 | if ($intCashValue >= 0) { 143 | continue; 144 | } 145 | if ($intBalance < 0) { 146 | $amountToPay = self::intToMoney(-max(min($intBalance, 0), $intCashValue)); 147 | 148 | $key = isset($cash['docid']) ? $cash['docid'] : 'cash_' . $cash['id']; 149 | 150 | if (isset($liabilities[$key])) { 151 | $liabilities[$key]->amount += $amountToPay; 152 | } else { 153 | $liabilities[$key] = BillTechLink::linked($cash['customerid'], $cash['id'], $cash['docid'], $amountToPay); 154 | } 155 | } 156 | $balance = self::intToMoney($intBalance - $intCashValue); 157 | } 158 | 159 | return array_values($liabilities); 160 | } 161 | 162 | /* @throws Exception 163 | * @var $links BillTechLink[] 164 | */ 165 | private function addPayments($links) 166 | { 167 | global $DB; 168 | 169 | $generatedLinks = BillTechLinkApiService::generatePaymentLinks($links); 170 | $values = array(); 171 | foreach ($generatedLinks as $idx => $generatedLink) { 172 | $link = $links[$idx]; 173 | array_push($values, 174 | $link->customerId, 175 | $link->srcCashId, 176 | $link->srcDocumentId, 177 | $link->type, 178 | $generatedLink->link, 179 | $generatedLink->shortLink, 180 | $generatedLink->token, 181 | number_format($link->amount, 2, '.', '') 182 | ); 183 | } 184 | 185 | $sql = "insert into billtech_payment_links(customer_id, src_cash_id, src_document_id, type, link, short_link, token, amount) values " . 186 | BillTech::prepareMultiInsertPlaceholders(count($generatedLinks), 8) . ";"; 187 | $DB->Execute($sql, $values); 188 | } 189 | 190 | /* @throws Exception 191 | * @var $link BillTechLink 192 | */ 193 | private function updatePaymentAmount(BillTechLink $link) 194 | { 195 | global $DB; 196 | if (self::shouldCancelLink($link)) { 197 | BillTechLinkApiService::cancelPaymentLink($link->token); 198 | } 199 | $generatedLink = BillTechLinkApiService::generatePaymentLinks([$link])[0]; 200 | $DB->Execute("update billtech_payment_links set amount = ?, link = ?, short_link = ?, token = ? where id = ?", 201 | array(number_format($link->amount, 2, '.', ''), $generatedLink->link, $generatedLink->shortLink, $generatedLink->token, $link->id)); 202 | } 203 | 204 | /* @throws Exception 205 | * @var $link BillTechLink 206 | */ 207 | private function closePayment(BillTechLink $link) 208 | { 209 | global $DB; 210 | if (self::shouldCancelLink($link)) { 211 | BillTechLinkApiService::cancelPaymentLink($link->token, "PAID"); 212 | } 213 | $DB->Execute("delete from billtech_payment_links where id = ?", array($link->id)); 214 | } 215 | 216 | /** 217 | * @return array 218 | */ 219 | private function getPaymentLinksToCancel() 220 | { 221 | global $DB; 222 | return $DB->GetCol("select token from billtech_payment_links where src_cash_id is null and src_document_id is null")?: array(); 223 | } 224 | 225 | /** 226 | * @var $linkToken String 227 | */ 228 | private function deletePaymentLinkByToken($linkToken) 229 | { 230 | global $DB; 231 | $DB->Execute("delete from billtech_payment_links where token = ?", array($linkToken)); 232 | } 233 | 234 | private function shouldCancelLink($link) 235 | { 236 | global $DB; 237 | return $DB->GetOne("select count(*) from billtech_payments where token = ?", array($link->token)) == 0; 238 | } 239 | 240 | public static function moneyToInt($value) 241 | { 242 | return intval(round($value * 100)); 243 | } 244 | 245 | public static function intToMoney($value) 246 | { 247 | return $value / 100.0; 248 | } 249 | 250 | public function cancelPaymentLinksIfManuallyDeletedLiability() { 251 | $paymentLinkTokensToCancel = $this->getPaymentLinksToCancel(); 252 | foreach($paymentLinkTokensToCancel as $linkToken){ 253 | BillTechLinkApiService::cancelPaymentLink($linkToken); 254 | $this->deletePaymentLinkByToken($linkToken); 255 | } 256 | echo "Cancelled " . count($paymentLinkTokensToCancel) . " links for manually deleted liability\n"; 257 | } 258 | 259 | /** 260 | * @param array $actions 261 | * @return bool 262 | */ 263 | public function performActions($actions) 264 | { 265 | global $DB; 266 | $addBatches = array_chunk($actions['add'], $this->batchSize); 267 | $errorCount = 0; 268 | foreach ($addBatches as $idx => $links) { 269 | if ($this->verbose) { 270 | echo "Adding batch " . ($idx + 1) . " of " . count($addBatches) . "\n"; 271 | } 272 | try { 273 | $DB->BeginTrans(); 274 | $this->addPayments($links); 275 | if (!$DB->GetErrors()) { 276 | $DB->CommitTrans(); 277 | } else { 278 | foreach ($DB->GetErrors() as $error) { 279 | echo $error['query'] . PHP_EOL; 280 | echo $error['error'] . PHP_EOL; 281 | } 282 | $errorCount++; 283 | $DB->RollbackTrans(); 284 | } 285 | } catch (Exception $e) { 286 | $errorCount++; 287 | if (ConfigHelper::checkConfig("billtech.debug")) { 288 | echo $e->getMessage(); 289 | } 290 | } 291 | } 292 | 293 | foreach ($actions['update'] as $idx => $link) { 294 | if ($this->verbose) { 295 | echo "Updating link " . ($idx + 1) . " of " . count($actions['update']) . "\n"; 296 | } 297 | try { 298 | $DB->BeginTrans(); 299 | $this->updatePaymentAmount($link); 300 | if (!$DB->GetErrors()) { 301 | $DB->CommitTrans(); 302 | } else { 303 | foreach ($DB->GetErrors() as $error) { 304 | echo $error['query'] . PHP_EOL; 305 | echo $error['error'] . PHP_EOL; 306 | } 307 | $errorCount++; 308 | $DB->RollbackTrans(); 309 | } 310 | } catch (Exception $e) { 311 | $errorCount++; 312 | if (ConfigHelper::checkConfig("billtech.debug")) { 313 | echo $e->getMessage(); 314 | } 315 | } 316 | } 317 | 318 | foreach ($actions['close'] as $idx => $link) { 319 | if ($this->verbose) { 320 | echo "Closing link " . ($idx + 1) . " of " . count($actions['close']) . "\n"; 321 | } 322 | try { 323 | $DB->BeginTrans(); 324 | $this->closePayment($link); 325 | if (!$DB->GetErrors()) { 326 | $DB->CommitTrans(); 327 | } else { 328 | foreach ($DB->GetErrors() as $error) { 329 | echo $error['query'] . PHP_EOL; 330 | echo $error['error'] . PHP_EOL; 331 | } 332 | $errorCount++; 333 | $DB->RollbackTrans(); 334 | } 335 | } catch (Exception $e) { 336 | $errorCount++; 337 | if ($this->verbose) { 338 | echo $e->getMessage(); 339 | } 340 | } 341 | } 342 | 343 | return $errorCount == 0; 344 | } 345 | 346 | /** 347 | * @param $customerId 348 | * @return array 349 | */ 350 | private function getCustomerUpdateBalanceActions($customerId) 351 | { 352 | global $DB; 353 | $actions = array( 354 | "add" => array(), 355 | "update" => array(), 356 | "close" => array() 357 | ); 358 | $cashItems = $DB->GetAll("select c.id, value, c.customerid, d.id as docid from cash c left join documents d on d.id = c.docid where c.customerid = ? order by c.time desc, c.id desc", array($customerId)); 359 | if (!$cashItems) { 360 | return $actions; 361 | } 362 | 363 | $targetLinks = $this->getTargetLinks($cashItems); 364 | $existingLinks = $this->getCustomerPaymentLinks($customerId); 365 | $existingLinkMap = BillTech::toMap(function ($link) { 366 | /* @var $payment BillTechLink */ 367 | return $link->getKey(); 368 | }, $existingLinks); 369 | 370 | foreach ($targetLinks as $targetLink) { 371 | if(isset($existingLinkMap[$targetLink->getKey()])) { 372 | $existingLink = $existingLinkMap[$targetLink->getKey()]; 373 | if(self::moneyToInt($existingLink->amount) != self::moneyToInt($targetLink->amount)) { 374 | $existingLink->amount = $targetLink->amount; 375 | array_push($actions['update'], $existingLink); 376 | } 377 | unset($existingLinkMap[$targetLink->getKey()]); 378 | } else if (self::moneyToInt($targetLink->amount) > 0){ 379 | array_push($actions['add'], $targetLink); 380 | } 381 | } 382 | 383 | foreach ($existingLinkMap as $cashId => $existingLink) { 384 | array_push($actions['close'], $existingLink); 385 | } 386 | 387 | return $actions; 388 | } 389 | 390 | private function addMissingCustomerInfo() 391 | { 392 | global $DB; 393 | $DB->Execute("insert into billtech_customer_info (customer_id, last_cash_id) 394 | select cu.id, 0 395 | from customers cu 396 | left join billtech_customer_info bci on bci.customer_id = cu.id 397 | where bci.customer_id is null;"); 398 | } 399 | 400 | /** 401 | * @return array 402 | */ 403 | private function getCustomerIdsForUpdate() 404 | { 405 | global $DB; 406 | return $DB->GetCol("select bci.customer_id 407 | from customers cu 408 | left join billtech_customer_info bci on bci.customer_id = cu.id 409 | left join cash ca on ca.customerid = cu.id 410 | group by bci.customer_id, bci.last_cash_id 411 | having bci.last_cash_id <= coalesce(max(ca.id), 0);"); 412 | } 413 | 414 | /** 415 | * @param array $customerIds 416 | * @param $maxCashId 417 | */ 418 | private function updateCustomerInfos(array $customerIds, $maxCashId) 419 | { 420 | global $DB; 421 | $params = $customerIds; 422 | array_unshift($params, $maxCashId); 423 | $DB->Execute("update billtech_customer_info set last_cash_id = ? where customer_id in (" . BillTech::repeatWithSeparator("?", ",", count($customerIds)) . ")", $params); 424 | } 425 | 426 | /** 427 | * @param array $params 428 | * @param BillTechLink $link 429 | */ 430 | private function addParamsToLink(array $params, BillTechLink $link) 431 | { 432 | $link->link .= http_build_query($params); 433 | 434 | if ($link->shortLink) { 435 | $link->shortLink = $this->linkShortener->addParameters($link->shortLink, $params); 436 | } 437 | } 438 | } 439 | -------------------------------------------------------------------------------- /lib/LinkShortenerApiService.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | 8 | use GuzzleHttp\Client; 9 | use GuzzleHttp\Exception\ClientException; 10 | 11 | class LinkShortenerApiService 12 | { 13 | private $client; 14 | 15 | public function __construct() 16 | { 17 | $this->client = new Client([ 18 | 'base_uri' => 'https://zapl.ac', 19 | ]); 20 | } 21 | 22 | public function addParameters($url, $params = array()) 23 | { 24 | $retryLimit = 3; 25 | $retryCount = 1; 26 | 27 | while($retryCount <= $retryLimit) { 28 | try { 29 | return $this->postEncodeUrl($url, $params); 30 | } catch (Exception $e) { 31 | $retryCount++; 32 | sleep(1); 33 | if($retryCount === $retryLimit) { 34 | $error = $e->getResponse(); 35 | echo "Unable to add parameters to the link. Server response: ".$error; 36 | } 37 | continue; 38 | } 39 | } 40 | 41 | return ""; 42 | } 43 | 44 | private function postEncodeUrl($url, $params) 45 | { 46 | $response = $this->client->post('/encode', [ 47 | 'query' => $params, 48 | 'json' => [ 49 | 'url' => $url 50 | ]]); 51 | return "" . $response->getBody(); 52 | } 53 | } -------------------------------------------------------------------------------- /lib/upgradedb/mysql.2017112400.php: -------------------------------------------------------------------------------- 1 | Execute(" 3 | CREATE TABLE billtech_log 4 | ( 5 | cdate INT DEFAULT 0 NOT NULL, 6 | type VARCHAR(255) DEFAULT '' NOT NULL, 7 | description TEXT NOT NULL 8 | ) 9 | ENGINE = InnoDB; 10 | "); 11 | 12 | $this->Execute("INSERT INTO uiconfig(section, var, value) 13 | SELECT 'billtech', 'isp_id', '' 14 | WHERE NOT EXISTS (SELECT 1 from uiconfig WHERE section = 'billtech' AND var = 'isp_id')"); 15 | $this->Execute("INSERT INTO uiconfig(section, var, value) 16 | SELECT 'billtech', 'payment_url', '' 17 | WHERE NOT EXISTS (SELECT 1 from uiconfig WHERE section = 'billtech' AND var = 'payment_url')"); 18 | $this->Execute("INSERT INTO uiconfig(section, var, value) 19 | SELECT 'billtech', 'api_url', '' 20 | WHERE NOT EXISTS (SELECT 1 from uiconfig WHERE section = 'billtech' AND var = 'api_url')"); 21 | $this->Execute("INSERT INTO uiconfig(section, var, value) 22 | SELECT 'billtech', 'api_key', '' 23 | WHERE NOT EXISTS (SELECT 1 from uiconfig WHERE section = 'billtech' AND var = 'api_key')"); 24 | $this->Execute("INSERT INTO uiconfig(section, var, value) 25 | SELECT 'billtech', 'api_secret', '' 26 | WHERE NOT EXISTS (SELECT 1 from uiconfig WHERE section = 'billtech' AND var = 'api_secret')"); 27 | 28 | $this->Execute("UPDATE dbinfo SET keyvalue = ? WHERE keytype = ?",array('2017112400', 'dbversion_BillTech')); 29 | -------------------------------------------------------------------------------- /lib/upgradedb/mysql.2020091900.php: -------------------------------------------------------------------------------- 1 | Execute(" 3 | create table billtech_payment_links ( 4 | id serial primary key, 5 | customer_id integer not null references customers(id) on delete cascade, 6 | src_cash_id integer references cash(id) on delete cascade, 7 | src_document_id integer references documents(id) on delete cascade, 8 | type varchar(255) not null, 9 | link varchar(2000) not null, 10 | short_link varchar(160), 11 | token varchar(1000) not null, 12 | amount numeric(9,2) not null 13 | ); 14 | "); 15 | 16 | $this->Execute("create index billtech_payment_links__customer_id on billtech_payment_links (customer_id);"); 17 | $this->Execute("create index billtech_payment_links__src_cash_id on billtech_payment_links (src_cash_id);"); 18 | $this->Execute("create index billtech_payment_links__src_document_id on billtech_payment_links (src_document_id);"); 19 | $this->Execute("create index billtech_payment_links__token on billtech_payment_links (token);"); 20 | 21 | $this->Execute("alter table billtech_payments add column token varchar(1000);"); 22 | $this->Execute("create index billtech_payments__reference_number on billtech_payments(reference_number);"); 23 | $this->Execute("create index billtech_payments__closed_cdate on billtech_payments(closed, cdate);"); 24 | $this->Execute("create index billtech_payments__token on billtech_payments(token);"); 25 | 26 | $this->Execute("create table billtech_customer_info 27 | ( 28 | customer_id int primary key, 29 | last_cash_id int 30 | );"); 31 | 32 | $this->Execute("create index billtech_customer_info__customer_id on billtech_customer_info (customer_id);"); 33 | 34 | $this->Execute("INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'cashimport_enabled', true)"); 35 | $this->Execute("INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'manage_cutoff', true)"); 36 | $this->Execute("INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'append_client_info', true)"); 37 | $this->Execute("UPDATE uiconfig SET value='never' WHERE section='billtech' AND var='payment_expiration' AND value=0"); 38 | 39 | $this->Execute("UPDATE billtech_payments SET title='Wpłata online' WHERE title='BillTech Payments' AND closed=0"); 40 | 41 | $this->Execute("UPDATE dbinfo SET keyvalue = ? WHERE keytype = ?", array('2020091900', 'dbversion_BillTech')); -------------------------------------------------------------------------------- /lib/upgradedb/mysql.2021041400.php: -------------------------------------------------------------------------------- 1 | Execute("UPDATE uiconfig SET var='append_customer_info' WHERE var='append_client_info'"); 3 | 4 | $this->Execute("UPDATE dbinfo SET keyvalue = ? WHERE keytype = ?", array('2021041400', 'dbversion_BillTech')); 5 | -------------------------------------------------------------------------------- /lib/upgradedb/mysql.2022011200.php: -------------------------------------------------------------------------------- 1 | Execute(" 3 | ALTER TABLE billtech_payment_links 4 | DROP FOREIGN KEY billtech_payment_links_src_cash_id_fkey, 5 | ADD CONSTRAINT billtech_payment_links_src_cash_id_fkey 6 | FOREIGN KEY (src_cash_id) REFERENCES cash ON 7 | DELETE 8 | SET NULL 9 | "); 10 | 11 | $this->Execute(" 12 | ALTER TABLE billtech_payment_links 13 | DROP FOREIGN KEY billtech_payment_links_src_document_id_fkey, 14 | ADD CONSTRAINT billtech_payment_links_src_document_id_fkey 15 | FOREIGN KEY (src_document_id) REFERENCES documents ON 16 | DELETE 17 | SET NULL 18 | "); 19 | 20 | $this->Execute("UPDATE dbinfo SET keyvalue = ? WHERE keytype = ?", array('2022011200', 'dbversion_BillTech')); 21 | -------------------------------------------------------------------------------- /lib/upgradedb/mysql.2022100400.php: -------------------------------------------------------------------------------- 1 | Execute("SELECT var from uiconfig WHERE section=? AND var=?", array('billtech', 'cashimport_enabled')); 4 | 5 | if ($cashimport_enabled) { 6 | $this->Execute("UPDATE uiconfig SET value=? WHERE section=? AND var=?", array('never', 'billtech', 'payment_expiration')); 7 | } 8 | 9 | $this->Execute("UPDATE dbinfo SET keyvalue = ? WHERE keytype = ?", array('2022100400', 'dbversion_BillTech')); -------------------------------------------------------------------------------- /lib/upgradedb/postgres.2017112400.php: -------------------------------------------------------------------------------- 1 | Execute(" 3 | CREATE TABLE billtech_log 4 | ( 5 | cdate INT DEFAULT 0 NOT NULL, 6 | type VARCHAR(255) DEFAULT '' NOT NULL, 7 | description TEXT NOT NULL 8 | ); 9 | "); 10 | 11 | $this->Execute("INSERT INTO uiconfig(section, var, value) 12 | SELECT 'billtech', 'isp_id', '' 13 | WHERE NOT EXISTS (SELECT 1 from uiconfig WHERE section = 'billtech' AND var = 'isp_id')"); 14 | $this->Execute("INSERT INTO uiconfig(section, var, value) 15 | SELECT 'billtech', 'payment_url', '' 16 | WHERE NOT EXISTS (SELECT 1 from uiconfig WHERE section = 'billtech' AND var = 'payment_url')"); 17 | $this->Execute("INSERT INTO uiconfig(section, var, value) 18 | SELECT 'billtech', 'api_url', '' 19 | WHERE NOT EXISTS (SELECT 1 from uiconfig WHERE section = 'billtech' AND var = 'api_url')"); 20 | $this->Execute("INSERT INTO uiconfig(section, var, value) 21 | SELECT 'billtech', 'api_key', '' 22 | WHERE NOT EXISTS (SELECT 1 from uiconfig WHERE section = 'billtech' AND var = 'api_key')"); 23 | $this->Execute("INSERT INTO uiconfig(section, var, value) 24 | SELECT 'billtech', 'api_secret', '' 25 | WHERE NOT EXISTS (SELECT 1 from uiconfig WHERE section = 'billtech' AND var = 'api_secret')"); 26 | 27 | $this->Execute("UPDATE dbinfo SET keyvalue = ? WHERE keytype = ?",array('2017112400', 'dbversion_BillTech')); 28 | -------------------------------------------------------------------------------- /lib/upgradedb/postgres.2020091900.php: -------------------------------------------------------------------------------- 1 | Execute(" 3 | create table billtech_payment_links ( 4 | id serial primary key, 5 | customer_id integer not null references customers(id) on delete cascade, 6 | src_cash_id integer references cash(id) on delete cascade, 7 | src_document_id integer references documents(id) on delete cascade, 8 | type varchar(255) not null, 9 | link varchar(2000) not null, 10 | short_link varchar(160), 11 | token varchar(1000) not null, 12 | amount numeric(9,2) not null 13 | ); 14 | "); 15 | 16 | $this->Execute("create index billtech_payment_links__customer_id on billtech_payment_links (customer_id);"); 17 | $this->Execute("create index billtech_payment_links__src_cash_id on billtech_payment_links (src_cash_id);"); 18 | $this->Execute("create index billtech_payment_links__src_document_id on billtech_payment_links (src_document_id);"); 19 | $this->Execute("create index billtech_payment_links__token on billtech_payment_links (token);"); 20 | 21 | $this->Execute("alter table billtech_payments add column token varchar(1000);"); 22 | $this->Execute("alter table billtech_payments add primary key (id);"); 23 | $this->Execute("create index billtech_payments__reference_number on billtech_payments(reference_number);"); 24 | $this->Execute("create index billtech_payments__closed_cdate on billtech_payments(closed, cdate);"); 25 | $this->Execute("create index billtech_payments__token on billtech_payments(token);"); 26 | 27 | $this->Execute("create table billtech_customer_info 28 | ( 29 | customer_id int primary key, 30 | last_cash_id int 31 | );"); 32 | 33 | $this->Execute("create index billtech_customer_info__customer_id on billtech_customer_info (customer_id);"); 34 | 35 | $this->Execute("INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'cashimport_enabled', true)"); 36 | $this->Execute("INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'manage_cutoff', true)"); 37 | $this->Execute("INSERT INTO uiconfig (section, var, value) VALUES ('billtech', 'append_client_info', true)"); 38 | $this->Execute("UPDATE uiconfig SET value='never' WHERE section='billtech' AND var='payment_expiration' AND value=0"); 39 | 40 | $this->Execute("UPDATE billtech_payments SET title='Wpłata online' WHERE title='BillTech Payments' AND closed=0"); 41 | 42 | $this->Execute("UPDATE dbinfo SET keyvalue = ? WHERE keytype = ?", array('2020091900', 'dbversion_BillTech')); -------------------------------------------------------------------------------- /lib/upgradedb/postgres.2021041400.php: -------------------------------------------------------------------------------- 1 | Execute("UPDATE uiconfig SET var='append_customer_info' WHERE var='append_client_info'"); 3 | 4 | $this->Execute("UPDATE dbinfo SET keyvalue = ? WHERE keytype = ?", array('2021041400', 'dbversion_BillTech')); 5 | -------------------------------------------------------------------------------- /lib/upgradedb/postgres.2022011200.php: -------------------------------------------------------------------------------- 1 | Execute(" 3 | ALTER TABLE billtech_payment_links 4 | DROP CONSTRAINT billtech_payment_links_src_cash_id_fkey, 5 | ADD CONSTRAINT billtech_payment_links_src_cash_id_fkey 6 | FOREIGN KEY (src_cash_id) REFERENCES cash ON 7 | DELETE 8 | SET NULL 9 | "); 10 | 11 | $this->Execute(" 12 | ALTER TABLE billtech_payment_links 13 | DROP CONSTRAINT billtech_payment_links_src_document_id_fkey, 14 | ADD CONSTRAINT billtech_payment_links_src_document_id_fkey 15 | FOREIGN KEY (src_document_id) REFERENCES documents ON 16 | DELETE 17 | SET NULL 18 | "); 19 | 20 | $this->Execute("UPDATE dbinfo SET keyvalue = ? WHERE keytype = ?", array('2022011200', 'dbversion_BillTech')); 21 | -------------------------------------------------------------------------------- /lib/upgradedb/postgres.2022100400.php: -------------------------------------------------------------------------------- 1 | Execute("SELECT var from uiconfig WHERE section=? AND var=?", array('billtech', 'cashimport_enabled')); 4 | 5 | if ($cashimport_enabled) { 6 | $this->Execute("UPDATE uiconfig SET value=? WHERE section=? AND var=?", array('never', 'billtech', 'payment_expiration')); 7 | } 8 | 9 | $this->Execute("UPDATE dbinfo SET keyvalue = ? WHERE keytype = ?", array('2022100400', 'dbversion_BillTech')); -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | “Commons Clause” License Condition v1.0 2 | ======================================= 3 | 4 | The Software is provided to you by the Licensor under the License, as defined below, subject to the following condition. 5 | 6 | Without limiting other conditions in the License, the grant of rights under the License will not include, and the License does not grant to you, the right to Sell the Software. 7 | 8 | For purposes of the foregoing, “Sell” means practicing any or all of the rights granted to you under the License to provide to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/ support services related to the Software), a product or service whose value derives, entirely or substantially, from the functionality of the Software. Any license notice or attribution required by the License must also include this Commons Clause License Condition notice. 9 | 10 | Software: lms-billtech-plugin 11 | 12 | License: Mozilla Public License with Commons Clause 13 | 14 | Licensor: BillTech sp. z o.o. 15 | 16 | Mozilla Public License Version 2.0 17 | ================================== 18 | 19 | 1. Definitions 20 | -------------- 21 | 22 | 1.1. "Contributor" 23 | means each individual or legal entity that creates, contributes to 24 | the creation of, or owns Covered Software. 25 | 26 | 1.2. "Contributor Version" 27 | means the combination of the Contributions of others (if any) used 28 | by a Contributor and that particular Contributor's Contribution. 29 | 30 | 1.3. "Contribution" 31 | means Covered Software of a particular Contributor. 32 | 33 | 1.4. "Covered Software" 34 | means Source Code Form to which the initial Contributor has attached 35 | the notice in Exhibit A, the Executable Form of such Source Code 36 | Form, and Modifications of such Source Code Form, in each case 37 | including portions thereof. 38 | 39 | 1.5. "Incompatible With Secondary Licenses" 40 | means 41 | 42 | (a) that the initial Contributor has attached the notice described 43 | in Exhibit B to the Covered Software; or 44 | 45 | (b) that the Covered Software was made available under the terms of 46 | version 1.1 or earlier of the License, but not also under the 47 | terms of a Secondary License. 48 | 49 | 1.6. "Executable Form" 50 | means any form of the work other than Source Code Form. 51 | 52 | 1.7. "Larger Work" 53 | means a work that combines Covered Software with other material, in 54 | a separate file or files, that is not Covered Software. 55 | 56 | 1.8. "License" 57 | means this document. 58 | 59 | 1.9. "Licensable" 60 | means having the right to grant, to the maximum extent possible, 61 | whether at the time of the initial grant or subsequently, any and 62 | all of the rights conveyed by this License. 63 | 64 | 1.10. "Modifications" 65 | means any of the following: 66 | 67 | (a) any file in Source Code Form that results from an addition to, 68 | deletion from, or modification of the contents of Covered 69 | Software; or 70 | 71 | (b) any new file in Source Code Form that contains any Covered 72 | Software. 73 | 74 | 1.11. "Patent Claims" of a Contributor 75 | means any patent claim(s), including without limitation, method, 76 | process, and apparatus claims, in any patent Licensable by such 77 | Contributor that would be infringed, but for the grant of the 78 | License, by the making, using, selling, offering for sale, having 79 | made, import, or transfer of either its Contributions or its 80 | Contributor Version. 81 | 82 | 1.12. "Secondary License" 83 | means either the GNU General Public License, Version 2.0, the GNU 84 | Lesser General Public License, Version 2.1, the GNU Affero General 85 | Public License, Version 3.0, or any later versions of those 86 | licenses. 87 | 88 | 1.13. "Source Code Form" 89 | means the form of the work preferred for making modifications. 90 | 91 | 1.14. "You" (or "Your") 92 | means an individual or a legal entity exercising rights under this 93 | License. For legal entities, "You" includes any entity that 94 | controls, is controlled by, or is under common control with You. For 95 | purposes of this definition, "control" means (a) the power, direct 96 | or indirect, to cause the direction or management of such entity, 97 | whether by contract or otherwise, or (b) ownership of more than 98 | fifty percent (50%) of the outstanding shares or beneficial 99 | ownership of such entity. 100 | 101 | 2. License Grants and Conditions 102 | -------------------------------- 103 | 104 | 2.1. Grants 105 | 106 | Each Contributor hereby grants You a world-wide, royalty-free, 107 | non-exclusive license: 108 | 109 | (a) under intellectual property rights (other than patent or trademark) 110 | Licensable by such Contributor to use, reproduce, make available, 111 | modify, display, perform, distribute, and otherwise exploit its 112 | Contributions, either on an unmodified basis, with Modifications, or 113 | as part of a Larger Work; and 114 | 115 | (b) under Patent Claims of such Contributor to make, use, sell, offer 116 | for sale, have made, import, and otherwise transfer either its 117 | Contributions or its Contributor Version. 118 | 119 | 2.2. Effective Date 120 | 121 | The licenses granted in Section 2.1 with respect to any Contribution 122 | become effective for each Contribution on the date the Contributor first 123 | distributes such Contribution. 124 | 125 | 2.3. Limitations on Grant Scope 126 | 127 | The licenses granted in this Section 2 are the only rights granted under 128 | this License. No additional rights or licenses will be implied from the 129 | distribution or licensing of Covered Software under this License. 130 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 131 | Contributor: 132 | 133 | (a) for any code that a Contributor has removed from Covered Software; 134 | or 135 | 136 | (b) for infringements caused by: (i) Your and any other third party's 137 | modifications of Covered Software, or (ii) the combination of its 138 | Contributions with other software (except as part of its Contributor 139 | Version); or 140 | 141 | (c) under Patent Claims infringed by Covered Software in the absence of 142 | its Contributions. 143 | 144 | This License does not grant any rights in the trademarks, service marks, 145 | or logos of any Contributor (except as may be necessary to comply with 146 | the notice requirements in Section 3.4). 147 | 148 | 2.4. Subsequent Licenses 149 | 150 | No Contributor makes additional grants as a result of Your choice to 151 | distribute the Covered Software under a subsequent version of this 152 | License (see Section 10.2) or under the terms of a Secondary License (if 153 | permitted under the terms of Section 3.3). 154 | 155 | 2.5. Representation 156 | 157 | Each Contributor represents that the Contributor believes its 158 | Contributions are its original creation(s) or it has sufficient rights 159 | to grant the rights to its Contributions conveyed by this License. 160 | 161 | 2.6. Fair Use 162 | 163 | This License is not intended to limit any rights You have under 164 | applicable copyright doctrines of fair use, fair dealing, or other 165 | equivalents. 166 | 167 | 2.7. Conditions 168 | 169 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 170 | in Section 2.1. 171 | 172 | 3. Responsibilities 173 | ------------------- 174 | 175 | 3.1. Distribution of Source Form 176 | 177 | All distribution of Covered Software in Source Code Form, including any 178 | Modifications that You create or to which You contribute, must be under 179 | the terms of this License. You must inform recipients that the Source 180 | Code Form of the Covered Software is governed by the terms of this 181 | License, and how they can obtain a copy of this License. You may not 182 | attempt to alter or restrict the recipients' rights in the Source Code 183 | Form. 184 | 185 | 3.2. Distribution of Executable Form 186 | 187 | If You distribute Covered Software in Executable Form then: 188 | 189 | (a) such Covered Software must also be made available in Source Code 190 | Form, as described in Section 3.1, and You must inform recipients of 191 | the Executable Form how they can obtain a copy of such Source Code 192 | Form by reasonable means in a timely manner, at a charge no more 193 | than the cost of distribution to the recipient; and 194 | 195 | (b) You may distribute such Executable Form under the terms of this 196 | License, or sublicense it under different terms, provided that the 197 | license for the Executable Form does not attempt to limit or alter 198 | the recipients' rights in the Source Code Form under this License. 199 | 200 | 3.3. Distribution of a Larger Work 201 | 202 | You may create and distribute a Larger Work under terms of Your choice, 203 | provided that You also comply with the requirements of this License for 204 | the Covered Software. If the Larger Work is a combination of Covered 205 | Software with a work governed by one or more Secondary Licenses, and the 206 | Covered Software is not Incompatible With Secondary Licenses, this 207 | License permits You to additionally distribute such Covered Software 208 | under the terms of such Secondary License(s), so that the recipient of 209 | the Larger Work may, at their option, further distribute the Covered 210 | Software under the terms of either this License or such Secondary 211 | License(s). 212 | 213 | 3.4. Notices 214 | 215 | You may not remove or alter the substance of any license notices 216 | (including copyright notices, patent notices, disclaimers of warranty, 217 | or limitations of liability) contained within the Source Code Form of 218 | the Covered Software, except that You may alter any license notices to 219 | the extent required to remedy known factual inaccuracies. 220 | 221 | 3.5. Application of Additional Terms 222 | 223 | You may choose to offer, and to charge a fee for, warranty, support, 224 | indemnity or liability obligations to one or more recipients of Covered 225 | Software. However, You may do so only on Your own behalf, and not on 226 | behalf of any Contributor. You must make it absolutely clear that any 227 | such warranty, support, indemnity, or liability obligation is offered by 228 | You alone, and You hereby agree to indemnify every Contributor for any 229 | liability incurred by such Contributor as a result of warranty, support, 230 | indemnity or liability terms You offer. You may include additional 231 | disclaimers of warranty and limitations of liability specific to any 232 | jurisdiction. 233 | 234 | 4. Inability to Comply Due to Statute or Regulation 235 | --------------------------------------------------- 236 | 237 | If it is impossible for You to comply with any of the terms of this 238 | License with respect to some or all of the Covered Software due to 239 | statute, judicial order, or regulation then You must: (a) comply with 240 | the terms of this License to the maximum extent possible; and (b) 241 | describe the limitations and the code they affect. Such description must 242 | be placed in a text file included with all distributions of the Covered 243 | Software under this License. Except to the extent prohibited by statute 244 | or regulation, such description must be sufficiently detailed for a 245 | recipient of ordinary skill to be able to understand it. 246 | 247 | 5. Termination 248 | -------------- 249 | 250 | 5.1. The rights granted under this License will terminate automatically 251 | if You fail to comply with any of its terms. However, if You become 252 | compliant, then the rights granted under this License from a particular 253 | Contributor are reinstated (a) provisionally, unless and until such 254 | Contributor explicitly and finally terminates Your grants, and (b) on an 255 | ongoing basis, if such Contributor fails to notify You of the 256 | non-compliance by some reasonable means prior to 60 days after You have 257 | come back into compliance. Moreover, Your grants from a particular 258 | Contributor are reinstated on an ongoing basis if such Contributor 259 | notifies You of the non-compliance by some reasonable means, this is the 260 | first time You have received notice of non-compliance with this License 261 | from such Contributor, and You become compliant prior to 30 days after 262 | Your receipt of the notice. 263 | 264 | 5.2. If You initiate litigation against any entity by asserting a patent 265 | infringement claim (excluding declaratory judgment actions, 266 | counter-claims, and cross-claims) alleging that a Contributor Version 267 | directly or indirectly infringes any patent, then the rights granted to 268 | You by any and all Contributors for the Covered Software under Section 269 | 2.1 of this License shall terminate. 270 | 271 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 272 | end user license agreements (excluding distributors and resellers) which 273 | have been validly granted by You or Your distributors under this License 274 | prior to termination shall survive termination. 275 | 276 | ************************************************************************ 277 | * * 278 | * 6. Disclaimer of Warranty * 279 | * ------------------------- * 280 | * * 281 | * Covered Software is provided under this License on an "as is" * 282 | * basis, without warranty of any kind, either expressed, implied, or * 283 | * statutory, including, without limitation, warranties that the * 284 | * Covered Software is free of defects, merchantable, fit for a * 285 | * particular purpose or non-infringing. The entire risk as to the * 286 | * quality and performance of the Covered Software is with You. * 287 | * Should any Covered Software prove defective in any respect, You * 288 | * (not any Contributor) assume the cost of any necessary servicing, * 289 | * repair, or correction. This disclaimer of warranty constitutes an * 290 | * essential part of this License. No use of any Covered Software is * 291 | * authorized under this License except under this disclaimer. * 292 | * * 293 | ************************************************************************ 294 | 295 | ************************************************************************ 296 | * * 297 | * 7. Limitation of Liability * 298 | * -------------------------- * 299 | * * 300 | * Under no circumstances and under no legal theory, whether tort * 301 | * (including negligence), contract, or otherwise, shall any * 302 | * Contributor, or anyone who distributes Covered Software as * 303 | * permitted above, be liable to You for any direct, indirect, * 304 | * special, incidental, or consequential damages of any character * 305 | * including, without limitation, damages for lost profits, loss of * 306 | * goodwill, work stoppage, computer failure or malfunction, or any * 307 | * and all other commercial damages or losses, even if such party * 308 | * shall have been informed of the possibility of such damages. This * 309 | * limitation of liability shall not apply to liability for death or * 310 | * personal injury resulting from such party's negligence to the * 311 | * extent applicable law prohibits such limitation. Some * 312 | * jurisdictions do not allow the exclusion or limitation of * 313 | * incidental or consequential damages, so this exclusion and * 314 | * limitation may not apply to You. * 315 | * * 316 | ************************************************************************ 317 | 318 | 8. Litigation 319 | ------------- 320 | 321 | Any litigation relating to this License may be brought only in the 322 | courts of a jurisdiction where the defendant maintains its principal 323 | place of business and such litigation shall be governed by laws of that 324 | jurisdiction, without reference to its conflict-of-law provisions. 325 | Nothing in this Section shall prevent a party's ability to bring 326 | cross-claims or counter-claims. 327 | 328 | 9. Miscellaneous 329 | ---------------- 330 | 331 | This License represents the complete agreement concerning the subject 332 | matter hereof. If any provision of this License is held to be 333 | unenforceable, such provision shall be reformed only to the extent 334 | necessary to make it enforceable. Any law or regulation which provides 335 | that the language of a contract shall be construed against the drafter 336 | shall not be used to construe this License against a Contributor. 337 | 338 | 10. Versions of the License 339 | --------------------------- 340 | 341 | 10.1. New Versions 342 | 343 | Mozilla Foundation is the license steward. Except as provided in Section 344 | 10.3, no one other than the license steward has the right to modify or 345 | publish new versions of this License. Each version will be given a 346 | distinguishing version number. 347 | 348 | 10.2. Effect of New Versions 349 | 350 | You may distribute the Covered Software under the terms of the version 351 | of the License under which You originally received the Covered Software, 352 | or under the terms of any subsequent version published by the license 353 | steward. 354 | 355 | 10.3. Modified Versions 356 | 357 | If you create software not governed by this License, and you want to 358 | create a new license for such software, you may create and use a 359 | modified version of this License if you rename the license and remove 360 | any references to the name of the license steward (except to note that 361 | such modified license differs from this License). 362 | 363 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 364 | Licenses 365 | 366 | If You choose to distribute Source Code Form that is Incompatible With 367 | Secondary Licenses under the terms of this version of the License, the 368 | notice described in Exhibit B of this License must be attached. 369 | 370 | Exhibit A - Source Code Form License Notice 371 | ------------------------------------------- 372 | 373 | This Source Code Form is subject to the terms of the Mozilla Public 374 | License, v. 2.0. If a copy of the MPL was not distributed with this 375 | file, You can obtain one at https://mozilla.org/MPL/2.0/. 376 | 377 | If it is not possible or desirable to put the notice in a particular 378 | file, then You may include the notice in a location (such as a LICENSE 379 | file in a relevant directory) where a recipient would be likely to look 380 | for such a notice. 381 | 382 | You may add additional accurate notices of copyright ownership. 383 | 384 | Exhibit B - "Incompatible With Secondary Licenses" Notice 385 | --------------------------------------------------------- 386 | 387 | This Source Code Form is "Incompatible With Secondary Licenses", as 388 | defined by the Mozilla Public License, v. 2.0. -------------------------------------------------------------------------------- /modules/billtechconfig.php: -------------------------------------------------------------------------------- 1 | $isp_id, 23 | 'apiKey' => $api_key, 24 | 'pin' => $pin 25 | )); 26 | 27 | $curl = curl_init($api_url . '/api/service-provider/generate-key'); 28 | curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST"); 29 | curl_setopt($curl, CURLOPT_POSTFIELDS, $data); 30 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 31 | curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json')); 32 | $result = curl_exec($curl); 33 | 34 | $api_secret = json_decode($result)->data->apiSecret; 35 | 36 | if ($api_secret) { 37 | $DB->Execute("UPDATE uiconfig SET value = ? WHERE section = 'billtech' AND var = 'api_secret'", array($api_secret)); 38 | } 39 | } 40 | 41 | case 'setbilltechconfig': 42 | $isp_id = $_POST['isp_id']; 43 | $payment_url = $_POST['payment_url']; 44 | $api_url = $_POST['api_url']; 45 | $api_key = $_POST['api_key']; 46 | $DB->Execute("UPDATE uiconfig SET value = ? WHERE section = 'billtech' AND var = 'isp_id'", array($isp_id)); 47 | $DB->Execute("UPDATE uiconfig SET value = ? WHERE section = 'billtech' AND var = 'payment_url'", array($payment_url)); 48 | $DB->Execute("UPDATE uiconfig SET value = ? WHERE section = 'billtech' AND var = 'api_url'", array($api_url)); 49 | $DB->Execute("UPDATE uiconfig SET value = ? WHERE section = 'billtech' AND var = 'api_key'", array($api_key)); 50 | $SESSION->redirect('?m=billtechconfig'); 51 | } 52 | 53 | $SMARTY->assign('isp_id', $isp_id); 54 | $SMARTY->assign('payment_url', $payment_url); 55 | $SMARTY->assign('api_url', $api_url); 56 | $SMARTY->assign('api_key', $api_key); 57 | $SMARTY->assign('api_secret', $api_secret); 58 | $SMARTY->display('billtechconfig.html'); 59 | -------------------------------------------------------------------------------- /modules/billtechpaymentconfirmed.php: -------------------------------------------------------------------------------- 1 | restore('bplm', $bplm); 4 | $SESSION->remove('bplm'); 5 | 6 | if (is_array($_POST['marks']) && sizeof($_POST['marks'])) 7 | foreach ($_POST['marks'] as $id => $mark) 8 | $bplm[$id] = $mark; 9 | 10 | if (is_array($bplm) && sizeof($bplm)) 11 | foreach ($bplm as $mark) 12 | $ids[] = $mark; 13 | 14 | if (is_array($ids) && sizeof($ids)) { 15 | $DB->BeginTrans(); 16 | 17 | $payments = $DB->GetAll("SELECT id, customerid, amount, cdate, title, closed, cashid FROM billtech_payments WHERE id IN (" . implode(',', $ids) . ")"); 18 | 19 | foreach ($payments as $payment) { 20 | if ($payment['closed']) { 21 | $addbalance = array( 22 | 'value' => $payment['amount'], 23 | 'type' => 100, 24 | 'userid' => Auth::GetCurrentUser(), 25 | 'customerid' => $payment['customerid'], 26 | 'comment' => BillTech::CASH_COMMENT.' za: '.$payment['title'], 27 | 'time' => $payment['cdate'] 28 | ); 29 | 30 | $cashid = BillTechPaymentsUpdater::addBalanceReturnCashIdOrFalse($addbalance); 31 | if ($cashid) { 32 | $DB->Execute("UPDATE billtech_payments SET closed = 0, cashid = ? WHERE id = ?", array($cashid, $payment['id'])); 33 | } 34 | } else { 35 | $cash = $LMS->GetCashByID($payment['cashid']); 36 | if ($cash && strpos($cash['comment'], BillTech::CASH_COMMENT) !== false) { 37 | $DB->Execute("UPDATE billtech_payments SET closed = 1, cashid = NULL WHERE id = ?", array($payment['id'])); 38 | $LMS->DelBalance($payment['cashid']); 39 | } 40 | } 41 | } 42 | 43 | if (is_array($DB->GetErrors()) && sizeof($DB->GetErrors())) { 44 | $DB->RollbackTrans(); 45 | throw new Exception("Error writing to database"); 46 | } else { 47 | $DB->CommitTrans(); 48 | } 49 | } 50 | 51 | $SESSION->redirect('?' . $SESSION->get('backto')); 52 | -------------------------------------------------------------------------------- /modules/billtechpaymentlist.php: -------------------------------------------------------------------------------- 1 | Escape('%' . $search . '%'); 41 | break; 42 | case 'cdate': 43 | $where = ' AND p.cdate >= ' . intval($search) . ' AND cdate < ' . (intval($search) + 86400); 44 | break; 45 | case 'month': 46 | $last = mktime(23, 59, 59, date('n', $search) + 1, 0, date('Y', $search)); 47 | $where = ' AND p.cdate >= ' . intval($search) . ' AND cdate <= ' . $last; 48 | break; 49 | case 'customerid': 50 | $where = ' AND p.customerid = ' . intval($search); 51 | break; 52 | case 'name': 53 | $where = ' AND UPPER(name) ?LIKE? UPPER(' . $DB->Escape('%' . $search . '%') . ')'; 54 | break; 55 | case 'amount': 56 | $where = ' AND p.amount = ' . str_replace(',', '.', f_round($search)); 57 | break; 58 | } 59 | } 60 | 61 | if ($hideclosed) 62 | $where .= ' AND closed = 0'; 63 | 64 | if ($res = $DB->Exec("SELECT p.id, p.customerid as customerid, p.amount, p.title, p.document_number, p.reference_number, p.cdate, p.closed, 65 | " . $DB->Concat('c.lastname', "' '", 'c.name') . " as name 66 | FROM billtech_payments p LEFT JOIN customers c ON c.id = p.customerid 67 | LEFT JOIN ( 68 | SELECT DISTINCT a.customerid FROM customerassignments a 69 | JOIN excludedgroups e ON (a.customergroupid = e.customergroupid) 70 | WHERE e.userid = lms_current_user() 71 | ) e ON (e.customerid = p.customerid) 72 | WHERE e.customerid IS NULL " 73 | . $where . $sqlord . " " . $direction)) { 74 | if ($page > 0) { 75 | $start = ($page - 1) * $pagelimit; 76 | $stop = $start + $pagelimit; 77 | } 78 | $id = 0; 79 | 80 | while ($row = $DB->FetchRow($res)) { 81 | $row['customlinks'] = array(); 82 | $row['expired'] = !$row['closed'] && isExpired($row['cdate']); 83 | $result[$id] = $row; 84 | // free memory for rows which will not be displayed 85 | if ($page > 0) { 86 | if (($id < $start || $id > $stop) && isset($result[$id])) 87 | $result[$id] = NULL; 88 | } elseif (isset($result[$id - $pagelimit])) 89 | $result[$id - $pagelimit] = NULL; 90 | 91 | $id++; 92 | } 93 | 94 | $result['page'] = $page > 0 ? $page : ceil($id / $pagelimit); 95 | } 96 | 97 | $result['order'] = $order; 98 | $result['direction'] = $direction; 99 | 100 | return $result; 101 | } 102 | 103 | function isExpired($date) 104 | { 105 | return $date < time() - ConfigHelper::getConfig('billtech.payment_expiration_warning', 7) * 86400; 106 | } 107 | 108 | global $SESSION, $LMS, $SMARTY; 109 | 110 | $SESSION->save('backto', $_SERVER['QUERY_STRING']); 111 | 112 | $SESSION->restore('bplm', $marks); 113 | if (isset($_POST['marks'])) 114 | foreach ($_POST['marks'] as $id => $mark) 115 | $marks[$id] = $mark; 116 | $SESSION->save('bplm', $marks); 117 | 118 | if (isset($_POST['search'])) 119 | $s = $_POST['search']; 120 | else 121 | $SESSION->restore('bpls', $s); 122 | if (!isset($s)) { 123 | $year = date("Y", time()); 124 | $month = date("m", time()); 125 | $s = $year . '/' . $month; 126 | } 127 | $SESSION->save('bpls', $s); 128 | 129 | if (isset($_GET['o'])) 130 | $o = $_GET['o']; 131 | else 132 | $SESSION->restore('bplo', $o); 133 | $SESSION->save('bplo', $o); 134 | 135 | if (isset($_POST['cat'])) 136 | $c = $_POST['cat']; 137 | else 138 | $SESSION->restore('bplc', $c); 139 | if (!isset($c)) { 140 | $c = "month"; 141 | } 142 | $SESSION->save('bplc', $c); 143 | 144 | if (isset($_POST['search'])) 145 | $h = isset($_POST['hideclosed']); 146 | elseif (($h = $SESSION->get('bplh')) === NULL) 147 | $h = ConfigHelper::checkConfig('billtech.hide_closed_payments'); 148 | $SESSION->save('bplh', $h); 149 | 150 | if ($c == 'cdate' && $s && preg_match('/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}$/', $s)) { 151 | list($year, $month, $day) = explode('/', $s); 152 | $s = mktime(0, 0, 0, $month, $day, $year); 153 | } elseif ($c == 'month' && $s && preg_match('/^[0-9]{4}\/[0-9]{2}$/', $s)) { 154 | list($year, $month) = explode('/', $s); 155 | $s = mktime(0, 0, 0, $month, 1, $year); 156 | } 157 | 158 | $pagelimit = ConfigHelper::getConfig('phpui.billtechpaymentlist_pagelimit', 100); 159 | $page = !isset($_GET['page']) ? 0 : intval($_GET['page']); 160 | 161 | $paymentlist = GetBillTtechPaymentsList($o, $s, $c, $h, $pagelimit, $page); 162 | 163 | $SESSION->restore('bplc', $listdata['cat']); 164 | $SESSION->restore('bpls', $listdata['search']); 165 | $SESSION->restore('bplh', $listdata['hideclosed']); 166 | 167 | 168 | $listdata['order'] = $paymentlist['order']; 169 | $listdata['direction'] = $paymentlist['direction']; 170 | $page = $paymentlist['page']; 171 | 172 | unset($paymentlist['page']); 173 | unset($paymentlist['order']); 174 | unset($paymentlist['direction']); 175 | 176 | $listdata['total'] = is_array($paymentlist) ? sizeof($paymentlist) : 0; 177 | 178 | $hook_data = $LMS->ExecuteHook('billtechpaymentlist_before_display', 179 | array( 180 | 'paymentlist' => $paymentlist, 181 | ) 182 | ); 183 | $paymentlist = $hook_data['paymentlist']; 184 | 185 | $SMARTY->assign('listdata', $listdata); 186 | $SMARTY->assign('pagelimit', $pagelimit); 187 | $SMARTY->assign('start', ($page - 1) * $pagelimit); 188 | $SMARTY->assign('page', $page); 189 | $SMARTY->assign('marks', $marks); 190 | $SMARTY->assign('paymentlist', $paymentlist); 191 | $SMARTY->display('billtechpaymentlist.html'); 192 | -------------------------------------------------------------------------------- /templates/billtechconfig.html: -------------------------------------------------------------------------------- 1 | {extends file="layout.html"} 2 | {block name=title}::: LMS :{$layout.pagetitle|striphtml} :::{/block} 3 | {block name=module_content} 4 | 15 | 16 |

{$layout.pagetitle}

17 |
18 |

19 | 20 |

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 38 | 41 | 42 | 43 | 46 | 49 | 50 | 51 | 54 | 57 | 58 | 59 | 62 | 65 | 66 | 67 | 70 | 73 | 74 | 75 | 78 | 81 | 82 | 83 | 86 | 87 | 88 |
29 | BillTech - konfiguracja 30 |
36 | ISP id: 37 | 39 | 40 |
44 | Payment url: 45 | 47 | 48 |
52 | API url: 53 | 55 | 56 |
60 | API key: 61 | 63 | 64 |
68 | API secret: 69 | 71 | 72 |
76 | PIN: 77 | 79 |  Generuj API secret »»» 80 |
84 | {trans("Submit")} 85 |
89 |
90 | {/block} 91 | -------------------------------------------------------------------------------- /templates/billtechpaymentlist.html: -------------------------------------------------------------------------------- 1 | {extends file="layout.html"} 2 | {block name=title}::: LMS :{$layout.pagetitle|striphtml} :::{/block} 3 | {block name=module_content} 4 | 5 |

{$layout.pagetitle}

6 | 40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 53 | 56 | 59 | 62 | 65 | 68 | 71 | 72 | 73 | 88 | 89 | {if $listdata.total != 0} 90 | 91 | 94 | 95 | {/if} 96 | 97 | 98 | {cycle values="light,lucid" print=false} 99 | {section name=paymentlist loop=$paymentlist start=$start max=$pagelimit} 100 | {assign var=payment value=$paymentlist[paymentlist]} 101 | {assign var=paymentid value=$payment.id} 102 | 104 | 107 | 110 | 113 | 116 | 119 | 122 | 125 | 126 | {sectionelse} 127 | 128 | 131 | 132 | {/section} 133 | 134 | 135 | {if $listdata.total != 0} 136 | 137 | 140 | 141 | {/if} 142 | 143 | 158 | 159 | 160 |
51 | ID: {if $listdata.order == "id"}{/if} 52 | 54 | Data: {if $listdata.order == "cdate"}{/if} 55 | 57 | Numer referencyjny: {if $listdata.order == "cdate"}{/if} 58 | 60 | Tytułem: {if $listdata.order == "value"}{/if} 61 | 63 | Wartość: {if $listdata.order == "value"}{/if} 64 | 66 | Od: {if $listdata.order == "name"}{/if} 67 | 69 |   70 |
74 | {trans("Filter:")} 75 |   76 |   85 | 86 |  »»» 87 |
92 | {include file="scroller.html" loop=$paymentlist scrollerno=1} 93 |
105 | {$payment.id|string_format:"%06d"} 106 | 108 | {$payment.cdate|date_format:"%Y/%m/%d %H:%M"} 109 | 111 | {$payment.reference_number} 112 | 114 | {$payment.title} 115 | 117 | {$payment.amount|money_format} 118 | 120 | {$payment.name} 121 | 123 | 124 |
129 |

Brak płatności BillTech w bazie danych

130 |
138 | {include file="scroller.html" loop=$paymentlist scrollerno=2} 139 |
144 | 145 | 146 | 149 | 155 | 156 |
147 | {trans("Check/Uncheck as accounted")}   148 | 150 | 154 |
157 |
161 |
162 | {/block} 163 | -------------------------------------------------------------------------------- /templates/button/bclean/billtechbalancebutton.html: -------------------------------------------------------------------------------- 1 | 24 | 25 | Opłać saldo -------------------------------------------------------------------------------- /templates/button/bclean/billtechrowbutton.html: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /templates/button/billtechemailbutton.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 |
4 | 19 |
22 | -------------------------------------------------------------------------------- /templates/button/default/billtechbalancebutton.html: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /templates/button/default/billtechrowbutton.html: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /templates/button/default/customerbilltechbutton.html: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /templates/button/default/customerotheripbilltechbutton.html: -------------------------------------------------------------------------------- 1 | 26 | --------------------------------------------------------------------------------