init_hooks(); } /** * Initialize hooks. */ private function init_hooks() { add_shortcode( 'afridirect_form', array( $this, 'render_payment_form' ) ); add_shortcode( 'afridirect_payment_pending', array( $this, 'render_payment_pending' ) ); add_shortcode( 'afridirect_payment_receipt', array( $this, 'render_payment_receipt' ) ); } /** * Render payment form shortcode. * * @param array $atts Shortcode attributes. * @return string */ public function render_payment_form( $atts ) { $atts = shortcode_atts( array( 'id' => 0, ), $atts, 'afridirect_form' ); $form_id = absint( $atts['id'] ); if ( ! $form_id ) { return '
' . esc_html__( 'Invalid form ID.', 'afridirect-payment' ) . '
'; } // Get form data. $form = AfriDirect_Payment_Forms_Helper::get_form( $form_id ); if ( ! $form ) { return '' . esc_html__( 'Form not found.', 'afridirect-payment' ) . '
'; } // Get allowed payment methods. $allowed_methods = array(); // Normalize method IDs (handle different formats). $method_ids = AfriDirect_Payment_Forms_Helper::normalize_method_ids( $form['allowed_method_ids'] ); // If no methods selected, use all active methods as fallback. if ( empty( $method_ids ) ) { $all_methods = AfriDirect_Payment_Methods::get_all(); $method_ids = array_keys( $all_methods ); } // Get method data for each ID. foreach ( $method_ids as $method_id ) { $method = AfriDirect_Payment_Methods::get( $method_id ); if ( $method ) { $allowed_methods[ $method_id ] = $method; } } if ( empty( $allowed_methods ) ) { return '' . esc_html__( 'No payment methods available. Please create payment methods first.', 'afridirect-payment' ) . '
'; } // Enqueue scripts and styles. wp_enqueue_script( 'afridirect-frontend' ); wp_enqueue_style( 'afridirect-frontend' ); // Localize script for payment form creation. wp_localize_script( 'afridirect-frontend', 'afridirectFormData', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'afridirect_create_payment' ), 'formId' => $form_id, ) ); // Also localize afridirectPayment for upload functionality (if not already localized by main class). // This ensures it's available even if the main enqueue didn't run. wp_localize_script( 'afridirect-frontend', 'afridirectPayment', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'afridirect-payment-nonce' ), 'i18n' => array( 'copied' => __( 'Copied!', 'afridirect-payment' ), 'copy' => __( 'Copy', 'afridirect-payment' ), 'selectFile' => __( 'Please select a file to upload.', 'afridirect-payment' ), 'uploading' => __( 'Uploading...', 'afridirect-payment' ), 'uploadComplete' => __( 'Upload complete!', 'afridirect-payment' ), 'errorOccurred' => __( 'An error occurred during upload. Please try again.', 'afridirect-payment' ), 'timeExpired' => __( 'Time expired!', 'afridirect-payment' ), 'proofReceived' => __( 'Proof received — under review', 'afridirect-payment' ), ), ) ); // Render form. ob_start(); include AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'public/views/payment-form.php'; return ob_get_clean(); } /** * Render payment pending shortcode. * * @param array $atts Shortcode attributes. * @return string */ public function render_payment_pending( $atts ) { // Read identifiers from the query string. This is a public shortcode reached // via a bookmarkable link, so there is no nonce; the key/order-key is // validated with hash_equals() before any data is shown. // phpcs:disable WordPress.Security.NonceVerification.Recommended $order_id = isset( $_GET['order_id'] ) ? absint( wp_unslash( $_GET['order_id'] ) ) : 0; $order_key = isset( $_GET['order_key'] ) ? sanitize_text_field( wp_unslash( $_GET['order_key'] ) ) : ''; $payment_id = isset( $_GET['pid'] ) ? absint( wp_unslash( $_GET['pid'] ) ) : 0; $key = isset( $_GET['key'] ) ? sanitize_text_field( wp_unslash( $_GET['key'] ) ) : ''; // phpcs:enable WordPress.Security.NonceVerification.Recommended // WooCommerce order flow. if ( $order_id && $order_key && function_exists( 'wc_get_order' ) ) { return $this->render_wc_pending( $order_id, $order_key ); } if ( ! $payment_id || ! $key ) { return '' . esc_html__( 'Invalid payment link.', 'afridirect-payment' ) . '
'; } // Get payment and verify key using hash_equals before showing any status. $payment = AfriDirect_Payments::get_payment( $payment_id ); if ( ! $payment ) { return '' . esc_html__( 'Invalid payment link.', 'afridirect-payment' ) . '
'; } // Validate key using hash_equals for secure comparison. $stored_key = isset( $payment['access_key'] ) ? $payment['access_key'] : ''; if ( empty( $stored_key ) || ! hash_equals( $stored_key, $key ) ) { return '' . esc_html__( 'Invalid payment link.', 'afridirect-payment' ) . '
'; } // Get payment method. $method = null; if ( ! empty( $payment['method_id'] ) ) { $method = AfriDirect_Payment_Methods::get( $payment['method_id'] ); } // Enqueue scripts and styles. wp_enqueue_script( 'afridirect-frontend' ); wp_enqueue_style( 'afridirect-frontend' ); // Localize script for upload - ensure afridirectPayment is available. wp_localize_script( 'afridirect-frontend', 'afridirectPayment', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'afridirect-payment-nonce' ), 'i18n' => array( 'copied' => __( 'Copied!', 'afridirect-payment' ), 'copy' => __( 'Copy', 'afridirect-payment' ), 'selectFile' => __( 'Please select a file to upload.', 'afridirect-payment' ), 'uploading' => __( 'Uploading...', 'afridirect-payment' ), 'uploadComplete' => __( 'Upload complete!', 'afridirect-payment' ), 'errorOccurred' => __( 'An error occurred during upload. Please try again.', 'afridirect-payment' ), 'timeExpired' => __( 'Time expired!', 'afridirect-payment' ), 'proofReceived' => __( 'Proof received — under review', 'afridirect-payment' ), ), ) ); // Render pending view. ob_start(); include AFRIDIRECT_PAYMENT_PLUGIN_DIR . 'public/views/payment-pending-standalone.php'; return ob_get_clean(); } /** * Render the pending / proof-upload view for a WooCommerce order. * * The gateway redirects here after checkout so the customer sees the countdown * and upload form regardless of whether the store uses the classic or block * order-received page. * * @param int $order_id WooCommerce order ID. * @param string $order_key Order key from the redirect URL. * @return string */ private function render_wc_pending( $order_id, $order_key ) { $order = wc_get_order( $order_id ); if ( ! $order || 'afridirect_payment' !== $order->get_payment_method() || ! hash_equals( (string) $order->get_order_key(), $order_key ) ) { return '' . esc_html__( 'Invalid payment link.', 'afridirect-payment' ) . '
'; } wp_enqueue_script( 'afridirect-frontend' ); wp_enqueue_style( 'afridirect-frontend' ); $this->localize_frontend(); $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(); ob_start(); 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; default: echo '' . esc_html__( 'Your payment is being processed. Thank you!', 'afridirect-payment' ) . '
' . esc_html__( 'Invalid receipt link.', 'afridirect-payment' ) . '
' . esc_html__( 'Invalid receipt link.', 'afridirect-payment' ) . '
' . esc_html__( 'Invalid receipt link.', 'afridirect-payment' ) . '