From 8d3fbcb2ab2cb7ec6a1b1a364310ce83a3a174ba Mon Sep 17 00:00:00 2001 From: johnnyq Date: Tue, 20 Oct 2026 16:17:32 -0400 Subject: [PATCH] Payment Providers stage 3: the providers list shows card payments in the last 30 days with their total and the last payment date, and fees recorded in 30 days with a count of payments whose fee is still to record, flagged once more than two days old; adds Check connection, which reports the Stripe account the saved key belongs to or why it is not connected, and Record fees now, which runs the nightly fee reconciliation on demand through the same shared function the nightly job now uses; Settings > Invoices and billing shows how card payments stand under Paid online, and the Settings overview flags test keys, card payments going into an archived account, and Stripe fees not being recorded, with the Invoices and billing tile showing whether cards are on; overview warnings keep their button beside the text where there is room --- CHANGELOG.md | 3 + admin/payment_providers.php | 37 +++++-- admin/post/payment_provider.php | 62 +++++++++++ admin/settings.php | 11 +- admin/settings_invoice.php | 3 + cron/nightly_tasks.php | 75 ++----------- css/itflow_custom.css | 5 + functions/payment_provider.php | 180 ++++++++++++++++++++++++++++++++ 8 files changed, 302 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d76383ce..6cce6dafb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1044,6 +1044,8 @@ If you are coming from 26.08 or earlier, the database update still has to be run - The audit log says what changed, from what to what - "Card limit No limit → Under $2,500.00", "Mode test → live" - with the keys only as changed. - Saved Cards: each card a client has saved, as Visa •••• 4242 with when it runs out, whose it is (to their client page), and the recurring invoices that autopay with it and when each next does. Tabs for the cards on autopay and the ones that have expired - an expired card on autopay says its next autopay will be declined, and the Expired tab turns red while there is one. Filter by client, or by provider from the Payment Providers list; search by the provider's customer or payment method ID; sort by when they run out. - Removing a card says first which recurring invoices stop autopaying with it. It is taken off Stripe as well, each of those recurring invoices says in its history that its autopay stopped and why, and when Stripe can't be reached the card is still removed and you are told it is saved there. + - What goes through it: each provider shows its card payments in the last 30 days and when the last came, what its fees came to, and how many are still to record - flagged once they are more than two days old, as the nightly run should have had them. Record fees now does what the nightly run does, and says what it recorded, what Stripe hasn't settled yet and what it wouldn't say. Check connection asks Stripe whose account the key is, and says so - or why it isn't connected. + - Settings > Invoices and billing says how card payments stand, under Paid online: none set up, turned off, or on - with live or test keys, where payments go, the card limit and how many came in the last 30 days. The Settings overview flags test keys, card payments going into an archived account, and Stripe's fees not being recorded, and the Invoices and billing tile says whether cards are on. ### Bug Fixes @@ -1771,6 +1773,7 @@ If you are coming from 26.08 or earlier, the database update still has to be run - Settings: Notifications, Security, Client portal, Single sign-on (`admin/identity_providers.php`), Mail and Logs use the frame. `settingsSearch($query, $limit)` scores settings as Find a setting does, leaving out pages for a module that is off; `agent/ajax.php?global_search` returns them as `settings` for admins and `js/global_search.js` shows them under Settings. Single sign-on's fields are renamed to their columns (`config_azure_client_id`, `config_azure_client_secret`). A field marked `quiet` in `settingsFields()` is logged as changed without its value (the login key). The Logs handler is unchanged. - Settings: Mail's Sending, Receiving, OAuth and From addresses tabs use the frame, and every Mail column is in `settingsFields()`. Its handlers save through `settingsSave()` and `settingsWrite()`, logging from -> to; `edit_mail_smtp_settings` and `edit_mail_imap_settings` share one check, and both come back to their tab when refused. A password, secret or refresh token posted empty keeps what is saved; an empty OAuth client ID forgets its secret and refresh token; a changed app or refresh token clears `config_mail_oauth_access_token` and its expiry, so it is fetched again. The access token is no longer a field. `oauth_connect_microsoft_mail` saves the same way before going to Microsoft, and `admin/oauth_microsoft_mail_callback.php` now stores only the tokens. New in `functions/settings.php`: `settingsMailProviders()`, `settingsMailEncryption()` and `settingsEnterSaves($button)` - a hidden Save placed first in a form whose first button would otherwise be another (Mail's OAuth tab, Logs). `settingsRefuse()` takes the page to go back to; `settingsInput()`, `settingsSelect()` and `settingsTextarea()` take `id`; `settingsWrite()` writes a `secret` field exactly as given, rather than through `escapeSql()`, which trims and strips tags. - Payment Providers: new `functions/payment_provider.php` - `paymentProviderKinds()` (the providers ITFlow takes payments through and what their keys look like), `paymentProviderMode()`, `paymentProviderRow()`, `paymentProviderTies()` and `paymentProviderTiesText()`, `paymentProviderLimitText()`, `paymentProviderCheck($post, $before)`, `paymentProviderKeysChanged()`, `paymentProviderAsk($name, $secret_key)` (Stripe's account, from the secret key), `paymentProviderChangeLines()` and `paymentProviderWrite()`, which saves the keys exactly as given. `check_payment_provider` answers in JSON - `ok`, `errors` by field and the Stripe `account` - and the window asks it before saving; a key it has had taken isn't asked about again on the save, for ten minutes. New handlers `turn_payment_provider_off` and `turn_payment_provider_on` (GET with the CSRF token). `payment_provider_add.php` opens `payment_provider_edit.php` with no id. Field `record_fees` replaces `enable_expense`; `active` and `description` are new. `admin/post/payment_provider_model.php` is gone. +- Payment Providers: the nightly Stripe fee reconciliation is `paymentProviderRecordFees($provider)` now, which Record fees now (`record_payment_provider_fees`, GET with the CSRF token) runs too - recorded, waiting and failed counts, and the fees by currency. Also `paymentProviderUnrecordedSql()`, `paymentProviderActivity($provider)` (card payments and fees in 30 days, and the payments still to record), `paymentProviderMoneyText()`, `paymentProviderProblems()` (the Settings overview's card payment items) and `paymentProviderSummaryHtml()` (Paid online). `check_payment_provider_connection` (GET with the CSRF token) runs `paymentProviderAsk()` on the saved key. The overview's items take `.itflow-settings-attention-text`, so the button stays beside the text where there is room. - Saved Cards (`admin/saved_payment_methods.php`): `paymentCardExpirySql()` (a saved card's expiry as yyyymm, from its description), `paymentCardAutopay($saved_ids)` and `paymentCardRemove($saved_payment_id, $by)`, which detaches it at Stripe (a card Stripe no longer has counts as done), writes `history` for each recurring invoice that loses its autopay, and deletes it - the autopay cascades. Filters `provider`, `client` and `show` (`autopay`, `expired`). The audit log entry is type Payment Provider, with the client. - Removed, as nothing reached them or they couldn't run: `admin/settings_custom_fields.php` (it required a modal that no longer existed) with `admin/post/custom_field.php`, `admin/post/custom_field_model.php` and `admin/modals/custom_field/`; `admin/post/settings_ai.php` and `admin/post/settings_online_payment.php`, which wrote the `config_ai_*` and `config_stripe_*` columns dropped in 2.3.2; `admin/post/settings_online_payment_clients.php`, and `agent/client_autopay.php`, which read the dropped Stripe columns and was linked from nowhere. The `custom_fields` and `custom_values` tables stay as they are, and the client export still includes asset custom field values. - Mail Queue: new `functions/mail_queue.php` - `MAIL_QUEUE_TRIES` (4), `MAIL_QUEUE_RETRY_MINUTES` (30) and `MAIL_QUEUE_KEEP_DAYS` (90), which `cron/mail_queue.php`'s retry pass and the nightly clean-up now use too; `mailQueueTabs()`, `mailQueueState($row)`, `mailQueueCan($row, $action)`, `mailQueueAct($action, $ids)` and `mailQueueHealth()`. `email_status` 4 is Cancelled - never selected by the worker. Retry now sets status 0, no attempts and no failure time. Handlers: `retry_mail`, `cancel_mail`, `delete_mail` (GET with the CSRF token) and `bulk_retry_emails`, `bulk_cancel_emails`, `bulk_delete_emails`; `send_failed_mail` still works. The worker sets `email_failed_at` on the bad sender, bad recipient and no-MX paths. diff --git a/admin/payment_providers.php b/admin/payment_providers.php index 4b16cd598..226dd7b18 100644 --- a/admin/payment_providers.php +++ b/admin/payment_providers.php @@ -4,10 +4,13 @@ * Admin > Payment Providers - the services clients pay invoices by card * through (functions/payment_provider.php). Each row says whether it is on, * live or test (as its keys say), the account its payments go into, its card - * limit, whether its fees are recorded as expenses, its saved cards and the - * autopay on them, and when a card payment last came through it. Add and - * Edit are one window (modals/payment_provider/payment_provider_edit.php); - * turning one off, on and deleting it are admin/post/payment_provider.php. + * limit, whether its fees are recorded as expenses (and what they came to, + * and how many are still to record), its saved cards and the autopay on + * them, and its card payments in the last 30 days. Check connection asks + * the provider whose account the key is; Record fees now does what the + * nightly run does. Add and Edit are one window + * (modals/payment_provider/payment_provider_edit.php); the rest are + * admin/post/payment_provider.php. */ // Default Column Sortby Filter @@ -94,7 +97,7 @@ $csrf = $_SESSION['csrf_token']; Fees - + @@ -110,6 +113,7 @@ $csrf = $_SESSION['csrf_token']; $autopay = intval($row['autopay']); $fees_on = intval($row['payment_provider_expense_vendor']) > 0 && intval($row['payment_provider_expense_category']) > 0; $ties = paymentProviderTiesText(paymentProviderTies($provider_id)); + $activity = paymentProviderActivity($row); ?> @@ -146,6 +150,13 @@ $csrf = $_SESSION['csrf_token'];
Recorded
+ +
in 30 days
+ + + to record + Not recorded @@ -158,7 +169,13 @@ $csrf = $_SESSION['csrf_token']; None yet - > + + +
in 30 days
+
+ +
>
+