get_order_key(), $order_key ) ) { wp_die( esc_html__( 'This payment link is invalid or has expired.', 'aam-partial-cod' ), esc_html__( 'Payment error', 'aam-partial-cod' ), array( 'response' => 403 ) ); } // Once submitted or verified, there is nothing to pay — show the order. $status = $order->get_meta( BD_PCOD_Helpers::META_STATUS ); if ( in_array( $status, array( BD_PCOD_Helpers::STATUS_SUBMITTED, BD_PCOD_Helpers::STATUS_VERIFIED ), true ) ) { wp_safe_redirect( $order->get_checkout_order_received_url() ); exit; } $gateway_id = $order->get_payment_method(); // Bank transfer gateway gets its own template. if ( BD_PCOD_BANK_GATEWAY_ID === $gateway_id ) { $banks = BD_PCOD_Helpers::get_enabled_banks(); $default_bank = $banks ? array_key_first( $banks ) : ''; $nonce = wp_create_nonce( 'bd_pcod_bank_submit' ); $icon = trim( (string) BD_PCOD_Helpers::get_setting( $gateway_id, 'icon_url', '' ) ); if ( '' === $icon ) { $icon = BD_PCOD_Helpers::default_icon( $gateway_id ); } $this->enqueue_standalone_assets( array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => $nonce, 'action' => 'bd_pcod_bank_submit', 'copied' => __( 'Copied!', 'aam-partial-cod' ), 'copy' => __( 'Copy', 'aam-partial-cod' ), 'required' => __( 'Please select a bank and enter your account number.', 'aam-partial-cod' ), 'invalidAccount' => __( 'Please enter at least the last 4 digits of your account number.', 'aam-partial-cod' ), 'leaveWarning' => __( 'You have not completed your payment yet. If you leave now, your order will stay unconfirmed.', 'aam-partial-cod' ), ) ); $this->load_template( 'bank-pay-page.php', array( 'order' => $order, 'gateway_id' => $gateway_id, 'icon' => esc_url_raw( $icon ), 'total' => (float) $order->get_meta( BD_PCOD_Helpers::META_ADVANCE ), 'banks' => $banks, 'default_bank' => $default_bank, 'nonce' => $nonce, 'return_url' => $order->get_checkout_order_received_url(), ) ); exit; } $methods = BD_PCOD_Helpers::get_enabled_methods( $gateway_id ); $default_method = isset( $methods['bkash_merchant'] ) ? 'bkash_merchant' : ( $methods ? array_key_first( $methods ) : '' ); $nonce = wp_create_nonce( 'bd_pcod_submit' ); $collect_trxid = BD_PCOD_Helpers::get_setting( $gateway_id, 'collect_trxid', 'off' ); $sender_mode = BD_PCOD_Helpers::get_setting( $gateway_id, 'sender_number_mode', 'full' ); $icon = trim( (string) BD_PCOD_Helpers::get_setting( $gateway_id, 'icon_url', '' ) ); if ( '' === $icon ) { $icon = BD_PCOD_Helpers::default_icon( $gateway_id ); } $this->enqueue_standalone_assets( array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => $nonce, 'copied' => __( 'Copied!', 'aam-partial-cod' ), 'copy' => __( 'Copy', 'aam-partial-cod' ), 'senderMode' => $sender_mode, 'trxidMode' => $collect_trxid, 'invalidPhone' => __( 'Please enter a valid 11-digit mobile number (e.g. 01XXXXXXXXX).', 'aam-partial-cod' ), 'invalidDigits' => __( 'Please enter at least the last 3 digits of your sender number.', 'aam-partial-cod' ), 'requiredTrxid' => __( 'Please enter the transaction ID (TrxID).', 'aam-partial-cod' ), 'required' => __( 'Please fill in all required fields.', 'aam-partial-cod' ), 'leaveWarning' => __( 'You have not completed your payment yet. If you leave now, your order will stay unconfirmed.', 'aam-partial-cod' ), ) ); $context = array( 'order' => $order, 'gateway_id' => $gateway_id, 'icon' => esc_url_raw( $icon ), 'advance' => (float) $order->get_meta( BD_PCOD_Helpers::META_ADVANCE ), 'remaining' => (float) $order->get_meta( BD_PCOD_Helpers::META_REMAINING ), 'methods' => $methods, 'default_method' => $default_method, 'collect_trxid' => $collect_trxid, 'sender_mode' => $sender_mode, 'nonce' => $nonce, 'return_url' => $order->get_checkout_order_received_url(), ); $this->load_template( 'pay-page.php', $context ); exit; } /** * Render a short status/confirmation block on the order-received page. * * The actual payment happens on the gateway (order-pay) page; here we only * confirm the order's current state and, if the advance is still unpaid, * point the customer back to the gateway page. * * @param int $order_id Order ID. */ public function render_thankyou_status( $order_id ) { $order = wc_get_order( $order_id ); if ( ! BD_PCOD_Helpers::is_our_order( $order ) ) { return; } $status = $order->get_meta( BD_PCOD_Helpers::META_STATUS ); $pay_url = BD_PCOD_Helpers::get_pay_url( $order ); $gateway_id = $order->get_payment_method(); echo '
'; if ( BD_PCOD_Helpers::STATUS_VERIFIED === $status ) { echo '
'; echo esc_html( BD_PCOD_Helpers::get_text( $gateway_id, 'status_verified' ) ); echo '
'; } elseif ( in_array( $status, array( BD_PCOD_Helpers::STATUS_SUBMITTED, BD_PCOD_Helpers::STATUS_VERIFIED ), true ) ) { echo '
'; echo esc_html( BD_PCOD_Helpers::get_text( $gateway_id, 'status_submitted' ) ); echo '
'; } else { echo '
'; echo esc_html( BD_PCOD_Helpers::get_text( $gateway_id, 'status_pending' ) ); echo '
'; printf( '

