id = 'afridirect_payment'; $this->icon = ''; $this->has_fields = true; $this->method_title = __( 'AfriDirect Payment', 'afridirect-payment' ); $this->method_description = __( 'Allow customers to pay you directly and upload proof of payment.', 'afridirect-payment' ); // Load settings. $this->init_form_fields(); $this->init_settings(); // Define user set variables. $this->title = $this->get_option( 'title' ); $this->description = $this->get_option( 'description' ); $this->enabled = $this->get_option( 'enabled' ); $this->default_form_id = $this->get_option( 'default_form_id', '' ); // Actions. add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) ); add_action( 'woocommerce_thankyou_' . $this->id, array( $this, 'thankyou_page' ) ); add_action( 'woocommerce_view_order', array( $this, 'view_order_page' ), 20 ); add_action( 'woocommerce_email_before_order_table', array( $this, 'email_instructions' ), 10, 3 ); } /** * Initialize form fields. */ public function init_form_fields() { $this->form_fields = array( 'enabled' => array( 'title' => __( 'Enable/Disable', 'afridirect-payment' ), 'type' => 'checkbox', 'label' => __( 'Enable AfriDirect Payment Plugin', 'afridirect-payment' ), 'default' => 'no', ), 'title' => array( 'title' => __( 'Title', 'afridirect-payment' ), 'type' => 'text', 'description' => __( 'This controls the title which the user sees during checkout.', 'afridirect-payment' ), 'default' => __( 'AfriDirect Payment Plugin', 'afridirect-payment' ), 'desc_tip' => true, ), 'description' => array( 'title' => __( 'Description', 'afridirect-payment' ), 'type' => 'textarea', 'description' => __( 'Payment method description that the customer will see on your checkout.', 'afridirect-payment' ), 'default' => __( 'Pay directly and upload proof of payment.', 'afridirect-payment' ), 'desc_tip' => true, ), 'default_form_id' => array( 'title' => __( 'Default Payment Form', 'afridirect-payment' ), 'type' => 'select', 'description' => __( 'Select the default payment form to use at checkout.', 'afridirect-payment' ), 'default' => '', 'options' => $this->get_form_options(), 'desc_tip' => true, ), ); } /** * Build options for the "Default Payment Form" select. * * Lists the Payment Forms (afridirect_form CPT) — the same forms used by the * [afridirect_form] shortcode. * * @return array */ protected function get_form_options() { $forms = get_posts( array( 'post_type' => 'afridirect_form', 'post_status' => 'publish', 'numberposts' => 200, 'orderby' => 'title', 'order' => 'ASC', 'suppress_filters' => false, ) ); if ( empty( $forms ) ) { return array( '' => __( '— No payment forms found — create one under Payment Forms —', 'afridirect-payment' ) ); } $options = array( '' => __( '— Select —', 'afridirect-payment' ) ); foreach ( $forms as $form ) { $options[ (string) $form->ID ] = $form->post_title; } return $options; } /** * Resolve the payment methods offered by the configured form. * * @param int $form_id Payment form (afridirect_form) post ID. * @return array method_id => method data. */ protected function get_form_methods( $form_id ) { $form_id = absint( $form_id ); if ( ! $form_id || ! class_exists( 'AfriDirect_Payment_Forms_Helper' ) || ! class_exists( 'AfriDirect_Payment_Methods' ) ) { return array(); } $form = AfriDirect_Payment_Forms_Helper::get_form( $form_id ); if ( ! $form ) { return array(); } $method_ids = $form['allowed_method_ids']; if ( empty( $method_ids ) ) { // No restriction configured: offer every method in the library. $method_ids = array_keys( AfriDirect_Payment_Methods::get_all() ); } $methods = array(); foreach ( $method_ids as $method_id ) { $method = AfriDirect_Payment_Methods::get( $method_id ); if ( $method ) { $methods[ $method_id ] = $method; } } return $methods; } /** * Payment fields on checkout. */ public function payment_fields() { if ( $this->description ) { echo wp_kses_post( wpautop( wptexturize( $this->description ) ) ); } // Option A: If checkout blocks are active, do NOT inject fields. // Instead, show a notice that blocks checkout cannot collect extra fields. if ( function_exists( 'afridirect_is_checkout_blocks_active' ) && afridirect_is_checkout_blocks_active() ) { ?>

