From 10821319b73d5773754bdf7d5d27f899c981e0d6 Mon Sep 17 00:00:00 2001 From: xecdev Date: Tue, 16 Dec 2025 16:37:40 +0430 Subject: [PATCH 01/17] Need refactoring for WC --- includes/class-paybutton-ajax.php | 289 +++++----------------- includes/class-paybutton-transactions.php | 270 ++++++++++++++++++++ paybutton.php | 1 + 3 files changed, 327 insertions(+), 233 deletions(-) create mode 100644 includes/class-paybutton-transactions.php diff --git a/includes/class-paybutton-ajax.php b/includes/class-paybutton-ajax.php index 8919851..583a1b9 100644 --- a/includes/class-paybutton-ajax.php +++ b/includes/class-paybutton-ajax.php @@ -119,7 +119,12 @@ public function payment_trigger() { } // Verify the signature - $verification_result = $this->verify_signature($payload, $signature, $public_key); + $verification_result = PayButton_Transactions::verify_signature( + $payload, + $signature, + $public_key + ); + if (!$verification_result) { wp_send_json_error(['message' => 'Signature verification failed.']); return; @@ -129,7 +134,7 @@ public function payment_trigger() { //Sanitize data $post_id = intval( $post_id_raw ); $tx_hash = sanitize_text_field( $tx_hash_raw ); - $tx_amount = sanitize_text_field( $tx_amount_raw ); + $tx_amount = (float) $tx_amount_raw; $tx_timestamp = intval( $ts_raw ); $user_address = sanitize_text_field( $user_addr_raw ); $currency = sanitize_text_field( $currency_raw ); @@ -147,34 +152,12 @@ public function payment_trigger() { return; } - global $wpdb; - $login_table = $wpdb->prefix . 'paybutton_logins'; - - // Idempotency: avoid dupes on replays - $exists = $wpdb->get_var( $wpdb->prepare( - "SELECT id FROM {$login_table} WHERE wallet_address = %s AND tx_hash = %s LIMIT 1", - $user_address, $tx_hash - ) ); - //error_log('[paybutton] login-branch addr=' . $user_address . ' tx=' . $tx_hash . ' ts=' . $tx_timestamp); - if ( ! $exists ) { - $wpdb->insert( - $login_table, - array( - 'wallet_address' => $user_address, - 'tx_hash' => $tx_hash, - 'tx_amount' => (float) $tx_amount, - 'tx_timestamp' => (int) $tx_timestamp, - 'used' => 0, - ), - array('%s','%s','%f','%d','%d') - ); - } - - // if ($wpdb->last_error) { - // error_log('[paybutton] insert error: ' . $wpdb->last_error); - // } else { - // error_log('[paybutton] insert ok id=' . $wpdb->insert_id); - // } + PayButton_Transactions::record_login_tx_if_new( + $user_address, + $tx_hash, + (float) $tx_amount, + (int) $tx_timestamp + ); wp_send_json_success(['message' => 'Login tx recorded']); return; @@ -204,7 +187,7 @@ public function payment_trigger() { } // Get expected price and unit by parsing shortcode in the post content - $required = $this->paybutton_get_paywall_requirements( $post_id ); + $required = PayButton_Transactions::get_paywall_requirements( $post_id ); if ( $required === null ) { wp_send_json_error( array( 'message' => 'Post not configured for paywall.' ), 400 ); return; @@ -212,28 +195,29 @@ public function payment_trigger() { $expected_price = floatval( $required['price'] ); $expected_unit = strtoupper( $required['unit'] ); - - - // Numeric amount check - $paid_amount = floatval( $tx_amount ); - $epsilon = 0.05; // tolerance for rounding differences - if ( $paid_amount + $epsilon < $expected_price ) { - wp_send_json_error( array( 'message' => 'Underpaid transaction ignored.' ), 400 ); - return; - } - - if ( $incoming_unit !== $expected_unit ) { - wp_send_json_error( array( 'message' => 'Currency/unit mismatch.' ), 400 ); + + if ( + ! PayButton_Transactions::validate_price_and_unit( + (float) $tx_amount, + $incoming_unit, + $expected_price, + $expected_unit + ) + ) { + wp_send_json_error( + array( 'message' => 'Invalid payment amount or currency.' ), + 400 + ); return; } // Passed validation -> store unlock in DB $is_logged_in = 0; - $this->store_unlock_in_db( + PayButton_Transactions::insert_unlock_if_new( sanitize_text_field( $user_address ), $post_id, sanitize_text_field( $tx_hash ), - floatval( $tx_amount ), + (float) $tx_amount, $mysql_timestamp, $is_logged_in ); @@ -241,92 +225,6 @@ public function payment_trigger() { wp_send_json_success(); } - // Verify the signature using the public key - private function verify_signature($payload, $signature, $public_key_hex) { - // Convert hex signature to binary - $binary_signature = hex2bin($signature); - if (!$binary_signature) { - return false; - } - - // Convert hex public key to binary - $binary_public_key = hex2bin($public_key_hex); - if (!$binary_public_key) { - return false; - } - - // If the public key is in DER format (44 bytes), extract the raw 32-byte key. - if (strlen($binary_public_key) === 44) { - $raw_public_key = substr($binary_public_key, 12); - } else { - $raw_public_key = $binary_public_key; - } - - // Ensure payload is in exact binary format - $binary_payload = mb_convert_encoding($payload, 'ISO-8859-1', 'UTF-8'); - - // Verify signature using Sodium (Ed25519) - $verification = sodium_crypto_sign_verify_detached($binary_signature, $binary_payload, $raw_public_key); - - if ($verification) { - return true; - } else { - return false; - } - } - - /** - * Get expected paywall price and unit for a post/page by parsing its - * first [paywalled_content] shortcode. - * - * @param int $post_id - * @return array|null Array( 'price' => float, 'unit' => string ) or null if not paywalled. - */ - private function paybutton_get_paywall_requirements( $post_id ) { - $post_id = absint( $post_id ); - if ( ! $post_id ) { - return null; - } - - $post = get_post( $post_id ); - if ( ! $post || ! isset( $post->post_content ) ) { - return null; - } - - $content = $post->post_content; - - // Capture the first [paywalled_content ...] opening tag attributes - if ( preg_match( '/\[paywalled_content([^\]]*)\]/i', $content, $matches ) ) { - $atts_raw = isset( $matches[1] ) ? $matches[1] : ''; - $atts = shortcode_parse_atts( $atts_raw ); - - $price = null; - $unit = ''; - - if ( isset( $atts['price'] ) && $atts['price'] !== '' ) { - $price = floatval( trim( $atts['price'] ) ); - } - - if ( isset( $atts['unit'] ) && $atts['unit'] !== '' ) { - $unit = strtoupper( sanitize_text_field( trim( $atts['unit'] ) ) ); - } - - // Fallbacks to plugin options - if ( $price === null || $price === 0.0 ) { - $price = floatval( get_option( 'paybutton_paywall_default_price', 5.5 ) ); - } - if ( $unit === '' ) { - $unit = strtoupper( sanitize_text_field( get_option( 'paybutton_paywall_unit', 'XEC' ) ) ); - } - - return array( - 'price' => $price, - 'unit' => $unit, - ); - } - - return null; - } /** * The following function sets the user's wallet address in a cookie via AJAX after * a successful login transaction. @@ -392,11 +290,11 @@ public function mark_payment_successful() { $post_id = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0; $tx_hash = isset( $_POST['tx_hash'] ) ? sanitize_text_field( $_POST['tx_hash'] ) : ''; - $tx_amount = isset( $_POST['tx_amount'] ) ? sanitize_text_field( $_POST['tx_amount'] ) : ''; + $tx_amount = isset( $_POST['tx_amount'] ) ? (float) $_POST['tx_amount'] : 0.0; $tx_timestamp = isset( $_POST['tx_timestamp'] ) ? sanitize_text_field( $_POST['tx_timestamp'] ) : ''; // NEW: Address passed from front-end if user is not logged in $user_address = isset( $_POST['user_address'] ) ? sanitize_text_field( $_POST['user_address'] ) : ''; - $unlock_token = isset( $_POST['unlock_token'] ) ? sanitize_text_field( $_POST['unlock_token'] ) : ''; + $unlock_token = isset( $_POST['unlock_token'] ) ? sanitize_text_field( $_POST['unlock_token'] ) : ''; if ( $post_id <= 0 || empty( $tx_hash ) || empty( $user_address ) || empty( $unlock_token ) ) { wp_send_json_error( array( 'message' => 'Missing required payment fields.' ), 400 ); @@ -561,38 +459,6 @@ private function extract_shortcode_inner_content( $post_content ) { return $inner; } - /** - * Store the unlock information in the database. - */ - private function store_unlock_in_db( $address, $post_id, $tx_hash, $tx_amount, $tx_dt, $is_logged_in ) { - global $wpdb; - $table_name = $wpdb->prefix . 'paybutton_paywall_unlocked'; - - // Check if the transaction already exists using tx hash - $exists = $wpdb->get_var($wpdb->prepare( - "SELECT id FROM $table_name WHERE tx_hash = %s LIMIT 1", - $tx_hash - )); - - if ($exists) { - return; // Transaction already recorded, so we don't insert again. - } - - // Insert the transaction if it's not already recorded - $wpdb->insert( - $table_name, - array( - 'pb_paywall_user_wallet_address' => $address, - 'post_id' => $post_id, - 'tx_hash' => $tx_hash, - 'tx_amount' => $tx_amount, - 'tx_timestamp' => $tx_dt, - 'is_logged_in' => $is_logged_in, - ), - array( '%s', '%d', '%s', '%f', '%s', '%d' ) - ); - } - /** * AJAX endpoint to validate a login transaction. * This checks that the provided wallet address and tx hash correspond to @@ -610,41 +476,21 @@ public function ajax_validate_login_tx() { } global $wpdb; - $table = $wpdb->prefix . 'paybutton_logins'; - // Only accept unused login tx rows - $row = $wpdb->get_row($wpdb->prepare( - "SELECT id FROM {$table} - WHERE wallet_address = %s AND tx_hash = %s AND used = 0 - ORDER BY id DESC LIMIT 1", - $wallet_address, $tx_hash - )); - - if (!$row) { - wp_send_json_error('Login validation failed'); // no match or already used - } - - // Generate a random, unguessable token like "9fx0..._..." so that malicious actors - // can't fake login attempts by reusing the same wallet address + tx hash using fake - // AJAX calls from the browser. - $raw = random_bytes(18); // 18 bytes → ~24 chars base64url - $token = rtrim(strtr(base64_encode($raw), '+/', '-_'), '='); - - // Mark as used + attach token - $wpdb->update( - $table, - array( - 'used' => 1, - 'login_token' => $token, - ), - array('id' => (int)$row->id), - array('%d','%s'), - array('%d') + $token = PayButton_Transactions::consume_row_and_attach_token( + $wpdb->prefix . 'paybutton_logins', + [ + 'wallet_address' => $wallet_address, + 'tx_hash' => $tx_hash, + ], + 'login_token' ); - wp_send_json_success(array( - 'login_token' => $token, - )); + if (!$token) { + wp_send_json_error('Login validation failed'); + } + + wp_send_json_success(['login_token' => $token]); } /** @@ -665,45 +511,22 @@ public function ajax_validate_unlock_tx() { } global $wpdb; - $table = $wpdb->prefix . 'paybutton_paywall_unlocked'; - - // Only accept unused unlock rows matching this wallet + tx + post - $row = $wpdb->get_row($wpdb->prepare( - "SELECT id FROM {$table} - WHERE pb_paywall_user_wallet_address = %s - AND tx_hash = %s - AND post_id = %d - AND used = 0 - ORDER BY id DESC - LIMIT 1", - $wallet_address, - $tx_hash, - $post_id - )); - if (!$row) { - wp_send_json_error('Unlock validation failed'); // no match or already used - } - - // Generate a random, unguessable token - $raw = random_bytes(18); // ~24 chars base64url - $token = rtrim(strtr(base64_encode($raw), '+/', '-_'), '='); - - // Mark row as used + attach unlock token - $wpdb->update( - $table, - array( - 'used' => 1, - 'unlock_token' => $token, - ), - array( 'id' => (int) $row->id ), - array( '%d', '%s' ), - array( '%d' ) + $token = PayButton_Transactions::consume_row_and_attach_token( + $wpdb->prefix . 'paybutton_paywall_unlocked', + [ + 'pb_paywall_user_wallet_address' => $wallet_address, + 'tx_hash' => $tx_hash, + 'post_id' => $post_id, + ], + 'unlock_token' ); - wp_send_json_success(array( - 'unlock_token' => $token, - )); + if (!$token) { + wp_send_json_error('Unlock validation failed'); + } + + wp_send_json_success(['unlock_token' => $token]); } /** diff --git a/includes/class-paybutton-transactions.php b/includes/class-paybutton-transactions.php new file mode 100644 index 0000000..ced6129 --- /dev/null +++ b/includes/class-paybutton-transactions.php @@ -0,0 +1,270 @@ + $value) { + $conditions[] = "{$column} = %s"; + $values[] = $value; + } + + $sql = " + SELECT id FROM {$table} + WHERE " . implode(' AND ', $conditions) . " + AND used = 0 + ORDER BY id DESC + LIMIT 1 + "; + + $row = $wpdb->get_row($wpdb->prepare($sql, ...$values)); + + if (!$row) { + return null; + } + + $token = self::generate_secure_token(); + + $wpdb->update( + $table, + [ + 'used' => 1, + $token_column => $token, + ], + ['id' => (int) $row->id], + ['%d', '%s'], + ['%d'] + ); + + return $token; + } + + /* ============================================================ + * Price & currency validation + * ============================================================ + */ + + public static function validate_price_and_unit( + float $paid_amount, + string $paid_unit, + float $expected_price, + string $expected_unit + ): bool { + + $epsilon = 0.05; + + if ($paid_amount + $epsilon < $expected_price) { + return false; + } + + if (strtoupper($paid_unit) !== strtoupper($expected_unit)) { + return false; + } + + return true; + } + + /* ============================================================ + * Store the unlock information in the database. + * ============================================================ + */ + public static function insert_unlock_if_new( + string $address, + int $post_id, + string $tx_hash, + float $tx_amount, + string $tx_dt, + int $is_logged_in + ): void { + + global $wpdb; + $table_name = $wpdb->prefix . 'paybutton_paywall_unlocked'; + + // Check if the transaction already exists using tx hash + $exists = $wpdb->get_var($wpdb->prepare( + "SELECT id FROM $table_name WHERE tx_hash = %s LIMIT 1", + $tx_hash + )); + + if ($exists) { + return; // Transaction already recorded, so we don't insert again. + } + + // Insert the transaction if it's not already recorded + $wpdb->insert( + $table_name, + array( + 'pb_paywall_user_wallet_address' => $address, + 'post_id' => $post_id, + 'tx_hash' => $tx_hash, + 'tx_amount' => $tx_amount, + 'tx_timestamp' => $tx_dt, + 'is_logged_in' => $is_logged_in, + ), + array( '%s', '%d', '%s', '%f', '%s', '%d' ) + ); + } + + /* ============================================================ + * Store the login tx information in the database. + * ============================================================ + */ + + public static function record_login_tx_if_new( + string $wallet_address, + string $tx_hash, + float $tx_amount, + int $tx_timestamp + ): void { + + global $wpdb; + $table = $wpdb->prefix . 'paybutton_logins'; + + $exists = $wpdb->get_var( + $wpdb->prepare( + "SELECT id FROM {$table} WHERE wallet_address = %s AND tx_hash = %s LIMIT 1", + $wallet_address, + $tx_hash + ) + ); + + if ($exists) { + return; + } + + $wpdb->insert( + $table, + [ + 'wallet_address' => $wallet_address, + 'tx_hash' => $tx_hash, + 'tx_amount' => $tx_amount, + 'tx_timestamp' => $tx_timestamp, + 'used' => 0, + ], + ['%s','%s','%f','%d','%d'] + ); + } + + + /** + * Get expected paywall price and unit for a post/page by parsing its + * first [paywalled_content] shortcode. + * + * @param int $post_id + * @return array|null Array( 'price' => float, 'unit' => string ) or null if not paywalled. + */ + public static function get_paywall_requirements( int $post_id ): ?array { + + $post_id = absint( $post_id ); + if ( ! $post_id ) { + return null; + } + + $post = get_post( $post_id ); + if ( ! $post || ! isset( $post->post_content ) ) { + return null; + } + + $content = $post->post_content; + + // Capture the first [paywalled_content ...] opening tag attributes + if ( preg_match( '/\[paywalled_content([^\]]*)\]/i', $content, $matches ) ) { + $atts_raw = isset( $matches[1] ) ? $matches[1] : ''; + $atts = shortcode_parse_atts( $atts_raw ); + + $price = null; + $unit = ''; + + if ( isset( $atts['price'] ) && $atts['price'] !== '' ) { + $price = floatval( trim( $atts['price'] ) ); + } + + if ( isset( $atts['unit'] ) && $atts['unit'] !== '' ) { + $unit = strtoupper( sanitize_text_field( trim( $atts['unit'] ) ) ); + } + + // Fallbacks to plugin options + if ( $price === null || $price === 0.0 ) { + $price = floatval( get_option( 'paybutton_paywall_default_price', 5.5 ) ); + } + if ( $unit === '' ) { + $unit = strtoupper( sanitize_text_field( get_option( 'paybutton_paywall_unit', 'XEC' ) ) ); + } + + return array( + 'price' => $price, + 'unit' => $unit, + ); + } + + return null; + } +} \ No newline at end of file diff --git a/paybutton.php b/paybutton.php index b8cb314..cb0d4b9 100644 --- a/paybutton.php +++ b/paybutton.php @@ -29,6 +29,7 @@ require_once PAYBUTTON_PLUGIN_DIR . 'includes/class-paybutton-deactivator.php'; require_once PAYBUTTON_PLUGIN_DIR . 'includes/class-paybutton-admin.php'; require_once PAYBUTTON_PLUGIN_DIR . 'includes/class-paybutton-public.php'; +require_once PAYBUTTON_PLUGIN_DIR . 'includes/class-paybutton-transactions.php'; require_once PAYBUTTON_PLUGIN_DIR . 'includes/class-paybutton-ajax.php'; require_once PAYBUTTON_PLUGIN_DIR . 'includes/class-paybutton-state.php'; From c31505cec9343ac41513510f52f0b15a72331b31 Mon Sep 17 00:00:00 2001 From: xecdev Date: Wed, 17 Dec 2025 14:03:44 +0430 Subject: [PATCH 02/17] Initial Woo + PayButton integration with blocks support --- assets/js/paybutton-blocks.js | 54 +++++ assets/js/paybutton-woo.js | 57 ++++++ includes/class-paybutton-ajax.php | 84 ++++++++ .../class-paybutton-blocks-support.php | 62 ++++++ .../class-wc-gateway-paybutton.php | 186 ++++++++++++++++++ paybutton.php | 36 ++++ 6 files changed, 479 insertions(+) create mode 100644 assets/js/paybutton-blocks.js create mode 100644 assets/js/paybutton-woo.js create mode 100644 includes/woocommerce/class-paybutton-blocks-support.php create mode 100644 includes/woocommerce/class-wc-gateway-paybutton.php diff --git a/assets/js/paybutton-blocks.js b/assets/js/paybutton-blocks.js new file mode 100644 index 0000000..69bf08f --- /dev/null +++ b/assets/js/paybutton-blocks.js @@ -0,0 +1,54 @@ +/** + * WooCommerce PayButton Blocks Integration JS +*/ +(function( wc, wp ) { + const { registerPaymentMethod } = wc.wcBlocksRegistry; + const { getSetting } = wc.wcSettings; + const { decodeEntities } = wp.htmlEntities; + const { createElement } = wp.element; + + const settings = getSetting( 'paybutton_data', {} ); + + const labelText = decodeEntities( settings.title || 'PayButton' ); + + // Create a Custom Label Component (Image ONLY) + const LabelIconOnly = () => { + return createElement( + 'span', + { + style: { + display: 'flex', + alignItems: 'center', + width: '100%' + } + }, + // 1. The Image + settings.icon ? createElement( 'img', { + src: settings.icon, + alt: labelText, + style: { + maxHeight: '30px', // Slightly larger since it stands alone + objectFit: 'contain' + } + } ) + // 2. Fallback: If no icon is found, show text so the button isn't invisible + : createElement( 'span', null, labelText ) + ); + }; + + const Content = () => { + return createElement( 'div', null, decodeEntities( settings.description || '' ) ); + }; + + registerPaymentMethod( { + name: 'paybutton', + label: createElement( LabelIconOnly ), + content: createElement( Content ), + edit: createElement( Content ), + canMakePayment: () => true, + ariaLabel: labelText, + supports: { + features: settings.supports, + }, + } ); +})( window.wc, window.wp ); \ No newline at end of file diff --git a/assets/js/paybutton-woo.js b/assets/js/paybutton-woo.js new file mode 100644 index 0000000..d8087a2 --- /dev/null +++ b/assets/js/paybutton-woo.js @@ -0,0 +1,57 @@ +/** + * WooCommerce PayButton Integration JS +*/ +jQuery(document).ready(function($) { + $('.paybutton-woo-container').each(function() { + var $container = $(this); + var configData = $container.data('config'); + + if (typeof configData === 'string') { + try { configData = JSON.parse(configData); } + catch (e) { return; } + } + + let paymentInitiated = false; + + function showOverlay(msg) { + const el = document.getElementById('paybutton_overlay'); + if (el) { + document.getElementById('paybutton_overlay_text').innerText = msg; + el.style.display = 'block'; + } + } + + function pollOrderStatus() { + setInterval(function() { + $.ajax({ + url: PaywallAjax.ajaxUrl, + method: 'POST', + data: { + action: 'paybutton_check_order_status', + security: PaywallAjax.nonce, + order_id: configData.opReturn + }, + success: function(response) { + if (response.success) { + // Success! Reload to hide button and show receipt + location.reload(); + } + } + }); + }, 3000); + } + + PayButton.render($container[0], { + ...configData, + onSuccess: function(tx) { + paymentInitiated = true; + pollOrderStatus(); + }, + onClose: function() { + if (paymentInitiated) { + showOverlay("Verifying Payment..."); + } + } + }); + }); +}); \ No newline at end of file diff --git a/includes/class-paybutton-ajax.php b/includes/class-paybutton-ajax.php index 583a1b9..fdb884b 100644 --- a/includes/class-paybutton-ajax.php +++ b/includes/class-paybutton-ajax.php @@ -54,6 +54,10 @@ public function __construct() { // AJAX endpoint to get sticky header HTML for auto-login after content unlock add_action( 'wp_ajax_paybutton_get_sticky_header', array( $this, 'get_sticky_header' ) ); add_action( 'wp_ajax_nopriv_paybutton_get_sticky_header', array( $this, 'get_sticky_header' ) ); + + // WooCommerce Order Status Polling + add_action( 'wp_ajax_paybutton_check_order_status', array( $this, 'check_order_status' ) ); + add_action( 'wp_ajax_nopriv_paybutton_check_order_status', array( $this, 'check_order_status' ) ); } /** * Payment Trigger Handler with Cryptographic Verification @@ -103,6 +107,7 @@ public function payment_trigger() { $ts_raw = $json['tx_timestamp'] ?? 0; $user_addr_raw = $json['user_address'][0]['address'] ?? ($json['user_address'][0] ?? ''); $currency_raw = $json['currency'] ?? ''; + $fiatValue = $json['value'] ?? 0; unset( $json ); // discard the rest immediately @@ -131,6 +136,26 @@ public function payment_trigger() { } // error_log('[paybutton] signature ok'); + // Ensure OP_RETURN (rawMessage) is cryptographically bound to the payload + $op_return = (string) $post_id_raw; + $op_return = trim($op_return); + + if ( $op_return === '' || strpos( $payload, $op_return ) === false ) { + wp_send_json_error( + [ 'message' => 'Payload does not match OP_RETURN.' ], + 400 + ); + return; + } + + if ( $tx_hash_raw && strpos( $payload, $tx_hash_raw ) === false ) { + wp_send_json_error( + [ 'message' => 'Payload does not match tx_hash.' ], + 400 + ); + return; + } + //Sanitize data $post_id = intval( $post_id_raw ); $tx_hash = sanitize_text_field( $tx_hash_raw ); @@ -162,6 +187,42 @@ public function payment_trigger() { wp_send_json_success(['message' => 'Login tx recorded']); return; } + + // --- BRANCH 2: WOOCOMMERCE PAYMENTS --- + if ( class_exists( 'WooCommerce' ) ) { + $order = wc_get_order( $post_id ); + + if ( $order && $order instanceof WC_Order ) { + + // 1. Check if already paid + if ( $order->is_paid() ) { + wp_send_json_success( array( 'message' => 'Order already paid' ) ); + return; + } + + // 2. Validate Amount using Fiat Value (USD) from Webhook + // We compare the webhook's USD value ($fiatValue) against the Order Total. + + $expected_fiat = (float) $order->get_total(); + // Allow small epsilon for floating point math + if ( $fiatValue < ( $expected_fiat - 0.05 ) ) { + // Add note about underpayment but don't mark complete yet + $order->add_order_note( sprintf( 'Underpayment detected. Expected $%s but received $%s worth of crypto. Tx: %s', $expected_fiat, $fiatValue, $tx_hash ) ); + wp_send_json_error( array( 'message' => 'Insufficient fiat value.' ), 400 ); + return; + } + + // 3. Mark as Paid + $order->payment_complete( $tx_hash ); + + // 4. Add informative note + $note = sprintf( 'PayButton Payment Received via Webhook. Value: $%s. Tx Hash: %s', $fiatValue, $tx_hash ); + $order->add_order_note( $note ); + + wp_send_json_success( array( 'message' => 'WooCommerce Order Updated' ) ); + return; // Stop processing + } + } // Convert timestamp to MySQL datetime $mysql_timestamp = $tx_timestamp ? gmdate('Y-m-d H:i:s', $tx_timestamp) : '0000-00-00 00:00:00'; @@ -551,4 +612,27 @@ public function get_sticky_header() { 'html' => $html, ) ); } + + /** + * Polls to see if a WooCommerce order has been paid. + */ + public function check_order_status() { + check_ajax_referer( 'paybutton_paywall_nonce', 'security' ); + + if ( ! class_exists( 'WooCommerce' ) ) wp_send_json_error(); + + $order_id = isset( $_POST['order_id'] ) ? intval( $_POST['order_id'] ) : 0; + $order = wc_get_order( $order_id ); + + if ( $order && $order->is_paid() ) { + wp_send_json_success(); + } + + // Also check if status is processing (for manual verification workflows) + if ( $order && ( $order->has_status( 'processing' ) || $order->has_status( 'completed' ) ) ) { + wp_send_json_success(); + } + + wp_send_json_error(); + } } \ No newline at end of file diff --git a/includes/woocommerce/class-paybutton-blocks-support.php b/includes/woocommerce/class-paybutton-blocks-support.php new file mode 100644 index 0000000..e629bce --- /dev/null +++ b/includes/woocommerce/class-paybutton-blocks-support.php @@ -0,0 +1,62 @@ +payment_gateways->payment_gateways(); + + if ( isset( $gateways[ $this->name ] ) ) { + return $gateways[ $this->name ]->is_available(); + } + + return false; + } + + /** + * Register the payment method script handles. + */ + public function get_payment_method_script_handles() { + wp_register_script( + 'wc-paybutton-blocks', + PAYBUTTON_PLUGIN_URL . 'assets/js/paybutton-blocks.js', + array( 'wc-blocks-registry', 'wc-settings', 'wp-element', 'wp-html-entities' ), + '1.0.0', + true + ); + + return array( 'wc-paybutton-blocks' ); + } + + /** + * Get payment method data for the blocks checkout. + */ + public function get_payment_method_data() { + $gateways = WC()->payment_gateways->payment_gateways(); + $gateway = isset( $gateways[ $this->name ] ) ? $gateways[ $this->name ] : null; + + return array( + 'title' => $gateway ? $gateway->get_title() : 'PayButton', + 'description' => $gateway ? $gateway->get_description() : '', + 'icon' => PAYBUTTON_PLUGIN_URL . 'assets/paybutton-logo.png', + 'supports' => array( 'products' ), + ); + } +} \ No newline at end of file diff --git a/includes/woocommerce/class-wc-gateway-paybutton.php b/includes/woocommerce/class-wc-gateway-paybutton.php new file mode 100644 index 0000000..bbe5dd2 --- /dev/null +++ b/includes/woocommerce/class-wc-gateway-paybutton.php @@ -0,0 +1,186 @@ +id = 'paybutton'; + $this->icon = PAYBUTTON_PLUGIN_URL . 'assets/icon-128x128.jpg'; + $this->has_fields = false; + $this->method_title = 'PayButton'; + $this->method_description = 'Accept eCash (XEC) payments directly via PayButton.'; + + // Load settings + $this->init_form_fields(); + $this->init_settings(); + + $this->title = $this->get_option( 'title' ); + $this->description = $this->get_option( 'description' ); + + // Admin Options Save Action + add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) ); + + // Inject PayButton on the Thank You page + add_action( 'woocommerce_thankyou_' . $this->id, array( $this, 'thankyou_page' ) ); + } + + /** + * Initialize Gateway Settings Form Fields + */ + public function init_form_fields() { + $this->form_fields = array( + 'enabled' => array( + 'title' => 'Enable/Disable', + 'type' => 'checkbox', + 'label' => 'Enable PayButton Payment', + 'default' => 'no' + ), + 'title' => array( + 'title' => 'Title', + 'type' => 'text', + 'default' => 'Pay with eCash (XEC)', + 'desc_tip' => true, + ), + 'description' => array( + 'title' => 'Description', + 'type' => 'textarea', + 'default' => 'Pay securely using your eCash wallet.', + ), + 'address' => array( + 'title' => 'Wallet Address', + 'type' => 'text', + 'description' => 'The eCash wallet address where you want to receive payments.', + 'default' => '', + 'desc_tip' => true, + 'placeholder' => 'ecash:qr...', + ), + ); + } + + /** + * Overridden to prevent activation if address is missing. + */ + public function process_admin_options() { + + // Is the admin trying to enable the gateway? + $is_enabling = isset( $_POST['woocommerce_paybutton_enabled'] ); + + // Gateway-specific wallet address ONLY + $posted_address = isset( $_POST['woocommerce_paybutton_address'] ) + ? sanitize_text_field( $_POST['woocommerce_paybutton_address'] ) + : ''; + + // Block enabling if address is missing + if ( $is_enabling && empty( $posted_address ) ) { + + WC_Admin_Settings::add_error( + __( 'PayButton Error: You must enter a wallet address to use this payment method.', 'paybutton' ) + ); + + // Force-disable the gateway + unset( $_POST['woocommerce_paybutton_enabled'] ); + } + + parent::process_admin_options(); + } + + /** + * Process the payment and return the result. + */ + public function process_payment( $order_id ) { + $order = wc_get_order( $order_id ); + + $order->update_status( 'on-hold', __( 'Awaiting PayButton payment.', 'paybutton' ) ); + $order->reduce_order_stock(); + WC()->cart->empty_cart(); + + return array( + 'result' => 'success', + 'redirect' => $this->get_return_url( $order ) + ); + } + + /** + * Output for the order received page. + */ + public function thankyou_page( $order_id ) { + $order = wc_get_order( $order_id ); + if ( ! $order ) return; + + if ( $order->is_paid() ) { + return; + } + + // Get Address (Gateway Setting -> Global Fallback) + $address = $this->get_option( 'address' ); + + if ( empty( $address ) ) { + return; + } + + if ( empty( $address ) ) { + // Should logically not happen if we block activation, but good as a failsafe + return; + } + + $amount_formatted = $order->get_formatted_order_total(); + $pay_text = 'Pay ' . strip_tags( $amount_formatted ); + + $config = array( + 'to' => $address, + 'amount' => (float) $order->get_total(), + 'currency' => $order->get_currency(), + 'text' => $pay_text, + 'hoverText' => 'Click to Pay', + 'opReturn' => (string) $order_id, + 'successText' => 'Payment Received! Processing...', + 'autoClose' => true + ); + + wp_enqueue_script( + 'paybutton-woo', + PAYBUTTON_PLUGIN_URL . 'assets/js/paybutton-woo.js', + array( 'jquery', 'paybutton-core' ), + '1.0', + true + ); + + wp_localize_script( 'paybutton-woo', 'PaywallAjax', array( + 'ajaxUrl' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'paybutton_paywall_nonce' ) + )); + + echo '