%2$s

', esc_url( $pay_url ), esc_html( BD_PCOD_Helpers::get_text( $gateway_id, 'pay_button' ) ) ); } echo '
'; } /** * Append a short payment reminder to customer emails for awaiting/submitted orders. * * @param WC_Order $order Order object. * @param bool $sent_to_admin Whether the email is for the admin. * @param bool $plain_text Whether the email is plain text. * @param WC_Email $email Email object. */ public function render_email_instructions( $order, $sent_to_admin = false, $plain_text = false, $email = null ) { if ( $sent_to_admin || ! BD_PCOD_Helpers::is_our_order( $order ) ) { return; } $status = $order->get_meta( BD_PCOD_Helpers::META_STATUS ); if ( ! in_array( $status, array( BD_PCOD_Helpers::STATUS_AWAITING, BD_PCOD_Helpers::STATUS_SUBMITTED ), true ) ) { return; } $advance = wc_price( (float) $order->get_meta( BD_PCOD_Helpers::META_ADVANCE ) ); // Awaiting customers go to the standalone gateway page to pay; once // submitted, the order-received page shows their status. $url = ( BD_PCOD_Helpers::STATUS_AWAITING === $status ) ? BD_PCOD_Helpers::get_pay_url( $order ) : $order->get_checkout_order_received_url(); if ( $plain_text ) { $line = sprintf( /* translators: 1: amount, 2: url */ __( 'Please pay %1$s to confirm your order, then submit your payment details here: %2$s', 'aam-partial-cod' ), $advance, $url ); echo "\n" . esc_html( wp_strip_all_tags( $line ) ) . "\n\n"; return; } echo '
'; printf( /* translators: 1: amount, 2: url */ wp_kses_post( __( 'Please pay %1$s to confirm your order, then submit your payment details here.', 'aam-partial-cod' ) ), wp_kses_post( $advance ), esc_url( $url ) ); echo '
'; } /** * Handle the AJAX submission of the customer's sender number + transaction ID. */ public function handle_submit() { check_ajax_referer( 'bd_pcod_submit', 'nonce' ); $order_id = isset( $_POST['order_id'] ) ? absint( wp_unslash( $_POST['order_id'] ) ) : 0; $order_key = isset( $_POST['order_key'] ) ? sanitize_text_field( wp_unslash( $_POST['order_key'] ) ) : ''; $method = isset( $_POST['method'] ) ? sanitize_key( wp_unslash( $_POST['method'] ) ) : ''; $sender = isset( $_POST['sender_number'] ) ? sanitize_text_field( wp_unslash( $_POST['sender_number'] ) ) : ''; $trxid_raw = isset( $_POST['trxid'] ) ? sanitize_text_field( wp_unslash( $_POST['trxid'] ) ) : ''; $order = $order_id ? wc_get_order( $order_id ) : false; // Validate ownership via the order key (the customer may be a guest). if ( ! $order || ! BD_PCOD_Helpers::is_our_order( $order ) || ! hash_equals( $order->get_order_key(), $order_key ) ) { wp_send_json_error( array( 'message' => __( 'Order not found or access denied.', 'aam-partial-cod' ) ), 403 ); } // Idempotency: if already submitted/verified, don't overwrite. $current = $order->get_meta( BD_PCOD_Helpers::META_STATUS ); if ( in_array( $current, array( BD_PCOD_Helpers::STATUS_SUBMITTED, BD_PCOD_Helpers::STATUS_VERIFIED ), true ) ) { wp_send_json_success( array( 'message' => __( 'Your payment details have already been submitted and are awaiting verification.', 'aam-partial-cod' ), 'submitted' => true, 'redirect' => $order->get_checkout_order_received_url(), ) ); } $gateway_id = $order->get_payment_method(); $methods = BD_PCOD_Helpers::get_enabled_methods( $gateway_id ); if ( ! isset( $methods[ $method ] ) ) { wp_send_json_error( array( 'message' => __( 'Please choose a valid payment method.', 'aam-partial-cod' ) ), 400 ); } $sender_mode = BD_PCOD_Helpers::get_setting( $gateway_id, 'sender_number_mode', 'full' ); $sender = BD_PCOD_Helpers::sanitize_sender_number( $sender, $sender_mode ); if ( false === $sender ) { $message = ( 'partial' === $sender_mode ) ? __( 'Please enter at least the last 3 digits of your sender number.', 'aam-partial-cod' ) : __( 'Please enter a valid 11-digit mobile number (e.g. 01XXXXXXXXX).', 'aam-partial-cod' ); wp_send_json_error( array( 'message' => $message ), 400 ); } // Transaction ID: required, optional, or not collected. $collect_trxid = BD_PCOD_Helpers::get_setting( $gateway_id, 'collect_trxid', 'off' ); $trxid = ( 'off' === $collect_trxid ) ? '' : $trxid_raw; if ( 'required' === $collect_trxid && '' === $trxid ) { wp_send_json_error( array( 'message' => __( 'Please enter the transaction ID (TrxID).', 'aam-partial-cod' ) ), 400 ); } // Persist the submission. $order->update_meta_data( BD_PCOD_Helpers::META_METHOD, $method ); $order->update_meta_data( BD_PCOD_Helpers::META_SENDER, $sender ); if ( '' !== $trxid ) { $order->update_meta_data( BD_PCOD_Helpers::META_TRXID, $trxid ); } $order->update_meta_data( BD_PCOD_Helpers::META_STATUS, BD_PCOD_Helpers::STATUS_SUBMITTED ); $order->add_order_note( sprintf( /* translators: 1: method, 2: sender number, 3: amount, 4: trxid suffix */ __( 'Customer submitted payment: %1$s, sender %2$s, amount %3$s%4$s. Awaiting verification.', 'aam-partial-cod' ), BD_PCOD_Helpers::method_label( $method ), $sender, wc_price( (float) $order->get_meta( BD_PCOD_Helpers::META_ADVANCE ) ), '' !== $trxid ? sprintf( /* translators: %s: transaction id */ __( ', TrxID %s', 'aam-partial-cod' ), $trxid ) : '' ) ); // Move the order to on-hold while we verify. $order->update_status( 'on-hold' ); $order->save(); wp_send_json_success( array( 'message' => __( 'Thank you! Your payment details have been submitted and are awaiting verification. We will confirm your order shortly.', 'aam-partial-cod' ), 'submitted' => true, 'redirect' => $order->get_checkout_order_received_url(), ) ); } /** * AJAX: handle the bank transfer confirmation submission. */ public function handle_bank_submit() { check_ajax_referer( 'bd_pcod_bank_submit', 'nonce' ); $order_id = isset( $_POST['order_id'] ) ? absint( wp_unslash( $_POST['order_id'] ) ) : 0; $order_key = isset( $_POST['order_key'] ) ? sanitize_text_field( wp_unslash( $_POST['order_key'] ) ) : ''; $bank = isset( $_POST['bank'] ) ? sanitize_key( wp_unslash( $_POST['bank'] ) ) : ''; $acct_raw = isset( $_POST['account_confirm'] ) ? sanitize_text_field( wp_unslash( $_POST['account_confirm'] ) ) : ''; $order = $order_id ? wc_get_order( $order_id ) : false; if ( ! $order || $order->get_payment_method() !== BD_PCOD_BANK_GATEWAY_ID || ! hash_equals( $order->get_order_key(), $order_key ) ) { wp_send_json_error( array( 'message' => __( 'Order not found or access denied.', 'aam-partial-cod' ) ), 403 ); } $current = $order->get_meta( BD_PCOD_Helpers::META_STATUS ); if ( in_array( $current, array( BD_PCOD_Helpers::STATUS_SUBMITTED, BD_PCOD_Helpers::STATUS_VERIFIED ), true ) ) { wp_send_json_success( array( 'submitted' => true, 'redirect' => $order->get_checkout_order_received_url() ) ); } $banks = BD_PCOD_Helpers::get_enabled_banks(); if ( ! isset( $banks[ $bank ] ) ) { wp_send_json_error( array( 'message' => __( 'Please select a valid bank.', 'aam-partial-cod' ) ), 400 ); } $acct = BD_PCOD_Helpers::sanitize_bank_account( $acct_raw ); if ( false === $acct ) { wp_send_json_error( array( 'message' => __( 'Please enter at least the last 4 digits of your account number.', 'aam-partial-cod' ) ), 400 ); } $order->update_meta_data( BD_PCOD_Helpers::META_METHOD, $bank ); $order->update_meta_data( BD_PCOD_Helpers::META_SENDER, $acct ); $order->update_meta_data( BD_PCOD_Helpers::META_STATUS, BD_PCOD_Helpers::STATUS_SUBMITTED ); $order->add_order_note( sprintf( /* translators: 1: bank name, 2: account digits, 3: amount */ __( 'Customer submitted bank transfer: %1$s, account ending %2$s, amount %3$s. Awaiting verification.', 'aam-partial-cod' ), $banks[ $bank ]['name'], $acct, wc_price( (float) $order->get_meta( BD_PCOD_Helpers::META_ADVANCE ) ) ) ); $order->update_status( 'on-hold' ); $order->save(); wp_send_json_success( array( 'message' => __( 'Thank you! Your transfer details have been submitted and are awaiting verification.', 'aam-partial-cod' ), 'submitted' => true, 'redirect' => $order->get_checkout_order_received_url(), ) ); } /** * Register and enqueue the standalone page's CSS/JS so the templates can emit * them via wp_print_styles()/wp_print_scripts() instead of hard-coded tags. * * The script's runtime config (formerly a raw window.bdPcod