licenses->mintForOrder( $order ); } catch ( Throwable $failure ) { $this->scheduleRetry( $order, 1, $failure ); } } /** * Retry a failed mint for a completed order. * * @since 1.0.0 * * @param int $orderId The order whose mint failed. * @param int $attempt Which attempt this is (1-based). * @return void */ public function onMintRetry( int $orderId, int $attempt ): void { $order = $this->orders->get( $orderId ); if ( null === $order || ! $order->isCompleted() ) { return; } try { $this->licenses->mintForOrder( $order ); } catch ( Throwable $failure ) { if ( $attempt >= self::MAX_ATTEMPTS ) { $this->logger->log( 'error', sprintf( 'License minting for order %d gave up after %d attempts: %s', $orderId, $attempt, $failure->getMessage() ), array( 'order_id' => $orderId ), 'order', $orderId ); return; } $this->scheduleRetry( $order, $attempt + 1, $failure ); } } /** * Apply the refund license policy without stopping the refund request. * * @since 1.0.0 * * @param Order $order The refunded order (already advanced in status). * @param int $amountRefunded The amount refunded, minor units. * @return void */ public function onOrderRefunded( Order $order, int $amountRefunded ): void { try { $this->licenses->onOrderRefunded( $order, $amountRefunded ); } catch ( Throwable $failure ) { $this->logger->log( 'error', sprintf( 'Refund license policy failed for order %d: %s', (int) $order->id(), $failure->getMessage() ), array( 'order_id' => (int) $order->id() ), 'order', (int) $order->id() ); } } /** * Run the daily expiry sweep without stopping the job queue. * * @since 1.0.0 * * @return void */ public function onExpiryJob(): void { try { $this->licenses->expireDueLicenses(); } catch ( Throwable $failure ) { $this->logger->log( 'error', sprintf( 'License expiry sweep failed: %s', $failure->getMessage() ) ); } } /** * Schedule the daily expiry sweep once. * * @since 1.0.0 * * @return void */ public function scheduleJobs(): void { // Remove the legacy WP-Cron event to prevent duplicate sweeps. if ( false !== wp_next_scheduled( self::EXPIRY_HOOK ) ) { wp_clear_scheduled_hook( self::EXPIRY_HOOK ); } if ( false === as_next_scheduled_action( self::EXPIRY_HOOK, array(), 'releasecart' ) ) { // Enforce a single recurring action during concurrent requests. as_schedule_recurring_action( time(), 86400, self::EXPIRY_HOOK, array(), 'releasecart', true ); } } /** * Log a mint failure and schedule its backed-off retry. * * @since 1.0.0 * * @param Order $order The order whose mint failed. * @param int $attempt The attempt number the retry will carry. * @param Throwable $failure What went wrong. * @return void */ private function scheduleRetry( Order $order, int $attempt, Throwable $failure ): void { $orderId = (int) $order->id(); $this->logger->log( 'error', sprintf( 'License minting failed for order %d (retry %d scheduled): %s', $orderId, $attempt, $failure->getMessage() ), array( 'order_id' => $orderId, 'attempt' => $attempt, ), 'order', $orderId ); // Back off retries to avoid repeated database failures. as_schedule_single_action( time() + ( self::RETRY_BASE_SECONDS * ( 2 ** $attempt ) ), self::RETRY_HOOK, array( $orderId, $attempt ), 'releasecart' ); } }