Complete your payment

'; + echo '
'; + } + + /** + * Check if the gateway is available for use. + */ + public function is_available() { + + if ( 'yes' !== $this->get_option( 'enabled' ) ) { + return false; + } + + $address = $this->get_option( 'address' ); + + if ( empty( $address ) ) { + return false; + } + + return parent::is_available(); + } + } +} \ No newline at end of file diff --git a/paybutton.php b/paybutton.php index cb0d4b9..d9ddc60 100644 --- a/paybutton.php +++ b/paybutton.php @@ -33,6 +33,12 @@ require_once PAYBUTTON_PLUGIN_DIR . 'includes/class-paybutton-ajax.php'; require_once PAYBUTTON_PLUGIN_DIR . 'includes/class-paybutton-state.php'; +// --- NEW: Include WooCommerce Gateway --- +// We load this file, but the class inside it initiates itself safely on 'plugins_loaded' +if ( file_exists( PAYBUTTON_PLUGIN_DIR . 'includes/woocommerce/class-wc-gateway-paybutton.php' ) ) { + require_once PAYBUTTON_PLUGIN_DIR . 'includes/woocommerce/class-wc-gateway-paybutton.php'; +} + /** * Registers the plugin's activation and deactivation hooks. * @@ -62,6 +68,36 @@ new PayButton_AJAX(); }, 1); // Use a priority to ensure this runs before other actions that might depend on session data. +// 1. Register the Gateway +add_filter( 'woocommerce_payment_gateways', 'add_paybutton_gateway_class' ); + +function add_paybutton_gateway_class( $gateways ) { + if ( class_exists( 'WC_Gateway_PayButton' ) ) { + $gateways[] = 'WC_Gateway_PayButton'; + } + return $gateways; +} + +// 2. Register Gutenberg Block Support +add_action( 'woocommerce_blocks_payment_method_type_registration', 'register_paybutton_blocks_support' ); + +function register_paybutton_blocks_support( \Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry $payment_method_registry ) { + // Ensure WooCommerce Blocks class exists + if ( ! class_exists( 'Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType' ) ) { + return; + } + + $block_support_file = PAYBUTTON_PLUGIN_DIR . 'includes/woocommerce/class-paybutton-blocks-support.php'; + + if ( file_exists( $block_support_file ) ) { + require_once $block_support_file; + + if ( class_exists( 'WC_PayButton_Blocks_Support' ) ) { + $payment_method_registry->register( new WC_PayButton_Blocks_Support() ); + } + } +} + add_action('admin_init', function() { if (get_option('paybutton_activation_redirect', false)) { delete_option('paybutton_activation_redirect'); From f82853ef65c3755575ca2ecac0e2c3832efa1df9 Mon Sep 17 00:00:00 2001 From: xecdev Date: Wed, 17 Dec 2025 16:39:16 +0430 Subject: [PATCH 03/17] Add 'Payment Confirmed' message --- includes/woocommerce/class-wc-gateway-paybutton.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/includes/woocommerce/class-wc-gateway-paybutton.php b/includes/woocommerce/class-wc-gateway-paybutton.php index bbe5dd2..db8e865 100644 --- a/includes/woocommerce/class-wc-gateway-paybutton.php +++ b/includes/woocommerce/class-wc-gateway-paybutton.php @@ -119,7 +119,11 @@ public function thankyou_page( $order_id ) { if ( ! $order ) return; if ( $order->is_paid() ) { - return; + echo '
'; + echo 'Payment confirmed!
'; + echo 'Your payment has been received and your order is now being processed.'; + echo '
'; + return; } // Get Address (Gateway Setting -> Global Fallback) From 591403e38150d438396d99589de712b16e93de6f Mon Sep 17 00:00:00 2001 From: xecdev Date: Wed, 17 Dec 2025 19:45:22 +0430 Subject: [PATCH 04/17] Add HPOS Compatibilty for WC --- includes/woocommerce/class-wc-gateway-paybutton.php | 4 ---- paybutton.php | 8 ++++++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/includes/woocommerce/class-wc-gateway-paybutton.php b/includes/woocommerce/class-wc-gateway-paybutton.php index db8e865..5218e56 100644 --- a/includes/woocommerce/class-wc-gateway-paybutton.php +++ b/includes/woocommerce/class-wc-gateway-paybutton.php @@ -128,10 +128,6 @@ public function thankyou_page( $order_id ) { // Get Address (Gateway Setting -> Global Fallback) $address = $this->get_option( 'address' ); - - if ( empty( $address ) ) { - return; - } if ( empty( $address ) ) { // Should logically not happen if we block activation, but good as a failsafe diff --git a/paybutton.php b/paybutton.php index d9ddc60..e2b30a9 100644 --- a/paybutton.php +++ b/paybutton.php @@ -24,6 +24,14 @@ define( 'PAYBUTTON_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); define( 'PAYBUTTON_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); +// NEW: Declare HPOS Compatibility for WooCommerce +// This must run on 'before_woocommerce_init' to ensure WooCommerce knows we support custom order tables. +add_action( 'before_woocommerce_init', function() { + if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) { + \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true ); + } +} ); + // Include required class files. require_once PAYBUTTON_PLUGIN_DIR . 'includes/class-paybutton-activator.php'; require_once PAYBUTTON_PLUGIN_DIR . 'includes/class-paybutton-deactivator.php'; From 6a19c4ca9ba56f1e8eb432f170b10ab689170e3a Mon Sep 17 00:00:00 2001 From: xecdev Date: Wed, 17 Dec 2025 21:06:32 +0430 Subject: [PATCH 05/17] Render PayButton metadata in WooCommerce Order Edit screen and misc refactors --- includes/class-paybutton-ajax.php | 3 + .../class-wc-gateway-paybutton.php | 108 ++++++++++++------ 2 files changed, 74 insertions(+), 37 deletions(-) diff --git a/includes/class-paybutton-ajax.php b/includes/class-paybutton-ajax.php index fdb884b..0c5ee28 100644 --- a/includes/class-paybutton-ajax.php +++ b/includes/class-paybutton-ajax.php @@ -218,6 +218,9 @@ public function payment_trigger() { // 4. Add informative note $note = sprintf( 'PayButton Payment Received via Webhook. Value: $%s. Tx Hash: %s', $fiatValue, $tx_hash ); $order->add_order_note( $note ); + $order->update_meta_data( '_paybutton_tx_hash', $tx_hash ); + $order->update_meta_data( '_paybutton_fiat_value', $fiatValue ); + $order->save(); wp_send_json_success( array( 'message' => 'WooCommerce Order Updated' ) ); return; // Stop processing diff --git a/includes/woocommerce/class-wc-gateway-paybutton.php b/includes/woocommerce/class-wc-gateway-paybutton.php index 5218e56..33057ae 100644 --- a/includes/woocommerce/class-wc-gateway-paybutton.php +++ b/includes/woocommerce/class-wc-gateway-paybutton.php @@ -3,7 +3,7 @@ * WooCommerce PayButton Gateway Integration * * Registers the PayButton payment gateway with WooCommerce and adds Gutenberg block support. -*/ + */ if ( ! defined( 'ABSPATH' ) ) exit; // We hook into plugins_loaded to ensuring WC is loaded first @@ -33,6 +33,12 @@ public function __construct() { // Inject PayButton on the Thank You page add_action( 'woocommerce_thankyou_' . $this->id, array( $this, 'thankyou_page' ) ); + + // Load Frontend Scripts (Separated Logic) + add_action( 'wp_enqueue_scripts', array( $this, 'payment_scripts' ) ); + + // Render PayButton order metadata in WooCommerce admin + add_action( 'woocommerce_admin_order_data_after_order_details', array( $this, 'render_order_admin_panel' ) ); } /** @@ -72,23 +78,15 @@ public function init_form_fields() { * Overridden to prevent activation if address is missing. */ public function process_admin_options() { - - // Is the admin trying to enable the gateway? $is_enabling = isset( $_POST['woocommerce_paybutton_enabled'] ); - - // Gateway-specific wallet address ONLY $posted_address = isset( $_POST['woocommerce_paybutton_address'] ) ? sanitize_text_field( $_POST['woocommerce_paybutton_address'] ) : ''; - // Block enabling if address is missing if ( $is_enabling && empty( $posted_address ) ) { - WC_Admin_Settings::add_error( __( 'PayButton Error: You must enter a wallet address to use this payment method.', 'paybutton' ) ); - - // Force-disable the gateway unset( $_POST['woocommerce_paybutton_enabled'] ); } @@ -111,6 +109,36 @@ public function process_payment( $order_id ) { ); } + /** + * Separate Script Loading Logic + */ + public function payment_scripts() { + if ( ! is_wc_endpoint_url( 'order-received' ) ) return; + + global $wp; + $order_id = isset( $wp->query_vars['order-received'] ) ? absint( $wp->query_vars['order-received'] ) : 0; + if ( ! $order_id ) return; + + $order = wc_get_order( $order_id ); + if ( ! $order ) return; + + if ( $this->id !== $order->get_payment_method() ) return; + if ( $order->is_paid() ) return; + + wp_enqueue_script( + 'paybutton-woo', + PAYBUTTON_PLUGIN_URL . 'assets/js/paybutton-woo.js', + array( 'jquery', 'paybutton-core' ), + '1.0', + true + ); + + wp_localize_script( 'paybutton-woo', 'PaywallAjax', array( + 'ajaxUrl' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'paybutton_paywall_nonce' ) + )); + } + /** * Output for the order received page. */ @@ -126,13 +154,8 @@ public function thankyou_page( $order_id ) { return; } - // Get Address (Gateway Setting -> Global Fallback) $address = $this->get_option( 'address' ); - - if ( empty( $address ) ) { - // Should logically not happen if we block activation, but good as a failsafe - return; - } + if ( empty( $address ) ) return; $amount_formatted = $order->get_formatted_order_total(); $pay_text = 'Pay ' . strip_tags( $amount_formatted ); @@ -141,26 +164,13 @@ public function thankyou_page( $order_id ) { 'to' => $address, 'amount' => (float) $order->get_total(), 'currency' => $order->get_currency(), - 'text' => $pay_text, + 'text' => $pay_text, 'hoverText' => 'Click to Pay', 'opReturn' => (string) $order_id, 'successText' => 'Payment Received! Processing...', 'autoClose' => true ); - wp_enqueue_script( - 'paybutton-woo', - PAYBUTTON_PLUGIN_URL . 'assets/js/paybutton-woo.js', - array( 'jquery', 'paybutton-core' ), - '1.0', - true - ); - - wp_localize_script( 'paybutton-woo', 'PaywallAjax', array( - 'ajaxUrl' => admin_url( 'admin-ajax.php' ), - 'nonce' => wp_create_nonce( 'paybutton_paywall_nonce' ) - )); - echo '

Complete your payment

'; echo '
'; } @@ -169,18 +179,42 @@ public function thankyou_page( $order_id ) { * Check if the gateway is available for use. */ public function is_available() { - - if ( 'yes' !== $this->get_option( 'enabled' ) ) { - return false; - } - + if ( 'yes' !== $this->get_option( 'enabled' ) ) return false; $address = $this->get_option( 'address' ); + if ( empty( $address ) ) return false; - if ( empty( $address ) ) { - return false; + return parent::is_available(); + } + + /** + * Render PayButton metadata in WooCommerce Order Edit screen + */ + public function render_order_admin_panel( $order ) { + if ( ! $order instanceof WC_Order ) return; + if ( $order->get_payment_method() !== 'paybutton' ) return; + + $tx_hash = $order->get_meta( '_paybutton_tx_hash' ); + $val_usd = $order->get_meta( '_paybutton_fiat_value' ); + + echo '
'; + echo '
'; + echo '

' . esc_html__( 'PayButton Transaction Details', 'paybutton' ) . '

'; + + if ( ! $tx_hash ) { + echo '

No PayButton transaction detected.

'; + } else { + echo '

Transaction Hash:
'; + echo ''; + echo esc_html( $tx_hash ); + echo '

'; + + if ( $val_usd ) { + $formatted_val = number_format( (float) $val_usd, 5, '.', '' ); + echo '

Amount Paid: $' . esc_html( $formatted_val ) . '

'; + } } - return parent::is_available(); + echo '
'; } } } \ No newline at end of file From 091676768aab7d937e34cce8ec828d25f48d5176 Mon Sep 17 00:00:00 2001 From: xecdev Date: Thu, 18 Dec 2025 15:31:32 +0430 Subject: [PATCH 06/17] Improve Payment Sucess CSS --- includes/woocommerce/class-wc-gateway-paybutton.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/includes/woocommerce/class-wc-gateway-paybutton.php b/includes/woocommerce/class-wc-gateway-paybutton.php index 33057ae..873196b 100644 --- a/includes/woocommerce/class-wc-gateway-paybutton.php +++ b/includes/woocommerce/class-wc-gateway-paybutton.php @@ -148,8 +148,7 @@ public function thankyou_page( $order_id ) { if ( $order->is_paid() ) { echo '
'; - echo 'Payment confirmed!
'; - echo 'Your payment has been received and your order is now being processed.'; + echo 'Payment confirmed! Your order is now being processed.'; echo '
'; return; } @@ -197,7 +196,7 @@ public function render_order_admin_panel( $order ) { $val_usd = $order->get_meta( '_paybutton_fiat_value' ); echo '
'; - echo '
'; + echo '
'; echo '

' . esc_html__( 'PayButton Transaction Details', 'paybutton' ) . '

'; if ( ! $tx_hash ) { From b940d62cca99d07f0f209647f5263eb3e508b0f2 Mon Sep 17 00:00:00 2001 From: xecdev Date: Thu, 18 Dec 2025 19:12:57 +0430 Subject: [PATCH 07/17] Improve styling and PayButton size --- includes/woocommerce/class-wc-gateway-paybutton.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/includes/woocommerce/class-wc-gateway-paybutton.php b/includes/woocommerce/class-wc-gateway-paybutton.php index 873196b..3181e08 100644 --- a/includes/woocommerce/class-wc-gateway-paybutton.php +++ b/includes/woocommerce/class-wc-gateway-paybutton.php @@ -167,10 +167,11 @@ public function thankyou_page( $order_id ) { 'hoverText' => 'Click to Pay', 'opReturn' => (string) $order_id, 'successText' => 'Payment Received! Processing...', - 'autoClose' => true + 'autoClose' => true, + 'size' => 'xl' ); - echo '

Complete your payment

'; + echo '

Complete your payment

'; echo '
'; } From 0066e45a6011f368ed9c03b08ce446e034659369 Mon Sep 17 00:00:00 2001 From: xecdev Date: Thu, 18 Dec 2025 20:43:59 +0430 Subject: [PATCH 08/17] Add Wallet Address Validation to Woo Settings and change validation result text placement --- assets/js/addressValidator.bundle.js | 120 +++++++++++++++++++-------- assets/js/paybutton-generator.js | 38 --------- includes/class-paybutton-admin.php | 12 +++ 3 files changed, 96 insertions(+), 74 deletions(-) diff --git a/assets/js/addressValidator.bundle.js b/assets/js/addressValidator.bundle.js index 1a7d060..6113fa1 100644 --- a/assets/js/addressValidator.bundle.js +++ b/assets/js/addressValidator.bundle.js @@ -752,45 +752,93 @@ window.cashaddrExports = cashaddrExports; document.addEventListener('DOMContentLoaded', () => { - // Find the wallet address input field by its ID. - const addressInput = document.getElementById('paybutton_admin_wallet_address'); - if (!addressInput) return; - - // Find or create a span for validation feedback. - let resultSpan = document.getElementById('adminAddressValidationResult'); - if (!resultSpan) { - resultSpan = document.createElement('span'); - resultSpan.id = 'adminAddressValidationResult'; - addressInput.parentNode.appendChild(resultSpan); - } - - // Select the "Save Changes" button by its name attribute. - const saveButton = document.querySelector('button[name="paybutton_paywall_save_settings"]'); - - // Listen to input events to auto-validate as the user types. - addressInput.addEventListener('input', () => { - const address = addressInput.value.trim(); - if (address === "") { - resultSpan.textContent = ''; - resultSpan.style.color = ''; - if (saveButton) saveButton.disabled = true; - return; + + // Define the targets: Input ID, Button Name (to disable/enable), and Context + const targets = [ + { + input: 'paybutton_admin_wallet_address', + btnName: 'paybutton_paywall_save_settings', + context: 'paywall' + }, + { + input: 'pbGenTo', + btnName: null, // Generator doesn't have a save button to block + context: 'generator' + }, + { + input: 'woocommerce_paybutton_address', + btnName: 'save', // Standard WooCommerce save button name + context: 'woo' } + ]; + + targets.forEach(target => { + const addressInput = document.getElementById(target.input); + if (!addressInput) return; + + // Create a unique span ID for this input + const resultSpanId = target.input + '_validation_result'; - const valid = cashaddrExports.isValidCashAddress(address); - if (valid) { - resultSpan.textContent = '✅ Valid address'; - resultSpan.style.color = 'green'; - if (saveButton) saveButton.disabled = false; - } else { - resultSpan.textContent = '❌ Invalid address'; - resultSpan.style.color = 'red'; - if (saveButton) saveButton.disabled = true; + // Find or create a span for validation feedback. + let resultSpan = document.getElementById(resultSpanId); + + if (!resultSpan) { + resultSpan = document.createElement('span'); + resultSpan.id = resultSpanId; + + // STYLING UPDATES + resultSpan.style.display = 'block'; + resultSpan.style.fontWeight = 'bold'; + // Add spacing below the text so it doesn't touch the input + resultSpan.style.marginBottom = '5px'; + + // PLACEMENT FIX (ABOVE INPUT BOX) + // This inserts the resultSpan immediately BEFORE the addressInput element. + // On the Generator page, this will place it between the Label and the Input. + addressInput.parentNode.insertBefore(resultSpan, addressInput); + } + + // Select the "Save Changes" button if applicable + let saveButton = null; + if (target.btnName) { + saveButton = document.querySelector(`button[name="${target.btnName}"]`); } + + const validateAddress = () => { + const address = addressInput.value.trim(); + + // Reset if empty + if (address === "") { + resultSpan.textContent = ''; + // When empty, we hide the margin so it doesn't create a blank gap above the input + resultSpan.style.marginBottom = '0px'; + resultSpan.style.color = ''; + + if (saveButton) saveButton.disabled = true; + return; + } + + // Restore margin when text is visible + resultSpan.style.marginBottom = '3px'; + + const valid = cashaddrExports.isValidCashAddress(address); + + if (valid) { + resultSpan.textContent = '✅ Valid address'; + resultSpan.style.color = 'green'; + if (saveButton) saveButton.disabled = false; + } else { + resultSpan.textContent = '❌ Invalid address'; + resultSpan.style.color = 'red'; + if (saveButton) saveButton.disabled = true; + } + }; + + // Listen to input events + addressInput.addEventListener('input', validateAddress); + + // Run immediately on load + validateAddress(); }); - - // Run validation immediately on page load in case the field already has a value. - addressInput.dispatchEvent(new Event('input')); }); - })(); \ No newline at end of file diff --git a/assets/js/paybutton-generator.js b/assets/js/paybutton-generator.js index 3e78009..67c8070 100644 --- a/assets/js/paybutton-generator.js +++ b/assets/js/paybutton-generator.js @@ -3,44 +3,6 @@ (function($) { "use strict"; - /* ========================================================================== - ADMIN WALLET ADDRESS VALIDATION - ========================================================================== - */ - if ($('#pbGenTo').length) { - - const $toField = $('#pbGenTo'); - let $validationMsg; - - if (!$('#pbGenToValidationResult').length) { - $toField.after('

'); - } - $validationMsg = $('#pbGenToValidationResult'); - - $toField.on('input', function() { - const address = $toField.val().trim(); - - if (!address) { - $validationMsg.text('').css('color', ''); - return; - } - - const valid = window.cashaddrExports && window.cashaddrExports.isValidCashAddress(address); - if (typeof window.cashaddrExports === 'undefined') { - console.error('[PayButton] addressValidator is missing or not loaded!'); - } - - if (valid) { - $validationMsg.text('✅ Valid address').css('color', 'green'); - } else { - $validationMsg.text('❌ Invalid address').css('color', 'red'); - } - }); - - // Trigger input event on page load to validate pre-set value (from Paywall Settings). - $toField.trigger('input'); - } - /* ========================================================================== BUTTON GENERATOR LOGIC ========================================================================== diff --git a/includes/class-paybutton-admin.php b/includes/class-paybutton-admin.php index 7f5b5b3..288ab58 100644 --- a/includes/class-paybutton-admin.php +++ b/includes/class-paybutton-admin.php @@ -116,6 +116,18 @@ public function enqueue_admin_scripts( $hook_suffix ) { true ); + // Load the address validator on WooCommerce settings page too + if ( $hook_suffix === 'woocommerce_page_wc-settings' ) { + // Enqueue the bundled address validator script + wp_enqueue_script( + 'address-validator', + PAYBUTTON_PLUGIN_URL . 'assets/js/addressValidator.bundle.js', + array(), + '2.0.0', + true + ); + } + if ( $hook_suffix === 'paybutton_page_paybutton-paywall' ) { wp_enqueue_style( 'wp-color-picker' ); wp_enqueue_script( 'wp-color-picker' ); From 8940a3def60db758db511892d687c4175086382b Mon Sep 17 00:00:00 2001 From: xecdev Date: Thu, 18 Dec 2025 22:04:43 +0430 Subject: [PATCH 09/17] Add the eCash logo to the PayButton Gateway in the checkout page --- assets/icons/eCash.png | Bin 0 -> 12817 bytes assets/js/paybutton-blocks.js | 36 ++++++++++++++---- .../class-paybutton-blocks-support.php | 4 +- 3 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 assets/icons/eCash.png diff --git a/assets/icons/eCash.png b/assets/icons/eCash.png new file mode 100644 index 0000000000000000000000000000000000000000..4fdab5412e7a89ef87f30e596936611f6bd2417f GIT binary patch literal 12817 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 { return createElement( 'span', @@ -19,20 +19,42 @@ style: { display: 'flex', alignItems: 'center', - width: '100%' + width: '100%', } }, - // 1. The Image + // 1. The PayButton Image settings.icon ? createElement( 'img', { src: settings.icon, alt: labelText, style: { - maxHeight: '30px', // Slightly larger since it stands alone + maxHeight: '30px', objectFit: 'contain' } - } ) - // 2. Fallback: If no icon is found, show text so the button isn't invisible - : createElement( 'span', null, labelText ) + } ) : null, + + // 2. The Pipeline Separator (Only shows if BOTH icons exist) + (settings.icon && settings.icon2) ? createElement( 'span', { + style: { + margin: '0 10px', // Spacing around the pipe + color: '#ccc', // Light gray color + fontSize: '24px', // Size of the pipe + lineHeight: '1', + fontWeight: '300' + } + }, '|' ) : null, + + // 3. The eCash Image + settings.icon2 ? createElement( 'img', { + src: settings.icon2, + alt: 'eCash', + style: { + maxHeight: '24px', + objectFit: 'contain' + } + } ) : null, + + // 4. Fallback: If no icons are found, show text + (!settings.icon && !settings.icon2) ? createElement( 'span', null, labelText ) : null ); }; diff --git a/includes/woocommerce/class-paybutton-blocks-support.php b/includes/woocommerce/class-paybutton-blocks-support.php index e629bce..c7682c7 100644 --- a/includes/woocommerce/class-paybutton-blocks-support.php +++ b/includes/woocommerce/class-paybutton-blocks-support.php @@ -55,7 +55,9 @@ public function get_payment_method_data() { return array( 'title' => $gateway ? $gateway->get_title() : 'PayButton', 'description' => $gateway ? $gateway->get_description() : '', - 'icon' => PAYBUTTON_PLUGIN_URL . 'assets/paybutton-logo.png', + 'icon' => PAYBUTTON_PLUGIN_URL . 'assets/paybutton-logo.png', + // NEW: Secondary icon (eCash) + 'icon2' => PAYBUTTON_PLUGIN_URL . 'assets/icons/eCash.png', 'supports' => array( 'products' ), ); } From c2b219122a6cd2a1d1a4d21fe4addfed34ab4873 Mon Sep 17 00:00:00 2001 From: xecdev Date: Mon, 22 Dec 2025 13:20:52 +0430 Subject: [PATCH 10/17] Added 'Enable/disable Sticky Header', seprate the verification overlay logic from Sticky Header and missing webhook parm, misc gateway text update --- assets/css/sticky-header.css | 2 +- includes/class-paybutton-admin.php | 5 +++++ includes/class-paybutton-ajax.php | 4 ++++ includes/class-paybutton-public.php | 20 +++++++++++++++++++ .../class-wc-gateway-paybutton.php | 3 ++- templates/admin/paywall-settings.php | 19 ++++++++++++++++++ templates/public/paybutton-overlay.php | 13 ++++++++++++ 7 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 templates/public/paybutton-overlay.php diff --git a/assets/css/sticky-header.css b/assets/css/sticky-header.css index c010792..1b958c8 100644 --- a/assets/css/sticky-header.css +++ b/assets/css/sticky-header.css @@ -117,7 +117,7 @@ } /* Prevent header from overlapping content */ -body { +body.pb-has-sticky-header { padding-top: 80px !important; } diff --git a/includes/class-paybutton-admin.php b/includes/class-paybutton-admin.php index 288ab58..2deeda1 100644 --- a/includes/class-paybutton-admin.php +++ b/includes/class-paybutton-admin.php @@ -343,6 +343,11 @@ private function save_settings() { ? sanitize_hex_color( wp_unslash( $_POST['paybutton_frontend_unlock_color'] ) ) : '#0074C2'; update_option( 'paybutton_frontend_unlock_color', $frontend_unlock_color ); + + update_option( + 'paybutton_hide_sticky_header', + isset( $_POST['paybutton_hide_sticky_header'] ) ? '1' : '0' + ); } /** diff --git a/includes/class-paybutton-ajax.php b/includes/class-paybutton-ajax.php index 0c5ee28..374a1b1 100644 --- a/includes/class-paybutton-ajax.php +++ b/includes/class-paybutton-ajax.php @@ -599,6 +599,10 @@ public function ajax_validate_unlock_tx() { public function get_sticky_header() { check_ajax_referer( 'paybutton_paywall_nonce', 'security' ); + if ( get_option( 'paybutton_hide_sticky_header', '0' ) === '1' ) { + wp_send_json_success( array( 'html' => '' ) ); + } + $template = PAYBUTTON_PLUGIN_DIR . 'templates/public/sticky-header.php'; if ( ! file_exists( $template ) ) { wp_send_json_error( array( 'message' => 'Sticky header template not found.' ), 500 ); diff --git a/includes/class-paybutton-public.php b/includes/class-paybutton-public.php index 89433dc..b141b50 100644 --- a/includes/class-paybutton-public.php +++ b/includes/class-paybutton-public.php @@ -33,6 +33,8 @@ public function __construct() { add_filter( 'preprocess_comment', array( $this, 'block_comment_if_locked' ) ); // Hard-block comment creation via REST API as well when the paywalled content is locked add_filter( 'rest_pre_insert_comment', array( $this, 'block_comment_if_locked_rest' ), 10, 2 ); + add_action( 'wp_footer', array( $this, 'output_paybutton_overlay' ), 5 ); + add_filter( 'body_class', array( $this, 'filter_body_classes' ) ); } /** @@ -163,6 +165,9 @@ private function load_public_template( $template_name, $args = array() ) { * Output the sticky header HTML. */ public function output_sticky_header() { + if ( get_option( 'paybutton_hide_sticky_header', '0' ) === '1' ) { + return; + } $paybutton_user_wallet_address = sanitize_text_field( PayButton_State::get_address() ); $this->load_public_template( 'sticky-header', array( 'paybutton_user_wallet_address' => $paybutton_user_wallet_address @@ -376,4 +381,19 @@ public function block_comment_if_locked_rest( $prepared, $request ) { } return $prepared; } + /** + * Output the PayButton overlay HTML. + */ + public function output_paybutton_overlay() { + $this->load_public_template( 'paybutton-overlay' ); + } + /** + * Filter to add custom body classes. + */ + public function filter_body_classes( $classes ) { + if ( get_option( 'paybutton_hide_sticky_header', '0' ) !== '1' ) { + $classes[] = 'pb-has-sticky-header'; + } + return $classes; + } } \ No newline at end of file diff --git a/includes/woocommerce/class-wc-gateway-paybutton.php b/includes/woocommerce/class-wc-gateway-paybutton.php index 3181e08..d770d04 100644 --- a/includes/woocommerce/class-wc-gateway-paybutton.php +++ b/includes/woocommerce/class-wc-gateway-paybutton.php @@ -55,7 +55,7 @@ public function init_form_fields() { 'title' => array( 'title' => 'Title', 'type' => 'text', - 'default' => 'Pay with eCash (XEC)', + 'default' => 'PayButton', 'desc_tip' => true, ), 'description' => array( @@ -150,6 +150,7 @@ public function thankyou_page( $order_id ) { echo '
'; echo 'Payment confirmed! Your order is now being processed.'; echo '
'; + echo '
'; return; } diff --git a/templates/admin/paywall-settings.php b/templates/admin/paywall-settings.php index 95b39e6..8d3ee52 100644 --- a/templates/admin/paywall-settings.php +++ b/templates/admin/paywall-settings.php @@ -137,6 +137,24 @@

Sticky Header Settings

+ + + + + + +

+ +

+ + @@ -257,6 +275,7 @@ class="regular-text" "tx_amount": <amount>, "tx_timestamp": <timestamp>, "user_address": <inputAddresses>, +"value": <value>, "currency": <currency> }