default_form_id ); $methods = $this->get_form_methods( $form_id ); // If no form selected or no methods found, show a message. if ( empty( $methods ) ) { ?>

default_form_id ); if ( empty( $form_id ) ) { wc_add_notice( __( 'Payment form not configured. Please contact the store administrator.', 'afridirect-payment' ), 'error' ); return false; } // The selected method must be one this form actually offers. $methods = $this->get_form_methods( $form_id ); if ( empty( $methods ) || ! isset( $methods[ $method_id ] ) ) { wc_add_notice( __( 'Invalid payment method selected.', 'afridirect-payment' ), 'error' ); return false; } return true; } /** * Process payment. * * @param int $order_id Order ID. * @return array */ public function process_payment( $order_id ) { $order = wc_get_order( $order_id ); if ( ! $order ) { return array( 'result' => 'fail', 'redirect' => '', ); } // Verify WooCommerce checkout nonce. if ( ! isset( $_POST['woocommerce-process-checkout-nonce'] ) ) { wc_add_notice( __( 'Security check failed. Please refresh the page and try again.', 'afridirect-payment' ), 'error' ); return array( 'result' => 'fail', 'redirect' => '', ); } $nonce = sanitize_text_field( wp_unslash( $_POST['woocommerce-process-checkout-nonce'] ) ); if ( ! wp_verify_nonce( $nonce, 'woocommerce-process_checkout' ) ) { wc_add_notice( __( 'Security check failed. Please refresh the page and try again.', 'afridirect-payment' ), 'error' ); return array( 'result' => 'fail', 'redirect' => '', ); } // Get payment method ID from POST. $method_id = isset( $_POST['afridirect_payment_method'] ) ? sanitize_key( wp_unslash( $_POST['afridirect_payment_method'] ) ) : ''; if ( empty( $method_id ) ) { return array( 'result' => 'fail', 'redirect' => '', ); } // Get form ID from settings. $form_id = absint( $this->default_form_id ); if ( empty( $form_id ) ) { return array( 'result' => 'fail', 'redirect' => '', ); } // The method must be offered by the configured form. $methods = $this->get_form_methods( $form_id ); if ( empty( $methods ) || ! isset( $methods[ $method_id ] ) ) { return array( 'result' => 'fail', 'redirect' => '', ); } $method = $methods[ $method_id ]; // Save to order meta. $order->update_meta_data( '_afridirect_method_id', $method_id ); $order->update_meta_data( '_afridirect_form_id', $form_id ); $order->update_meta_data( '_afridirect_method_label', $method['label'] ); // Countdown timer: form timer > per-method timer > 48h default. $timer_minutes = absint( get_post_meta( $form_id, '_afridirect_timer_minutes', true ) ); if ( $timer_minutes < 1 && isset( $method['timer_minutes'] ) && '' !== $method['timer_minutes'] ) { $timer_minutes = absint( $method['timer_minutes'] ); } if ( $timer_minutes < 1 ) { $timer_minutes = (int) apply_filters( 'afridirect_woo_default_timer_minutes', 2880 ); } $order->update_meta_data( '_afridirect_timer_minutes', $timer_minutes ); // Set expiration time (unix timestamp). $expires_at = time() + ( $timer_minutes * MINUTE_IN_SECONDS ); $order->update_meta_data( '_afridirect_expires_at', $expires_at ); // Set order status to afridirect-pending. $order->update_status( 'adp-pending', __( 'Awaiting payment proof upload.', 'afridirect-payment' ) ); $order->save(); // Some checkout stacks validate the new status against the registered // order-status list and silently revert it (typically to "pending") if it // doesn't recognise ours yet at that point in the request. Log it so it can // be diagnosed; the customer-facing pages self-heal via // AfriDirect_Payment_Order_Statuses::maybe_restore_pending(). if ( 'adp-pending' !== $order->get_status() && function_exists( 'wc_get_logger' ) ) { wc_get_logger()->warning( 'AfriDirect: order status did not persist as afridirect-pending after checkout.', array( 'source' => 'afridirect-payment', 'order_id' => $order->get_id(), 'actual_status' => $order->get_status(), ) ); } // Remove cart. if ( WC()->cart ) { WC()->cart->empty_cart(); } // Send the customer to the plugin's own pending page (countdown + proof // upload). This works with both classic and block checkout, which the // WooCommerce order-received page does not reliably. $pending_page_id = (int) get_option( 'afridirect_pending_page_id', 0 ); if ( ! $pending_page_id && function_exists( 'afridirect_payment' ) ) { afridirect_payment()->ensure_pages(); $pending_page_id = (int) get_option( 'afridirect_pending_page_id', 0 ); } $redirect = $this->get_return_url( $order ); if ( $pending_page_id ) { $redirect = add_query_arg( array( 'order_id' => $order->get_id(), 'order_key' => $order->get_order_key(), ), get_permalink( $pending_page_id ) ); } return array( 'result' => 'success', 'redirect' => $redirect, ); } /** * Thank you page content. * * @param int $order_id Order ID. */ public function thankyou_page( $order_id ) { $order = wc_get_order( $order_id ); if ( ! $order || $this->id !== $order->get_payment_method() ) { return; } $expires_at = $order->get_meta( '_afridirect_expires_at' ); $proof_url = $order->get_meta( '_afridirect_proof_url' ); $proof_attachment_id = $order->get_meta( '_afridirect_proof_attachment_id' ); // Some checkout stacks reset an unrecognised order status right after // checkout; restore ours if needed so the customer isn't left with nothing. $order_status = class_exists( 'AfriDirect_Payment_Order_Statuses' ) ? AfriDirect_Payment_Order_Statuses::maybe_restore_pending( $order ) : $order->get_status(); // Load status-specific views (payment instructions are now included in status-pending.php). switch ( $order_status ) { case 'adp-pending': include AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'public/views/status-pending.php'; break; case 'adp-proof': include AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'public/views/status-proof.php'; break; case 'adp-rejected': include AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'public/views/status-rejected.php'; break; case 'adp-expired': include AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'public/views/status-expired.php'; break; } } /** * View order page content (My Account). * * @param int $order_id Order ID. */ public function view_order_page( $order_id ) { $order = wc_get_order( $order_id ); if ( ! $order || $this->id !== $order->get_payment_method() ) { return; } // Only show if order is not completed/cancelled. if ( in_array( $order->get_status(), array( 'completed', 'cancelled', 'refunded' ), true ) ) { return; } $expires_at = $order->get_meta( '_afridirect_expires_at' ); $proof_url = $order->get_meta( '_afridirect_proof_url' ); $proof_attachment_id = $order->get_meta( '_afridirect_proof_attachment_id' ); // Some checkout stacks reset an unrecognised order status right after // checkout; restore ours if needed so the customer isn't left with nothing. $order_status = class_exists( 'AfriDirect_Payment_Order_Statuses' ) ? AfriDirect_Payment_Order_Statuses::maybe_restore_pending( $order ) : $order->get_status(); // Load status-specific views (payment instructions are now included in status-pending.php). switch ( $order_status ) { case 'adp-pending': include AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'public/views/status-pending.php'; break; case 'adp-proof': include AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'public/views/status-proof.php'; break; case 'adp-rejected': include AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'public/views/status-rejected.php'; break; case 'adp-expired': include AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'public/views/status-expired.php'; break; } } /** * Email instructions. * * @param WC_Order $order Order object. * @param bool $sent_to_admin Whether sent to admin. * @param bool $plain_text Whether plain text email. */ public function email_instructions( $order, $sent_to_admin, $plain_text ) { if ( $this->id !== $order->get_payment_method() || $sent_to_admin ) { return; } $form_id = $order->get_meta( '_afridirect_form_id' ); $method_id = $order->get_meta( '_afridirect_method_id' ); if ( empty( $form_id ) || empty( $method_id ) ) { return; } $method = AfriDirect_Payment_Methods::get( $method_id ); if ( ! $method || empty( $method['instructions'] ) ) { return; } if ( $plain_text ) { echo "\n\n" . esc_html__( 'Payment Instructions:', 'afridirect-payment' ) . "\n\n"; echo esc_html( wp_strip_all_tags( $method['instructions'] ) ) . "\n"; } else { echo '

' . esc_html__( 'Payment Instructions', 'afridirect-payment' ) . '

'; echo wp_kses_post( $method['instructions'] ); } } }