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'] );
}
}
}