diff --git a/templates/public/paybutton-overlay.php b/templates/public/paybutton-overlay.php new file mode 100644 index 0000000..79c78b2 --- /dev/null +++ b/templates/public/paybutton-overlay.php @@ -0,0 +1,13 @@ + + + +

\ No newline at end of file From a289db4ebe607872e1e96c2f8256fde25af0712e Mon Sep 17 00:00:00 2001 From: xecdev Date: Mon, 22 Dec 2025 16:56:16 +0430 Subject: [PATCH 11/17] Fixed the PluginCheck errors --- includes/class-paybutton-admin.php | 2 +- includes/class-paybutton-transactions.php | 67 ++++++++++++++----- .../class-wc-gateway-paybutton.php | 25 +++++-- paybutton.php | 10 +-- 4 files changed, 74 insertions(+), 30 deletions(-) diff --git a/includes/class-paybutton-admin.php b/includes/class-paybutton-admin.php index 2deeda1..069b994 100644 --- a/includes/class-paybutton-admin.php +++ b/includes/class-paybutton-admin.php @@ -85,7 +85,7 @@ public function handle_save_settings() { ) { $this->save_settings(); wp_cache_delete( 'paybutton_admin_wallet_address', 'options' ); - wp_redirect( admin_url( 'admin.php?page=paybutton-paywall&settings-updated=true' ) ); + wp_safe_redirect( admin_url( 'admin.php?page=paybutton-paywall&settings-updated=true' ) ); exit; } } diff --git a/includes/class-paybutton-transactions.php b/includes/class-paybutton-transactions.php index ced6129..0537ead 100644 --- a/includes/class-paybutton-transactions.php +++ b/includes/class-paybutton-transactions.php @@ -69,23 +69,52 @@ public static function consume_row_and_attach_token( global $wpdb; + // Explicit table whitelist + $allowed_tables = [ + $wpdb->prefix . 'paybutton_logins', + $wpdb->prefix . 'paybutton_paywall_unlocked', + ]; + + if ( ! in_array( $table, $allowed_tables, true ) ) { + return null; + } + + // Explicit column whitelist + $allowed_columns = [ + 'wallet_address', + 'tx_hash', + 'used', + ]; + $conditions = []; $values = []; - foreach ($where as $column => $value) { - $conditions[] = "{$column} = %s"; + foreach ( $where as $column => $value ) { + + if ( ! in_array( $column, $allowed_columns, true ) ) { + continue; + } + + $conditions[] = "`{$column}` = %s"; $values[] = $value; } + if ( empty( $conditions ) ) { + return null; + } + + // We construct the SQL dynamically here because the WHERE clause varies. $sql = " - SELECT id FROM {$table} - WHERE " . implode(' AND ', $conditions) . " + SELECT id + FROM `{$table}` + WHERE " . implode( ' AND ', $conditions ) . " AND used = 0 ORDER BY id DESC LIMIT 1 "; - $row = $wpdb->get_row($wpdb->prepare($sql, ...$values)); + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- SQL is constructed dynamically with whitelisted columns and placeholders. + $row = $wpdb->get_row( $wpdb->prepare( $sql, ...$values ) ); if (!$row) { return null; @@ -96,12 +125,12 @@ public static function consume_row_and_attach_token( $wpdb->update( $table, [ - 'used' => 1, + 'used' => 1, $token_column => $token, ], - ['id' => (int) $row->id], - ['%d', '%s'], - ['%d'] + [ 'id' => (int) $row->id ], + [ '%d', '%s' ], + [ '%d' ] ); return $token; @@ -149,10 +178,12 @@ public static function insert_unlock_if_new( $table_name = $wpdb->prefix . 'paybutton_paywall_unlocked'; // Check if the transaction already exists using tx hash - $exists = $wpdb->get_var($wpdb->prepare( - "SELECT id FROM $table_name WHERE tx_hash = %s LIMIT 1", - $tx_hash - )); + $exists = $wpdb->get_var( + $wpdb->prepare( + "SELECT id FROM {$table_name} WHERE tx_hash = %s LIMIT 1", + $tx_hash + ) + ); if ($exists) { return; // Transaction already recorded, so we don't insert again. @@ -163,11 +194,11 @@ public static function insert_unlock_if_new( $table_name, array( 'pb_paywall_user_wallet_address' => $address, - 'post_id' => $post_id, - 'tx_hash' => $tx_hash, - 'tx_amount' => $tx_amount, - 'tx_timestamp' => $tx_dt, - 'is_logged_in' => $is_logged_in, + 'post_id' => $post_id, + 'tx_hash' => $tx_hash, + 'tx_amount' => $tx_amount, + 'tx_timestamp' => $tx_dt, + 'is_logged_in' => $is_logged_in, ), array( '%s', '%d', '%s', '%f', '%s', '%d' ) ); diff --git a/includes/woocommerce/class-wc-gateway-paybutton.php b/includes/woocommerce/class-wc-gateway-paybutton.php index d770d04..1a6e3a8 100644 --- a/includes/woocommerce/class-wc-gateway-paybutton.php +++ b/includes/woocommerce/class-wc-gateway-paybutton.php @@ -7,9 +7,9 @@ if ( ! defined( 'ABSPATH' ) ) exit; // We hook into plugins_loaded to ensuring WC is loaded first -add_action( 'plugins_loaded', 'init_wc_gateway_paybutton' ); +add_action( 'plugins_loaded', 'paybutton_init_wc_gateway' ); -function init_wc_gateway_paybutton() { +function paybutton_init_wc_gateway() { if ( ! class_exists( 'WC_Payment_Gateway' ) ) return; class WC_Gateway_PayButton extends WC_Payment_Gateway { @@ -78,10 +78,23 @@ public function init_form_fields() { * Overridden to prevent activation if address is missing. */ public function process_admin_options() { + + // Verify WooCommerce settings nonce + if ( + ! isset( $_POST['_wpnonce'] ) || + ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'woocommerce-settings' ) + ) { + return; + } + $is_enabling = isset( $_POST['woocommerce_paybutton_enabled'] ); - $posted_address = isset( $_POST['woocommerce_paybutton_address'] ) - ? sanitize_text_field( $_POST['woocommerce_paybutton_address'] ) - : ''; + + $posted_address = ''; + if ( isset( $_POST['woocommerce_paybutton_address'] ) ) { + $posted_address = sanitize_text_field( + wp_unslash( $_POST['woocommerce_paybutton_address'] ) + ); + } if ( $is_enabling && empty( $posted_address ) ) { WC_Admin_Settings::add_error( @@ -158,7 +171,7 @@ public function thankyou_page( $order_id ) { if ( empty( $address ) ) return; $amount_formatted = $order->get_formatted_order_total(); - $pay_text = 'Pay ' . strip_tags( $amount_formatted ); + $pay_text = 'Pay ' . wp_strip_all_tags( $amount_formatted ); $config = array( 'to' => $address, diff --git a/paybutton.php b/paybutton.php index e2b30a9..b0638db 100644 --- a/paybutton.php +++ b/paybutton.php @@ -77,9 +77,9 @@ }, 1); // Use a priority to ensure this runs before other actions that might depend on session data. // 1. Register the Gateway -add_filter( 'woocommerce_payment_gateways', 'add_paybutton_gateway_class' ); +add_filter( 'woocommerce_payment_gateways', 'paybutton_add_gateway_class' ); -function add_paybutton_gateway_class( $gateways ) { +function paybutton_add_gateway_class( $gateways ) { if ( class_exists( 'WC_Gateway_PayButton' ) ) { $gateways[] = 'WC_Gateway_PayButton'; } @@ -87,9 +87,9 @@ function add_paybutton_gateway_class( $gateways ) { } // 2. Register Gutenberg Block Support -add_action( 'woocommerce_blocks_payment_method_type_registration', 'register_paybutton_blocks_support' ); +add_action( 'woocommerce_blocks_payment_method_type_registration', 'paybutton_register_blocks_support' ); -function register_paybutton_blocks_support( \Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry $payment_method_registry ) { +function paybutton_register_blocks_support( \Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry $payment_method_registry ) { // Ensure WooCommerce Blocks class exists if ( ! class_exists( 'Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType' ) ) { return; @@ -111,7 +111,7 @@ function register_paybutton_blocks_support( \Automattic\WooCommerce\Blocks\Payme delete_option('paybutton_activation_redirect'); // Prevent redirect during bulk plugin activation if (!isset($_GET['activate-multi'])) { - wp_redirect(admin_url('admin.php?page=paybutton-paywall')); + wp_safe_redirect(admin_url('admin.php?page=paybutton-paywall')); exit; } } From 7c24eec91fe92a27037f616f4379e21f2e2e58e3 Mon Sep 17 00:00:00 2001 From: xecdev Date: Wed, 24 Dec 2025 20:40:22 +0430 Subject: [PATCH 12/17] Responding to CodeRabbit's feedback --- includes/class-paybutton-admin.php | 13 +++++-------- includes/class-paybutton-transactions.php | 9 ++++++++- includes/woocommerce/class-wc-gateway-paybutton.php | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/includes/class-paybutton-admin.php b/includes/class-paybutton-admin.php index 069b994..94325b9 100644 --- a/includes/class-paybutton-admin.php +++ b/includes/class-paybutton-admin.php @@ -116,9 +116,9 @@ public function enqueue_admin_scripts( $hook_suffix ) { true ); - // Load the address validator on WooCommerce settings page too - if ( $hook_suffix === 'woocommerce_page_wc-settings' ) { - // Enqueue the bundled address validator script + $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; + + if ( $screen && $screen->id === 'woocommerce_page_wc-settings' ) { wp_enqueue_script( 'address-validator', PAYBUTTON_PLUGIN_URL . 'assets/js/addressValidator.bundle.js', @@ -128,7 +128,7 @@ public function enqueue_admin_scripts( $hook_suffix ) { ); } - if ( $hook_suffix === 'paybutton_page_paybutton-paywall' ) { + if ( $screen && $screen->id === 'paybutton_page_paybutton-paywall' ) { wp_enqueue_style( 'wp-color-picker' ); wp_enqueue_script( 'wp-color-picker' ); @@ -142,10 +142,7 @@ public function enqueue_admin_scripts( $hook_suffix ) { ); } - // Only load the generator JS on the PayButton Generator page - if ( $hook_suffix === 'paybutton_page_paybutton-generator' ) { - - // Enqueue the bundled address validator script + if ( $screen && $screen->id === 'paybutton_page_paybutton-generator' ) { wp_enqueue_script( 'address-validator', PAYBUTTON_PLUGIN_URL . 'assets/js/addressValidator.bundle.js', diff --git a/includes/class-paybutton-transactions.php b/includes/class-paybutton-transactions.php index 0537ead..6eda16b 100644 --- a/includes/class-paybutton-transactions.php +++ b/includes/class-paybutton-transactions.php @@ -80,8 +80,15 @@ public static function consume_row_and_attach_token( } // Explicit column whitelist - $allowed_columns = [ + $allowed_columns = [ + // login table 'wallet_address', + + // unlock table + 'pb_paywall_user_wallet_address', + 'post_id', + + // shared 'tx_hash', 'used', ]; diff --git a/includes/woocommerce/class-wc-gateway-paybutton.php b/includes/woocommerce/class-wc-gateway-paybutton.php index 1a6e3a8..7540021 100644 --- a/includes/woocommerce/class-wc-gateway-paybutton.php +++ b/includes/woocommerce/class-wc-gateway-paybutton.php @@ -113,7 +113,7 @@ public function process_payment( $order_id ) { $order = wc_get_order( $order_id ); $order->update_status( 'on-hold', __( 'Awaiting PayButton payment.', 'paybutton' ) ); - $order->reduce_order_stock(); + wc_reduce_stock_levels( $order_id ); WC()->cart->empty_cart(); return array( From 425d5d033aa8443398680724f1d82852eb0bb144 Mon Sep 17 00:00:00 2001 From: xecdev Date: Wed, 24 Dec 2025 21:42:37 +0430 Subject: [PATCH 13/17] Responding to the bots feedback --- includes/class-paybutton-activator.php | 14 ++--- includes/class-paybutton-transactions.php | 59 ++++++------------- .../class-wc-gateway-paybutton.php | 2 +- 3 files changed, 23 insertions(+), 52 deletions(-) diff --git a/includes/class-paybutton-activator.php b/includes/class-paybutton-activator.php index 06ec1c0..f2ef3e2 100644 --- a/includes/class-paybutton-activator.php +++ b/includes/class-paybutton-activator.php @@ -19,17 +19,11 @@ public static function activate() { self::create_profile_page(); // Set a flag to redirect the admin to the Paywall Settings page after activation update_option('paybutton_activation_redirect', true); - self::migrate_old_option(); + //self::migrate_old_option(); } private static function migrate_old_option() { - // --- 1. unlocked‑indicator colours --- - $txt_old = get_option( 'paybutton_unlocked_indicator_text_color', '' ); - $txt_new = get_option( 'paybutton_unlocked_indicator_color', '' ); - if ( ! empty( $txt_old ) && empty( $txt_new ) ) { - update_option( 'paybutton_unlocked_indicator_color', $txt_old ); - delete_option( 'paybutton_unlocked_indicator_text_color' ); - } + // Empty function for future use } /** @@ -55,7 +49,7 @@ public static function create_tables() { PRIMARY KEY (id), KEY pb_paywall_user_wallet_address_idx (pb_paywall_user_wallet_address), KEY post_id_idx (post_id), - KEY tx_hash_idx (tx_hash), + UNIQUE KEY tx_hash_idx (tx_hash), KEY unlock_token_idx (unlock_token) ) $charset_collate;"; @@ -75,7 +69,7 @@ public static function create_tables() { used TINYINT(1) NOT NULL DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), - KEY tx_hash_idx (tx_hash), + UNIQUE KEY tx_hash_idx (tx_hash), KEY wallet_addr_idx (wallet_address(190)), KEY used_idx (used), KEY login_token_idx (login_token) diff --git a/includes/class-paybutton-transactions.php b/includes/class-paybutton-transactions.php index 6eda16b..bef9ef2 100644 --- a/includes/class-paybutton-transactions.php +++ b/includes/class-paybutton-transactions.php @@ -184,31 +184,20 @@ public static function insert_unlock_if_new( global $wpdb; $table_name = $wpdb->prefix . 'paybutton_paywall_unlocked'; - // Check if the transaction already exists using tx hash - $exists = $wpdb->get_var( + // Insert the transaction if it's not already recorded + $wpdb->query( $wpdb->prepare( - "SELECT id FROM {$table_name} WHERE tx_hash = %s LIMIT 1", - $tx_hash + "INSERT IGNORE INTO {$table_name} + (pb_paywall_user_wallet_address, post_id, tx_hash, tx_amount, tx_timestamp, is_logged_in) + VALUES (%s, %d, %s, %f, %s, %d)", + $address, + $post_id, + $tx_hash, + $tx_amount, + $tx_dt, + $is_logged_in ) ); - - if ($exists) { - return; // Transaction already recorded, so we don't insert again. - } - - // Insert the transaction if it's not already recorded - $wpdb->insert( - $table_name, - array( - 'pb_paywall_user_wallet_address' => $address, - 'post_id' => $post_id, - 'tx_hash' => $tx_hash, - 'tx_amount' => $tx_amount, - 'tx_timestamp' => $tx_dt, - 'is_logged_in' => $is_logged_in, - ), - array( '%s', '%d', '%s', '%f', '%s', '%d' ) - ); } /* ============================================================ @@ -226,29 +215,17 @@ public static function record_login_tx_if_new( global $wpdb; $table = $wpdb->prefix . 'paybutton_logins'; - $exists = $wpdb->get_var( + $wpdb->query( $wpdb->prepare( - "SELECT id FROM {$table} WHERE wallet_address = %s AND tx_hash = %s LIMIT 1", + "INSERT IGNORE INTO {$table} + (wallet_address, tx_hash, tx_amount, tx_timestamp, used) + VALUES (%s, %s, %f, %d, 0)", $wallet_address, - $tx_hash + $tx_hash, + $tx_amount, + $tx_timestamp ) ); - - if ($exists) { - return; - } - - $wpdb->insert( - $table, - [ - 'wallet_address' => $wallet_address, - 'tx_hash' => $tx_hash, - 'tx_amount' => $tx_amount, - 'tx_timestamp' => $tx_timestamp, - 'used' => 0, - ], - ['%s','%s','%f','%d','%d'] - ); } diff --git a/includes/woocommerce/class-wc-gateway-paybutton.php b/includes/woocommerce/class-wc-gateway-paybutton.php index 7540021..95659a9 100644 --- a/includes/woocommerce/class-wc-gateway-paybutton.php +++ b/includes/woocommerce/class-wc-gateway-paybutton.php @@ -186,7 +186,7 @@ public function thankyou_page( $order_id ) { ); echo '

Complete your payment

'; - echo '
'; + echo '
'; } /** From 3ad7ebeb9372255e42e32fa97adea52b6d07915a Mon Sep 17 00:00:00 2001 From: xecdev Date: Thu, 25 Dec 2025 09:18:43 +0430 Subject: [PATCH 14/17] Enable the WooCommerce Payments button --- includes/class-paybutton-admin.php | 5 +++-- templates/admin/dashboard.php | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/includes/class-paybutton-admin.php b/includes/class-paybutton-admin.php index 94325b9..25ebe3b 100644 --- a/includes/class-paybutton-admin.php +++ b/includes/class-paybutton-admin.php @@ -42,7 +42,7 @@ public function add_admin_menus() { add_submenu_page( 'paybutton', 'Button Generator', - 'Button Generator NEW!', + 'Button Generator', 'manage_options', 'paybutton-generator', array( $this, 'button_generator_page' ) @@ -192,7 +192,8 @@ private function load_admin_template( $template_name, $args = array() ) { public function dashboard_page() { $args = array( 'generate_button_url' => esc_url( admin_url( 'admin.php?page=paybutton-generator' ) ), - 'paywall_settings_url' => esc_url( admin_url( 'admin.php?page=paybutton-paywall' ) ) + 'paywall_settings_url' => esc_url( admin_url( 'admin.php?page=paybutton-paywall' ) ), + 'woocommerce_payments_url'=> esc_url( admin_url( 'admin.php?page=wc-settings&tab=checkout' ) ) ); $this->load_admin_template( 'dashboard', $args ); } diff --git a/templates/admin/dashboard.php b/templates/admin/dashboard.php index a71339f..5934d8a 100644 --- a/templates/admin/dashboard.php +++ b/templates/admin/dashboard.php @@ -23,11 +23,11 @@ Paywall Settings
- -
-

- PayButton WooComerce – Coming soon! -

+ +
From 69596524296b785cd5bff997ba3caf580319aedd Mon Sep 17 00:00:00 2001 From: xecdev Date: Thu, 25 Dec 2025 09:31:17 +0430 Subject: [PATCH 15/17] Fix the wallet address validation bug --- includes/class-paybutton-admin.php | 33 ++++++++++++++++++------------ 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/includes/class-paybutton-admin.php b/includes/class-paybutton-admin.php index 25ebe3b..d35abb1 100644 --- a/includes/class-paybutton-admin.php +++ b/includes/class-paybutton-admin.php @@ -99,7 +99,7 @@ public function handle_save_settings() { * color selection functionality. */ public function enqueue_admin_scripts( $hook_suffix ) { - // Enqueue the paybutton-admin.css on every admin page + wp_enqueue_style( 'paybutton-admin', PAYBUTTON_PLUGIN_URL . 'assets/css/paybutton-admin.css', @@ -116,19 +116,11 @@ public function enqueue_admin_scripts( $hook_suffix ) { true ); + // --- SAFE SCREEN DETECTION --- $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; - if ( $screen && $screen->id === 'woocommerce_page_wc-settings' ) { - wp_enqueue_script( - 'address-validator', - PAYBUTTON_PLUGIN_URL . 'assets/js/addressValidator.bundle.js', - array(), - '2.0.0', - true - ); - } - - if ( $screen && $screen->id === 'paybutton_page_paybutton-paywall' ) { + // 1) Paywall Settings page + if ( $hook_suffix === 'paybutton_page_paybutton-paywall' ) { wp_enqueue_style( 'wp-color-picker' ); wp_enqueue_script( 'wp-color-picker' ); @@ -142,7 +134,8 @@ public function enqueue_admin_scripts( $hook_suffix ) { ); } - if ( $screen && $screen->id === 'paybutton_page_paybutton-generator' ) { + // 2) Button Generator page + if ( $hook_suffix === 'paybutton_page_paybutton-generator' ) { wp_enqueue_script( 'address-validator', PAYBUTTON_PLUGIN_URL . 'assets/js/addressValidator.bundle.js', @@ -167,6 +160,20 @@ public function enqueue_admin_scripts( $hook_suffix ) { true ); } + + // 3) WooCommerce → Settings → Payments (wallet address field) + if ( + $hook_suffix === 'woocommerce_page_wc-settings' + || ( $screen && $screen->base === 'woocommerce_page_wc-settings' ) + ) { + wp_enqueue_script( + 'address-validator', + PAYBUTTON_PLUGIN_URL . 'assets/js/addressValidator.bundle.js', + array(), + '2.0.0', + true + ); + } } /** From 1b4dd5327adf389e5010a483be02f4ef84730efc Mon Sep 17 00:00:00 2001 From: xecdev Date: Thu, 25 Dec 2025 09:32:49 +0430 Subject: [PATCH 16/17] Fix typo --- templates/admin/dashboard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/admin/dashboard.php b/templates/admin/dashboard.php index 5934d8a..f3400b6 100644 --- a/templates/admin/dashboard.php +++ b/templates/admin/dashboard.php @@ -23,7 +23,7 @@ Paywall Settings
- +
WooCommerce Payments From a4ef74757bddaadf2b676e867a05d11eab7c84e6 Mon Sep 17 00:00:00 2001 From: xecdev Date: Thu, 25 Dec 2025 09:49:40 +0430 Subject: [PATCH 17/17] Check if Woo is installed --- assets/css/paybutton-admin.css | 6 ++++++ includes/class-paybutton-admin.php | 3 ++- templates/admin/dashboard.php | 17 +++++++++++++++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/assets/css/paybutton-admin.css b/assets/css/paybutton-admin.css index f603a1e..d0bb8ac 100644 --- a/assets/css/paybutton-admin.css +++ b/assets/css/paybutton-admin.css @@ -82,6 +82,12 @@ border: none; } +/* Disabled state for buttons*/ +.paybutton-disabled { + opacity: 0.55; + cursor: not-allowed; +} + /* ------------------------------ Button Generator Page Styles ------------------------------ */ diff --git a/includes/class-paybutton-admin.php b/includes/class-paybutton-admin.php index d35abb1..6a62628 100644 --- a/includes/class-paybutton-admin.php +++ b/includes/class-paybutton-admin.php @@ -200,7 +200,8 @@ public function dashboard_page() { $args = array( 'generate_button_url' => esc_url( admin_url( 'admin.php?page=paybutton-generator' ) ), 'paywall_settings_url' => esc_url( admin_url( 'admin.php?page=paybutton-paywall' ) ), - 'woocommerce_payments_url'=> esc_url( admin_url( 'admin.php?page=wc-settings&tab=checkout' ) ) + 'woocommerce_payments_url'=> esc_url( admin_url( 'admin.php?page=wc-settings&tab=checkout' ) ), + 'woocommerce_installed' => class_exists( 'WooCommerce' ) ); $this->load_admin_template( 'dashboard', $args ); } diff --git a/templates/admin/dashboard.php b/templates/admin/dashboard.php index f3400b6..b7e9cb1 100644 --- a/templates/admin/dashboard.php +++ b/templates/admin/dashboard.php @@ -25,9 +25,22 @@