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