Payment Synchronization
A set of scheduled jobs keep gateway transaction state aligned with Farfalla's orders and payments tables. They cover gateway-initiated changes that did not reach the platform through an Instant Payment Notification (see IPNs): status updates, gateway keys, and missing payment rows.
Overview of Synchronization Processes
Payment Creation and Updates
- PaymentSync: Creates missing
paymentsrows for approved orders. Handles one-off, recurring, external, and total-discount strategies across Stripe, MercadoPago, Yuno, and external sources. - CheckMercadoPagoRecurringPayments: Manages MercadoPago recurring payment status and subscription states; cancels subscriptions that are not paid.
- CheckMercadoPagoOneOffPayments: Processes pending one-time MercadoPago payments. Not on its own schedule; dispatched as a side effect of
dispatch-payment-sync(see below) or via a Nova action. - CheckYunoRecurringPayments: Manages Yuno recurring subscription status; handles cancellations from the Yuno portal, card failures, and retries.
- VerifyPendingRefunds: Verifies Yuno refunds that are pending confirmation from the gateway. Stops after 12 attempts (~1 hour at 5-minute intervals).
- CheckPayUOneOffPayments: Handles pending PayU payment verification (legacy gateway).
- ProcessPendingIpns: Reprocesses IPN payloads that failed earlier processing. Critical recovery mechanism for the IPN backlog.
Execution Schedule
| Process | Schedule | Feature flag | Source of truth |
|---|---|---|---|
PaymentSync (dispatch-payment-sync) | Hourly at minute 12 | platform.feature_control.payment_sync | Gateways |
ProcessPendingIpns (process-pending-ipns) | Every 5 minutes | platform.feature_control.ipn_processing | ipn_records table |
| CheckMercadoPagoRecurringPayments | Every 5 minutes | platform.feature_control.check_mp_payments | payments table |
| CheckMercadoPagoOneOffPayments | Dispatched 5 min after each dispatch-payment-sync run; also Nova-triggerable | (inherits payment_sync via the dispatcher) | payments table |
| CheckYunoRecurringPayments | Every 5 minutes | None | payments table |
| VerifyPendingRefunds | Every 5 minutes | None | Yuno API |
| CheckPayUOneOffPayments | Hourly | None | payments table |
The asymmetry is intentional but easy to miss: PaymentSync and CheckMercadoPagoRecurringPayments are gated behind separate feature flags, while the Yuno, PayU, and pending-refund jobs always run on the schedule (other than being skipped in the local development environment).
PaymentSync
Overview
The payment processing system handles various types of payments (one-off, recurring, external, total discount) across multiple payment gateways (Stripe, MercadoPago, Yuno, External) in a multi-tenant environment. It uses a strategy pattern to process different payment types and implements job batching for efficient processing.
The live-state strategy covers MercadoPago and Yuno one-off payments. Stripe and PayU one-offs follow a different path inside the same strategy.
Core Components
PaymentSync Job
PaymentSync is the main orchestrator for payment processing, responsible for:
-
Strategy Selection: Chooses the appropriate strategy based on type (one-off, recurring, external, total discounts)
-
Order discovery: Searches for eligible orders for a specific strategy and redispatches in chunks
-
Order processing: Processes orders through the appropriate strategy to create missing payments
Payment Types
PaymentSync accepts a payment-type argument with these string values (camelCase, not kebab-case; one-off will not match):
anyoneOffrecurringexternaltotalDiscountOneOfftotalDiscountRecurring
When dispatched with any, the orchestrator redispatches itself once per non-any type.
Strategy Pattern Implementation
Base Strategy
All concrete strategies share a common base that provides:
- Logging functionality
- Order processing limits (200 orders per job)
- Activity tracking
- Error handling
Key features:
- Processes orders in chunks of 200 to prevent timeouts
- Implements logging for monitoring and debugging
- Handles both successful processing and bypassed orders
- Includes safety delays (30ms) between gateway calls
Concrete Strategies
-
One-off payment strategy
- Handles single payment orders across all gateways with valid credentials
- MercadoPago and Yuno orders: all statuses included (pending and non-pending)
- Other gateways (Stripe, PayU): non-pending orders only
- Orders must have valid payment gateway information and not have been checked recently
-
Recurring payment strategy
- Manages subscription-based payments
- Checks approved orders every 2 days
- Special handling for MercadoPago pending orders:
- Checks recently updated pending orders on every run
- Focuses on orders updated within the last 4 days
-
External payment strategy
- Processes external payment systems
- Handles orders that:
- Use the
externalgateway type - Don't have payments
- Were created or updated in the last 4 days
- Have
approvedstatus
- Use the
-
Total-discount one-off strategy
- Manages one-time total discount payments
- Processes orders where:
- Tenant has
charge_total_discount_couponenabled - Plan types are
prepaid,single, orretail - Don't have payments
- Tenant has
-
Total-discount recurring strategy
- Handles recurring total discount payments
- Processes orders:
- Without payments for the current month
- With valid recurring plans
Processing Flow details
-
Dispatch shapes:
PaymentSynccan be dispatched for all payment types (any), for a specific strategy, for a specific list of order ids under any strategy, or for total-discount processing scoped by date. -
Strategy Selection
- Handles
anyby re-dispatching itself for each strategy sequentially - Handles all other types by initiating the corresponding strategy
- Handles
-
Order Processing
- Fetches orders based on strategy criteria
- If no order ids were provided, it searches for the order ids to be processed and redispatches itself in chunks including those ids
- If order ids were provided (from a manual dispatch or from a previous job), it fetches those orders and processes them
- It always chunks orders (200 per batch) for efficient processing
- Implements safety delays between gateway calls
-
Logging and Monitoring
- Activity logs for tracking processing status
- Search them using
log_name = 'PaymentSync'
- Search them using
- Error logging for debugging
- Processing metrics (counts of processed/bypassed orders)
- Activity logs for tracking processing status
Maintenance Notes
Adding New Payment Types
Three places must be kept in sync when extending the payment-sync orchestrator:
- Add a new payment-type constant (camelCase string value) on the orchestrator job.
- Append it to the list of handled strategies so
anydispatches it. - Add a factory case for the new type in the strategy resolver so the right strategy class is instantiated.
Then create the strategy class itself, extending the shared base and implementing the order-discovery, order-processing, and payment-type hooks defined there.
Monitoring
Activity logs use log_name = 'PaymentSync'. Filter Horizon by the job names listed in the Execution Schedule table to inspect run history per job.
CheckMercadoPagoRecurringPayments
This job ensures that all payments for approved orders are present and handles situations such as manual subscription cancellations by users on the MercadoPago website or through the credit card company, as well as payment failures that MercadoPago keeps trying to collect indefinitely.
The process only covers approved UserPlans, verifying the associated payments and pausing the plan if the number of approved payments is less than the minimum expected. It constantly reviews recently updated pending orders (from the last 4 days) to approve them when they have enough payments or cancel them if they do not. This query, ordered by mp_checked_at, takes 100 orders at a time, placing pending orders at the end of the list once checked.
It prioritizes older orders because ordering by status would risk leaving pending orders at the end of the list, where they would not be processed if new approved orders accumulate. Most checks run against the internal database, with no interaction with external providers except when pausing unpaid subscriptions.
CheckMercadoPagoOneOffPayments
This job has no schedule of its own. It is dispatched as a side effect of the dispatch-payment-sync artisan command (5 minutes after the PaymentSync dispatch in the same run) and can also be triggered from a Nova action.
Running dispatch-payment-sync dispatches both PaymentSync (with payment type any) and CheckMercadoPagoOneOffPayments with a 5-minute delay. There is no separate registered command for the MercadoPago one-off check.
This job processes MercadoPago orders that are in pending status with plan_type of single or prepaid. It approves the order when the sum of successful payments in the payments table equals the amount in users_plans.
CheckYunoRecurringPayments
This job runs every 5 minutes and manages Yuno recurring subscription states. It is the direct equivalent of CheckMercadoPagoRecurringPayments for the Yuno gateway. It covers:
- Cancellations initiated from the Yuno portal
- Credit card failures and retries
- Subscription state drift between Yuno and Farfalla
VerifyPendingRefunds
This job runs every 5 minutes and polls the Yuno API for refunds that have been submitted but not yet confirmed. After 12 attempts (~1 hour at 5-minute intervals), the pending refund is marked failed and polling stops.
ProcessPendingIpns
Runs every 5 minutes and reprocesses IPN payloads that failed during their initial handling. The ipn_records table stores every inbound IPN with a processed flag; this job picks up the unprocessed backlog. It is the recovery path when an IPN handler crashes or a transient error breaks the inline processing.
CheckPayUOneOffPayments
This job handles cases where the PayU callback URL failed, leaving orders with approved payments stuck in pending status. It processes pending orders created in the last week, calls the PayU API to check payment status, and, if confirmed, approves the order and its UserPlan while updating gateway_key and gateway_meta_post.