} */ protected function get_payer_field_config(): array { $type = in_array( $this->payerFieldType, [ 'phone_last4', 'email', 'account' ], true ) ? $this->payerFieldType : 'phone_last4'; switch ( $type ) { case 'email': $label = $this->payerFieldLabel ? $this->payerFieldLabel : __( 'Payer Email', 'all-qr-pay-for-woocommerce' ); return [ 'type' => 'email', 'key' => 'payer_email', 'label' => $label, 'input_attrs' => [ 'type' => 'email', 'maxlength' => '100', 'placeholder' => __( 'e.g. name@example.com', 'all-qr-pay-for-woocommerce' ), ], ]; case 'account': $label = $this->payerFieldLabel ? $this->payerFieldLabel : __( 'Payer Account ID', 'all-qr-pay-for-woocommerce' ); return [ 'type' => 'account', 'key' => 'payer_account', 'label' => $label, 'input_attrs' => [ 'type' => 'text', 'maxlength' => '64', /* translators: %s: payer field label (e.g. Payer Alipay ID) */ 'placeholder' => sprintf( __( 'Enter %s', 'all-qr-pay-for-woocommerce' ), $label ), ], ]; case 'phone_last4': default: $label = $this->payerFieldLabel ? $this->payerFieldLabel : __( 'Payer Phone (last 4)', 'all-qr-pay-for-woocommerce' ); return [ 'type' => 'phone_last4', 'key' => 'payer_last4', 'label' => $label, 'input_attrs' => [ 'type' => 'text', 'maxlength' => '4', 'placeholder' => sprintf( /* translators: %s: Payment provider name (e.g., bKash, eSewa) */ __( 'Last 4 digits of your %s number', 'all-qr-pay-for-woocommerce' ), $this->providerName ), ], ]; } } /** * Read and sanitize the posted secondary payer field value. * * @param array $config Field config from get_payer_field_config(). * @return string */ protected function get_posted_payer_ref( array $config ): string { $field = $this->id . '_' . $config['key']; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- WooCommerce checkout nonce is validated upstream. if ( ! isset( $_POST[ $field ] ) ) { return ''; } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- See note above. $raw = is_scalar( $_POST[ $field ] ) ? sanitize_text_field( wp_unslash( (string) $_POST[ $field ] ) ) : ''; if ( 'email' === $config['type'] ) { return sanitize_email( $raw ); } $value = $raw; // Store phone last-4 as digits only. if ( 'phone_last4' === $config['type'] && '' !== $value ) { return preg_replace( '/\D+/', '', $value ); } return $value; } /** * Data passed to the Cart & Checkout Blocks script for this gateway. * * @return array */ public function get_block_data(): array { $payer_cfg = $this->get_payer_field_config(); return [ 'id' => $this->id, 'enabled' => 'yes' === $this->enabled, 'title' => wp_strip_all_tags( $this->title ), 'descriptionHtml' => $this->description ? wp_kses_post( wpautop( $this->description ) ) : '', 'provider' => $this->providerName, 'icon' => $this->icon ? esc_url_raw( $this->icon ) : '', 'qrImage' => $this->qr_image_url ? esc_url_raw( $this->qr_image_url ) : '', 'account' => $this->account_number, 'feePercent' => (float) $this->processing_fee, 'txnField' => $this->id . '_txn_id', 'txnLabel' => __( 'Transaction ID', 'all-qr-pay-for-woocommerce' ), 'txnPlaceholder' => sprintf( /* translators: %s: Payment provider name (e.g., bKash, PayPal) */ __( 'Enter %s Transaction ID', 'all-qr-pay-for-woocommerce' ), $this->providerName ), 'payerField' => $this->id . '_' . $payer_cfg['key'], 'payerType' => $payer_cfg['type'], 'payerLabel' => $payer_cfg['label'], 'payerInputType' => isset( $payer_cfg['input_attrs']['type'] ) ? $payer_cfg['input_attrs']['type'] : 'text', 'payerMaxLength' => isset( $payer_cfg['input_attrs']['maxlength'] ) ? (int) $payer_cfg['input_attrs']['maxlength'] : 0, 'payerPlaceholder' => isset( $payer_cfg['input_attrs']['placeholder'] ) ? $payer_cfg['input_attrs']['placeholder'] : '', 'strings' => [ 'scanAndPay' => __( 'Scan & Pay with ', 'all-qr-pay-for-woocommerce' ), 'processingFee' => __( 'Processing Fee:', 'all-qr-pay-for-woocommerce' ), 'amount' => __( 'Amount:', 'all-qr-pay-for-woocommerce' ), 'account' => __( 'Account:', 'all-qr-pay-for-woocommerce' ), 'afterPaying' => __( 'After paying, enter your transaction ID below.', 'all-qr-pay-for-woocommerce' ), 'txnRequired' => __( 'Transaction ID is required.', 'all-qr-pay-for-woocommerce' ), 'txnTooLong' => __( 'Transaction ID must be 100 characters or fewer.', 'all-qr-pay-for-woocommerce' ), 'invalidEmail' => __( 'Please enter a valid payer email address.', 'all-qr-pay-for-woocommerce' ), 'accountTooLong' => __( 'Payer account ID must be 64 characters or fewer.', 'all-qr-pay-for-woocommerce' ), 'invalidLast4' => __( 'Payer phone last 4 must be exactly 4 digits.', 'all-qr-pay-for-woocommerce' ), ], ]; } public function __construct() { // Gateway ID – must be unique and prefixed. $this->id = strtolower( 'aqrpfw_' . $this->providerName ); $this->method_title = sprintf( /* translators: %s: Payment provider name (e.g., bKash, Nagad, Upay, Rocket) */ esc_html__( '%s QR Pay', 'all-qr-pay-for-woocommerce' ), $this->providerName ); $this->method_description = sprintf( /* translators: %s: Payment provider name (e.g., bKash, Nagad, Upay, Rocket) */ esc_html__( 'Accept payments via %s QR (personal account).', 'all-qr-pay-for-woocommerce' ), $this->providerName ); $this->has_fields = true; $this->init_form_fields(); $this->init_settings(); $this->title = $this->get_option( 'title', $this->providerName . ' QR Pay' ); $this->description = $this->get_option( 'description', __( 'Scan QR code and enter your transaction ID.', 'all-qr-pay-for-woocommerce' ) ); $this->processing_fee = $this->get_option( 'processing_fee', '0' ); $this->instructions = $this->get_option( 'instructions', __( 'We will verify your payment manually.', 'all-qr-pay-for-woocommerce' ) ); $this->qr_image_url = $this->get_option( 'qr_image_url', '' ); $this->account_number = $this->get_option( 'account_number', '' ); add_action( "woocommerce_update_options_payment_gateways_{$this->id}", [ $this, 'process_admin_options' ] ); self::register_shared_hooks(); } /** * Whether the shared (non per-gateway) hooks were registered. * * @var bool */ protected static $shared_hooks_registered = false; /** * Register hooks once for all gateways, instead of once per gateway * (there are 28 gateways, so per-gateway hooks ran 28 times per request). */ protected static function register_shared_hooks(): void { if ( self::$shared_hooks_registered ) { return; } self::$shared_hooks_registered = true; add_action( 'woocommerce_cart_calculate_fees', [ __CLASS__, 'dispatch_cart_fees' ], 999 ); add_action( 'wp_enqueue_scripts', [ __CLASS__, 'enqueue_checkout_script' ] ); add_action( 'woocommerce_checkout_create_order', [ __CLASS__, 'dispatch_order_fee' ], 20, 2 ); } /** * Find a QR Pay gateway instance by its ID. * * @param string $id Gateway ID. * @return AbstractQRPay|null */ protected static function get_gateway_instance( string $id ) { if ( 0 !== strpos( $id, 'aqrpfw_' ) || ! function_exists( 'WC' ) || ! WC()->payment_gateways() ) { return null; } $gateways = WC()->payment_gateways()->payment_gateways(); return ( isset( $gateways[ $id ] ) && $gateways[ $id ] instanceof self ) ? $gateways[ $id ] : null; } /** * Add the processing fee for the currently chosen QR Pay gateway only. * * @param \WC_Cart $cart Cart. */ public static function dispatch_cart_fees( $cart ): void { if ( is_admin() && ! defined( 'DOING_AJAX' ) ) { return; } $gateway = self::get_gateway_instance( self::get_chosen_method() ); if ( $gateway ) { $gateway->calculate_fees( $cart ); } } /** * Make sure the order has the processing fee for its QR Pay gateway. * * @param \WC_Order $order Order. * @param array $data Checkout data. */ public static function dispatch_order_fee( $order, $data ): void { if ( ! $order instanceof \WC_Order ) { return; } $gateway = self::get_gateway_instance( (string) $order->get_payment_method() ); if ( $gateway ) { $gateway->ensure_fee_on_order( $order, $data ); } } public function ensure_fee_on_order( \WC_Order $order, $data ) { // Only for this gateway. if ( $order->get_payment_method() !== $this->id ) { return; } $fee_percent = (float) $this->get_option( 'processing_fee', '0' ); if ( $fee_percent <= 0 ) { return; } // If a fee is already present (from cart), do nothing. foreach ( $order->get_fees() as $fee_item ) { if ( $fee_item->get_name() === esc_html__( 'Payment Processing Fee', 'all-qr-pay-for-woocommerce' ) ) { return; } } // Compute base from current order items + shipping (pre-fee). $items_total = 0.0; foreach ( $order->get_items() as $item ) { $items_total += (float) $item->get_total(); } $shipping_total = (float) $order->get_shipping_total(); $base = $items_total + $shipping_total; if ( $base <= 0 ) { return; } $fee_amount = round( $base * ( $fee_percent / 100 ), wc_get_price_decimals() ); $fee = new \WC_Order_Item_Fee(); $fee->set_name( esc_html__( 'Payment Processing Fee', 'all-qr-pay-for-woocommerce' ) ); $fee->set_amount( $fee_amount ); $fee->set_total( $fee_amount ); if ( wc_tax_enabled() ) { $fee->set_tax_status( 'taxable' ); $fee->set_tax_class( '' ); // standard. } else { $fee->set_tax_status( 'none' ); } $order->add_item( $fee ); // Recalculate totals so Thank You page, emails, and order admin reflect it. $order->calculate_totals( true ); } /** * Refresh checkout totals when the payment method changes (classic checkout). * Only needed when at least one QR Pay gateway charges a processing fee. */ public static function enqueue_checkout_script(): void { if ( ! function_exists( 'is_checkout' ) || ! is_checkout() || ! function_exists( 'WC' ) || ! WC()->payment_gateways() ) { return; } $has_fee = false; foreach ( WC()->payment_gateways()->payment_gateways() as $gateway ) { if ( $gateway instanceof self && 'yes' === $gateway->enabled && (float) $gateway->processing_fee > 0 ) { $has_fee = true; break; } } if ( ! $has_fee ) { return; } wp_add_inline_script( 'wc-checkout', "jQuery(function($){ $(document.body).on('change', 'input[name=\"payment_method\"]', function(){ $(document.body).trigger('update_checkout'); }); });" ); } /** * Get the currently chosen payment method during checkout (works in AJAX too). */ protected static function get_chosen_method(): string { // phpcs:disable WordPress.Security.NonceVerification.Missing // 1) Direct POST. // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only access to checkout data; WooCommerce handles nonce verification upstream. if ( isset( $_POST['payment_method'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- WooCommerce checkout nonce is validated upstream. return sanitize_text_field( wp_unslash( $_POST['payment_method'] ) ); } // 2) Serialized post_data (default Woo AJAX path). // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only inspection of serialized checkout data; nonce is verified by WooCommerce core. if ( isset( $_POST['post_data'] ) ) { // Sanitize the entire string so PHPCS stops complaining. $raw_post_data = sanitize_text_field( wp_unslash( $_POST['post_data'] ) ); $data = []; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized // WooCommerce core sends `post_data` as a URL-encoded query string. // We only parse it into $data and later sanitize the specific `payment_method` field before use. // Safe because we sanitized the whole string before parsing it. parse_str( $raw_post_data, $data ); if ( isset( $data['payment_method'] ) ) { return sanitize_text_field( $data['payment_method'] ); } } // phpcs:enable WordPress.Security.NonceVerification.Missing // 3) Fallback to session. if ( WC()->session ) { $chosen = WC()->session->get( 'chosen_payment_method' ); if ( $chosen ) { return $chosen; } } return ''; } public function calculate_fees( $cart ) { if ( is_admin() && ! defined( 'DOING_AJAX' ) ) { return; } if ( ! $cart instanceof \WC_Cart ) { return; } $chosen = $this->get_chosen_method(); if ( $chosen !== $this->id ) { return; } $fee_percent = (float) $this->get_option( 'processing_fee', '0' ); if ( $fee_percent <= 0 ) { return; } // Base: subtotal + shipping. $base = (float) $cart->get_subtotal() + (float) $cart->get_shipping_total(); if ( $base <= 0 ) { return; } $surcharge = round( $base * ( $fee_percent / 100 ), wc_get_price_decimals() ); $taxable = wc_tax_enabled(); $cart->add_fee( esc_html__( 'Payment Processing Fee', 'all-qr-pay-for-woocommerce' ), $surcharge, $taxable ); } public function init_form_fields() { $this->form_fields = [ 'enabled' => [ 'title' => esc_html__( 'Enable/Disable', 'all-qr-pay-for-woocommerce' ), 'type' => 'checkbox', 'label' => sprintf( /* translators: %s: Payment provider name (e.g., bKash, Nagad, Upay, Rocket) */ esc_html__( 'Enable %s QR Pay', 'all-qr-pay-for-woocommerce' ), $this->providerName ), 'default' => 'no', ], 'title' => [ 'title' => esc_html__( 'Title', 'all-qr-pay-for-woocommerce' ), 'type' => 'text', 'default' => sprintf( /* translators: %s: Payment provider name (e.g., bKash, Nagad, Upay, Rocket) */ esc_html__( '%s QR Pay', 'all-qr-pay-for-woocommerce' ), $this->providerName ), 'desc_tip' => true, 'placeholder' => sprintf( /* translators: %s: Payment provider name (e.g., bKash, eSewa) */ __( '%s QR Pay', 'all-qr-pay-for-woocommerce' ), $this->providerName ), ], 'description' => [ 'title' => esc_html__( 'Description', 'all-qr-pay-for-woocommerce' ), 'type' => 'textarea', 'default' => esc_html__( 'Scan QR and provide transaction details.', 'all-qr-pay-for-woocommerce' ), 'placeholder' => __( 'Shown to customers at checkout.', 'all-qr-pay-for-woocommerce' ), ], 'processing_fee' => [ 'title' => esc_html__( 'Payment Processing Fee (%)', 'all-qr-pay-for-woocommerce' ), 'type' => 'number', 'description' => esc_html__( 'Enter a percentage fee to apply on checkout. Example: 1.5 means 1.5% extra will be added.', 'all-qr-pay-for-woocommerce' ), 'default' => '0', 'desc_tip' => true, 'placeholder' => '0', 'custom_attributes' => [ 'step' => '0.01', 'min' => '0', ], ], 'instructions' => [ 'title' => esc_html__( 'Instructions', 'all-qr-pay-for-woocommerce' ), 'type' => 'textarea', 'default' => esc_html__( 'We will confirm your payment manually.', 'all-qr-pay-for-woocommerce' ), 'placeholder' => __( 'e.g. We will verify your payment manually.', 'all-qr-pay-for-woocommerce' ), ], 'qr_image_url' => [ 'title' => esc_html__( 'QR Image URL', 'all-qr-pay-for-woocommerce' ), 'type' => 'text', 'description' => esc_html__( 'Paste Media Library URL for your QR code.', 'all-qr-pay-for-woocommerce' ), 'default' => '', 'placeholder' => 'https://example.com/wp-content/uploads/qr.png', ], 'account_number' => [ 'title' => esc_html__( 'Account Number', 'all-qr-pay-for-woocommerce' ), 'type' => 'text', 'default' => '', 'placeholder' => sprintf( /* translators: %s: Payment provider name (e.g., bKash, eSewa) */ __( 'Your %s account / wallet number', 'all-qr-pay-for-woocommerce' ), $this->providerName ), ], ]; } /** * Cart total to show next to the QR code, including this gateway's processing fee * even when another payment method is currently selected. */ protected function get_display_total(): float { if ( ! WC()->cart ) { return 0.0; } $total = (float) WC()->cart->get_total( 'edit' ); $fee_percent = (float) $this->processing_fee; // When this gateway is chosen, the cart total already contains the fee. if ( $fee_percent > 0 && self::get_chosen_method() !== $this->id ) { $base = (float) WC()->cart->get_subtotal() + (float) WC()->cart->get_shipping_total(); $total += round( $base * ( $fee_percent / 100 ), wc_get_price_decimals() ); } return $total; } /** * Whether another active order already used this Transaction ID with this gateway. * * @param string $txn Transaction ID. * @param int $exclude_order_id Order ID to ignore (e.g. order-pay retries). */ protected function is_duplicate_txn( string $txn, int $exclude_order_id = 0 ): bool { $args = [ 'limit' => 2, 'return' => 'ids', 'payment_method' => $this->id, 'status' => [ 'on-hold', 'processing', 'completed' ], 'meta_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Single indexed lookup, limited to one row. [ 'key' => '_aqrpfw_txn_id', 'value' => $txn, 'compare' => '=', ], ], ]; // Ignore the order being paid (limit 2 so one match can be discarded). $matches = array_diff( array_map( 'absint', (array) wc_get_orders( $args ) ), [ $exclude_order_id ] ); $is_duplicate = ! empty( $matches ); /** * Filters whether a Transaction ID is treated as already used. * * @param bool $is_duplicate Whether a duplicate was found. * @param string $txn Transaction ID. * @param string $gateway_id Gateway ID. */ return (bool) apply_filters( 'aqrpfw_is_duplicate_txn', $is_duplicate, $txn, $this->id ); } public function payment_fields() { $txn_field = $this->id . '_txn_id'; $payer_cfg = $this->get_payer_field_config(); $payer_field = $this->id . '_' . $payer_cfg['key']; if ( $this->description ) { echo wp_kses_post( wpautop( $this->description ) ); } // Amount the customer will actually be charged (includes the processing fee). $fee_percent = (float) $this->processing_fee; $final_amount = $this->get_display_total(); echo '
'; echo '
'; if ( $this->qr_image_url ) { echo '
'; echo '' . esc_attr( $this->providerName . ' QR' ) . ''; echo '
'; } echo '
'; echo '

' . esc_html__( 'Scan & Pay with ', 'all-qr-pay-for-woocommerce' ) . esc_html( $this->providerName ) . '

'; if ( $fee_percent > 0 ) { echo '

' . esc_html__( 'Processing Fee:', 'all-qr-pay-for-woocommerce' ) . ' ' . esc_html( $fee_percent ) . '%

'; } echo '

' . esc_html__( 'Amount:', 'all-qr-pay-for-woocommerce' ) . ' ' . wp_kses_post( wc_price( $final_amount ) ) . '

'; if ( $this->account_number ) { echo '

' . esc_html__( 'Account:', 'all-qr-pay-for-woocommerce' ) . ' ' . esc_html( $this->account_number ) . '

'; } echo '

' . esc_html__( 'After paying, enter your transaction ID below.', 'all-qr-pay-for-woocommerce' ) . '

'; echo '
'; // flex details. echo '
'; // flex row. // Custom fields. $txn_placeholder = sprintf( /* translators: %s: Payment provider name (e.g., bKash, PayPal) */ __( 'Enter %s Transaction ID', 'all-qr-pay-for-woocommerce' ), $this->providerName ); echo '

'; $input_type = isset( $payer_cfg['input_attrs']['type'] ) ? $payer_cfg['input_attrs']['type'] : 'text'; $maxlength = isset( $payer_cfg['input_attrs']['maxlength'] ) ? $payer_cfg['input_attrs']['maxlength'] : ''; $payer_placeholder = isset( $payer_cfg['input_attrs']['placeholder'] ) ? $payer_cfg['input_attrs']['placeholder'] : ''; echo '

'; echo '
'; echo '
'; // aqrpfw-box. } public function validate_fields() { $txn_field = $this->id . '_txn_id'; $payer_cfg = $this->get_payer_field_config(); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Checkout request is nonce-verified by WooCommerce core; here we only read and sanitize posted values. $txn = isset( $_POST[ $txn_field ] ) ? sanitize_text_field( wp_unslash( $_POST[ $txn_field ] ) ) : ''; $payer_ref = $this->get_posted_payer_ref( $payer_cfg ); if ( ! $txn ) { wc_add_notice( esc_html__( 'Transaction ID is required.', 'all-qr-pay-for-woocommerce' ), 'error' ); return false; } if ( strlen( $txn ) > 100 ) { wc_add_notice( esc_html__( 'Transaction ID must be 100 characters or fewer.', 'all-qr-pay-for-woocommerce' ), 'error' ); return false; } global $wp; $pay_order_id = isset( $wp->query_vars['order-pay'] ) ? absint( $wp->query_vars['order-pay'] ) : 0; if ( $this->is_duplicate_txn( $txn, $pay_order_id ) ) { wc_add_notice( esc_html__( 'This Transaction ID has already been used for another order. Please check it and try again.', 'all-qr-pay-for-woocommerce' ), 'error' ); return false; } if ( $payer_ref ) { switch ( $payer_cfg['type'] ) { case 'email': if ( ! is_email( $payer_ref ) ) { wc_add_notice( esc_html__( 'Please enter a valid payer email address.', 'all-qr-pay-for-woocommerce' ), 'error' ); return false; } break; case 'account': if ( strlen( $payer_ref ) > 64 ) { wc_add_notice( esc_html__( 'Payer account ID must be 64 characters or fewer.', 'all-qr-pay-for-woocommerce' ), 'error' ); return false; } break; case 'phone_last4': default: if ( strlen( preg_replace( '/\D+/', '', $payer_ref ) ) !== 4 ) { wc_add_notice( esc_html__( 'Payer phone last 4 must be exactly 4 digits.', 'all-qr-pay-for-woocommerce' ), 'error' ); return false; } break; } } return true; } public function process_payment( $order_id ) { $order = wc_get_order( $order_id ); if ( ! $order ) { wc_add_notice( esc_html__( 'Order not found. Please try again.', 'all-qr-pay-for-woocommerce' ), 'error' ); return [ 'result' => 'failure' ]; } $txn_field = $this->id . '_txn_id'; $payer_cfg = $this->get_payer_field_config(); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- WooCommerce checkout nonce is validated upstream; we only read and sanitize posted data. $txn = isset( $_POST[ $txn_field ] ) ? sanitize_text_field( wp_unslash( $_POST[ $txn_field ] ) ) : ''; $payer_ref = $this->get_posted_payer_ref( $payer_cfg ); // Save order meta (all prefixed and renamed). $order->update_meta_data( '_aqrpfw_provider', $this->providerName ); if ( $txn ) { $order->update_meta_data( '_aqrpfw_txn_id', $txn ); } if ( $payer_ref ) { $order->update_meta_data( '_aqrpfw_payer_ref', $payer_ref ); $order->update_meta_data( '_aqrpfw_payer_ref_type', $payer_cfg['type'] ); $order->update_meta_data( '_aqrpfw_payer_ref_label', $payer_cfg['label'] ); // Backward compatibility for phone-based gateways / older display code. if ( 'phone_last4' === $payer_cfg['type'] ) { $order->update_meta_data( '_aqrpfw_payer_last4', $payer_ref ); } } $order->save_meta_data(); // Set status and reduce stock. $order->update_status( 'on-hold', esc_html__( 'Awaiting QR payment verification.', 'all-qr-pay-for-woocommerce' ) ); wc_reduce_stock_levels( $order_id ); WC()->cart->empty_cart(); if ( function_exists( 'wc_clear_notices' ) ) { wc_clear_notices(); } // Debug logging (avoid writing full emails into logs). if ( function_exists( 'wc_get_logger' ) ) { $log_ref = $payer_ref; if ( 'email' === $payer_cfg['type'] && '' !== $payer_ref ) { $log_ref = '[redacted-email]'; } $logger = wc_get_logger(); $logger->info( "QRPay order {$order_id} via {$this->providerName}, TXN={$txn}, payer_ref={$log_ref}, type={$payer_cfg['type']}", [ 'source' => 'all-qr-pay-for-woocommerce', ] ); } return [ 'result' => 'success', 'redirect' => $this->get_return_url( $order ), ]; } }