Skip to content

Portal Payment Recovery — Crontab Reference

Status: INSTALLED via deploy.sh phase_postdeploy(). Cron entries are automatically installed when APP_ENV=production is set. See bautista-backend/etc/cron.d/bautista-portal-recovery.dist for the template.

Scripts

ScriptPurposeFrequency
cli/sweep-portal-payments.phpSweep stale issued/pending rows — poll gateway for issued rows (APPROVED → reconcile, else cancel), cancel pending rows directlyEvery 30 min
cli/retry-recibo-errors.phpRetry approved rows with recibo_error — reconcile again if sweep_retries < 3; send alert if cap reachedEvery 10 min

Cron Template (after {PROJECT_ROOT} substitution)

cron
# bautista-portal-recovery — managed by deploy.sh — do not edit manually
MAILTO=""

# Sweep stale issued/pending portal payment rows — every 30 min
*/30 * * * * www-data flock -n /var/lock/sweep-portal-payments.lock php /var/www/Bautista/bautista-backend/cli/sweep-portal-payments.php >> /var/log/bautista/sweep-portal-payments.log 2>&1

# Retry portal payments with recibo_error — every 10 min
*/10 * * * * www-data flock -n /var/lock/retry-recibo-errors.lock php /var/www/Bautista/bautista-backend/cli/retry-recibo-errors.php >> /var/log/bautista/retry-recibo-errors.log 2>&1

Cron Installation

deploy.sh phase_postdeploy() calls install_cron() automatically.

  • Production host (APP_ENV=production): installs /etc/cron.d/bautista-portal-recovery (mode 644) if writable, else falls back to idempotent www-data crontab.
  • Non-production (APP_ENV absent or any other value): install is skipped — fail-safe default.
  • Dry-run (--dry-run): prints rendered template to stdout, writes nothing.

Both install paths are idempotent: running deploy twice produces exactly one cron entry per script. A schedule change overwrites the previous entry.

Manual Migration (REQUIRED before first deploy with logging)

The cron_job_log table must exist in the ini database before the first deploy that ships CronJobLogger::summary() calls.

Run this manually before or after deploy as needed:

bash
php migrations/migrate-db-command.php --migrate

If the table does not exist, summary() INSERTs fail silently (caught and written to stderr) — the scripts continue processing tenants. The migration is NOT run by deploy.sh.

APP_ENV Requirement

Production hosts MUST set APP_ENV=production explicitly in the .env file or system environment to enable cron installation:

dotenv
APP_ENV=production

Any other value (absent, development, staging, etc.) causes the cron install to be skipped without error.

Notes

  • Both scripts iterate tenants from ini.sistema filtered by modulo_portal_clientes = 1.
  • Per-tenant isolation: a failure in one tenant's bootstrap does NOT abort the sweep for other tenants (try/catch + continue).
  • Each script logs errors to errorbri (ini DB) and summaries to cron_job_log (ini DB) via CronJobLogger. A logging failure never aborts tenant processing.
  • sweep-portal-payments.php uses a 2-hour TTL by default. Override with --ttl-hours=N.
  • Both scripts support --dry-run for safe inspection without mutations.
  • retry-recibo-errors.php excludes AMOUNT_MISMATCH: rows at SQL level AND in RetryReciboErrorsService (defence-in-depth). Those rows require human intervention.
  • Retry cap is 3 (sweep_retries in gateway_response JSONB). On cap reached, AlertService::sendAlert() fires once per cron run.
  • flock -n wraps each script invocation — concurrent runs of the same script exit immediately (non-zero) without processing tenants.

Log locations

/var/log/bautista/sweep-portal-payments.log
/var/log/bautista/retry-recibo-errors.log

Rollback

  1. Remove cron entries: delete /etc/cron.d/bautista-portal-recovery or remove matching lines from the www-data crontab.
  2. Revert commits.
  3. Verify no stale rows were left in an unexpected state — run SELECT status, count(*) FROM empresa.portal_payments GROUP BY status per tenant.
  4. The cron_job_log table is additive — dropping it is optional.