Skip to content

Commit c6a6d82

Browse files
xecdevxecdev
andauthored
Implement Woo Payment Gateway (#116)
* Need refactoring for WC * Initial Woo + PayButton integration with blocks support * Add 'Payment Confirmed' message * Add HPOS Compatibilty for WC * Render PayButton metadata in WooCommerce Order Edit screen and misc refactors * Improve Payment Sucess CSS * Improve styling and PayButton size * Add Wallet Address Validation to Woo Settings and change validation result text placement * Add the eCash logo to the PayButton Gateway in the checkout page * Added 'Enable/disable Sticky Header', seprate the verification overlay logic from Sticky Header and missing webhook parm, misc gateway text update * Fixed the PluginCheck errors * Responding to CodeRabbit's feedback * Responding to the bots feedback * Enable the WooCommerce Payments button * Fix the wallet address validation bug * Fix typo * Check if Woo is installed --------- Co-authored-by: xecdev <ecashinformer@gmail.com>
1 parent 4932e57 commit c6a6d82

18 files changed

Lines changed: 1104 additions & 331 deletions

assets/css/paybutton-admin.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@
8282
border: none;
8383
}
8484

85+
/* Disabled state for buttons*/
86+
.paybutton-disabled {
87+
opacity: 0.55;
88+
cursor: not-allowed;
89+
}
90+
8591
/* ------------------------------
8692
Button Generator Page Styles
8793
------------------------------ */

assets/css/sticky-header.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@
117117
}
118118

119119
/* Prevent header from overlapping content */
120-
body {
120+
body.pb-has-sticky-header {
121121
padding-top: 80px !important;
122122
}
123123

assets/icons/eCash.png

12.5 KB
Loading

assets/js/addressValidator.bundle.js

Lines changed: 84 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -752,45 +752,93 @@
752752
window.cashaddrExports = cashaddrExports;
753753

754754
document.addEventListener('DOMContentLoaded', () => {
755-
// Find the wallet address input field by its ID.
756-
const addressInput = document.getElementById('paybutton_admin_wallet_address');
757-
if (!addressInput) return;
758-
759-
// Find or create a span for validation feedback.
760-
let resultSpan = document.getElementById('adminAddressValidationResult');
761-
if (!resultSpan) {
762-
resultSpan = document.createElement('span');
763-
resultSpan.id = 'adminAddressValidationResult';
764-
addressInput.parentNode.appendChild(resultSpan);
765-
}
766-
767-
// Select the "Save Changes" button by its name attribute.
768-
const saveButton = document.querySelector('button[name="paybutton_paywall_save_settings"]');
769-
770-
// Listen to input events to auto-validate as the user types.
771-
addressInput.addEventListener('input', () => {
772-
const address = addressInput.value.trim();
773-
if (address === "") {
774-
resultSpan.textContent = '';
775-
resultSpan.style.color = '';
776-
if (saveButton) saveButton.disabled = true;
777-
return;
755+
756+
// Define the targets: Input ID, Button Name (to disable/enable), and Context
757+
const targets = [
758+
{
759+
input: 'paybutton_admin_wallet_address',
760+
btnName: 'paybutton_paywall_save_settings',
761+
context: 'paywall'
762+
},
763+
{
764+
input: 'pbGenTo',
765+
btnName: null, // Generator doesn't have a save button to block
766+
context: 'generator'
767+
},
768+
{
769+
input: 'woocommerce_paybutton_address',
770+
btnName: 'save', // Standard WooCommerce save button name
771+
context: 'woo'
778772
}
773+
];
774+
775+
targets.forEach(target => {
776+
const addressInput = document.getElementById(target.input);
777+
if (!addressInput) return;
778+
779+
// Create a unique span ID for this input
780+
const resultSpanId = target.input + '_validation_result';
779781

780-
const valid = cashaddrExports.isValidCashAddress(address);
781-
if (valid) {
782-
resultSpan.textContent = '✅ Valid address';
783-
resultSpan.style.color = 'green';
784-
if (saveButton) saveButton.disabled = false;
785-
} else {
786-
resultSpan.textContent = '❌ Invalid address';
787-
resultSpan.style.color = 'red';
788-
if (saveButton) saveButton.disabled = true;
782+
// Find or create a span for validation feedback.
783+
let resultSpan = document.getElementById(resultSpanId);
784+
785+
if (!resultSpan) {
786+
resultSpan = document.createElement('span');
787+
resultSpan.id = resultSpanId;
788+
789+
// STYLING UPDATES
790+
resultSpan.style.display = 'block';
791+
resultSpan.style.fontWeight = 'bold';
792+
// Add spacing below the text so it doesn't touch the input
793+
resultSpan.style.marginBottom = '5px';
794+
795+
// PLACEMENT FIX (ABOVE INPUT BOX)
796+
// This inserts the resultSpan immediately BEFORE the addressInput element.
797+
// On the Generator page, this will place it between the Label and the Input.
798+
addressInput.parentNode.insertBefore(resultSpan, addressInput);
799+
}
800+
801+
// Select the "Save Changes" button if applicable
802+
let saveButton = null;
803+
if (target.btnName) {
804+
saveButton = document.querySelector(`button[name="${target.btnName}"]`);
789805
}
806+
807+
const validateAddress = () => {
808+
const address = addressInput.value.trim();
809+
810+
// Reset if empty
811+
if (address === "") {
812+
resultSpan.textContent = '';
813+
// When empty, we hide the margin so it doesn't create a blank gap above the input
814+
resultSpan.style.marginBottom = '0px';
815+
resultSpan.style.color = '';
816+
817+
if (saveButton) saveButton.disabled = true;
818+
return;
819+
}
820+
821+
// Restore margin when text is visible
822+
resultSpan.style.marginBottom = '3px';
823+
824+
const valid = cashaddrExports.isValidCashAddress(address);
825+
826+
if (valid) {
827+
resultSpan.textContent = '✅ Valid address';
828+
resultSpan.style.color = 'green';
829+
if (saveButton) saveButton.disabled = false;
830+
} else {
831+
resultSpan.textContent = '❌ Invalid address';
832+
resultSpan.style.color = 'red';
833+
if (saveButton) saveButton.disabled = true;
834+
}
835+
};
836+
837+
// Listen to input events
838+
addressInput.addEventListener('input', validateAddress);
839+
840+
// Run immediately on load
841+
validateAddress();
790842
});
791-
792-
// Run validation immediately on page load in case the field already has a value.
793-
addressInput.dispatchEvent(new Event('input'));
794843
});
795-
796844
})();

assets/js/paybutton-blocks.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* WooCommerce PayButton Blocks Integration JS
3+
*/
4+
(function( wc, wp ) {
5+
const { registerPaymentMethod } = wc.wcBlocksRegistry;
6+
const { getSetting } = wc.wcSettings;
7+
const { decodeEntities } = wp.htmlEntities;
8+
const { createElement } = wp.element;
9+
10+
const settings = getSetting( 'paybutton_data', {} );
11+
12+
const labelText = decodeEntities( settings.title || 'PayButton' );
13+
14+
// Create a Custom Label Component (Dual Icons)
15+
const LabelIconOnly = () => {
16+
return createElement(
17+
'span',
18+
{
19+
style: {
20+
display: 'flex',
21+
alignItems: 'center',
22+
width: '100%',
23+
}
24+
},
25+
// 1. The PayButton Image
26+
settings.icon ? createElement( 'img', {
27+
src: settings.icon,
28+
alt: labelText,
29+
style: {
30+
maxHeight: '30px',
31+
objectFit: 'contain'
32+
}
33+
} ) : null,
34+
35+
// 2. The Pipeline Separator (Only shows if BOTH icons exist)
36+
(settings.icon && settings.icon2) ? createElement( 'span', {
37+
style: {
38+
margin: '0 10px', // Spacing around the pipe
39+
color: '#ccc', // Light gray color
40+
fontSize: '24px', // Size of the pipe
41+
lineHeight: '1',
42+
fontWeight: '300'
43+
}
44+
}, '|' ) : null,
45+
46+
// 3. The eCash Image
47+
settings.icon2 ? createElement( 'img', {
48+
src: settings.icon2,
49+
alt: 'eCash',
50+
style: {
51+
maxHeight: '24px',
52+
objectFit: 'contain'
53+
}
54+
} ) : null,
55+
56+
// 4. Fallback: If no icons are found, show text
57+
(!settings.icon && !settings.icon2) ? createElement( 'span', null, labelText ) : null
58+
);
59+
};
60+
61+
const Content = () => {
62+
return createElement( 'div', null, decodeEntities( settings.description || '' ) );
63+
};
64+
65+
registerPaymentMethod( {
66+
name: 'paybutton',
67+
label: createElement( LabelIconOnly ),
68+
content: createElement( Content ),
69+
edit: createElement( Content ),
70+
canMakePayment: () => true,
71+
ariaLabel: labelText,
72+
supports: {
73+
features: settings.supports,
74+
},
75+
} );
76+
})( window.wc, window.wp );

assets/js/paybutton-generator.js

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,6 @@
33
(function($) {
44
"use strict";
55

6-
/* ==========================================================================
7-
ADMIN WALLET ADDRESS VALIDATION
8-
==========================================================================
9-
*/
10-
if ($('#pbGenTo').length) {
11-
12-
const $toField = $('#pbGenTo');
13-
let $validationMsg;
14-
15-
if (!$('#pbGenToValidationResult').length) {
16-
$toField.after('<p id="pbGenToValidationResult"></p>');
17-
}
18-
$validationMsg = $('#pbGenToValidationResult');
19-
20-
$toField.on('input', function() {
21-
const address = $toField.val().trim();
22-
23-
if (!address) {
24-
$validationMsg.text('').css('color', '');
25-
return;
26-
}
27-
28-
const valid = window.cashaddrExports && window.cashaddrExports.isValidCashAddress(address);
29-
if (typeof window.cashaddrExports === 'undefined') {
30-
console.error('[PayButton] addressValidator is missing or not loaded!');
31-
}
32-
33-
if (valid) {
34-
$validationMsg.text('✅ Valid address').css('color', 'green');
35-
} else {
36-
$validationMsg.text('❌ Invalid address').css('color', 'red');
37-
}
38-
});
39-
40-
// Trigger input event on page load to validate pre-set value (from Paywall Settings).
41-
$toField.trigger('input');
42-
}
43-
446
/* ==========================================================================
457
BUTTON GENERATOR LOGIC
468
==========================================================================

assets/js/paybutton-woo.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* WooCommerce PayButton Integration JS
3+
*/
4+
jQuery(document).ready(function($) {
5+
$('.paybutton-woo-container').each(function() {
6+
var $container = $(this);
7+
var configData = $container.data('config');
8+
9+
if (typeof configData === 'string') {
10+
try { configData = JSON.parse(configData); }
11+
catch (e) { return; }
12+
}
13+
14+
let paymentInitiated = false;
15+
16+
function showOverlay(msg) {
17+
const el = document.getElementById('paybutton_overlay');
18+
if (el) {
19+
document.getElementById('paybutton_overlay_text').innerText = msg;
20+
el.style.display = 'block';
21+
}
22+
}
23+
24+
function pollOrderStatus() {
25+
setInterval(function() {
26+
$.ajax({
27+
url: PaywallAjax.ajaxUrl,
28+
method: 'POST',
29+
data: {
30+
action: 'paybutton_check_order_status',
31+
security: PaywallAjax.nonce,
32+
order_id: configData.opReturn
33+
},
34+
success: function(response) {
35+
if (response.success) {
36+
// Success! Reload to hide button and show receipt
37+
location.reload();
38+
}
39+
}
40+
});
41+
}, 3000);
42+
}
43+
44+
PayButton.render($container[0], {
45+
...configData,
46+
onSuccess: function(tx) {
47+
paymentInitiated = true;
48+
pollOrderStatus();
49+
},
50+
onClose: function() {
51+
if (paymentInitiated) {
52+
showOverlay("Verifying Payment...");
53+
}
54+
}
55+
});
56+
});
57+
});

includes/class-paybutton-activator.php

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,11 @@ public static function activate() {
1919
self::create_profile_page();
2020
// Set a flag to redirect the admin to the Paywall Settings page after activation
2121
update_option('paybutton_activation_redirect', true);
22-
self::migrate_old_option();
22+
//self::migrate_old_option();
2323
}
2424

2525
private static function migrate_old_option() {
26-
// --- 1. unlocked‑indicator colours ---
27-
$txt_old = get_option( 'paybutton_unlocked_indicator_text_color', '' );
28-
$txt_new = get_option( 'paybutton_unlocked_indicator_color', '' );
29-
if ( ! empty( $txt_old ) && empty( $txt_new ) ) {
30-
update_option( 'paybutton_unlocked_indicator_color', $txt_old );
31-
delete_option( 'paybutton_unlocked_indicator_text_color' );
32-
}
26+
// Empty function for future use
3327
}
3428

3529
/**
@@ -55,7 +49,7 @@ public static function create_tables() {
5549
PRIMARY KEY (id),
5650
KEY pb_paywall_user_wallet_address_idx (pb_paywall_user_wallet_address),
5751
KEY post_id_idx (post_id),
58-
KEY tx_hash_idx (tx_hash),
52+
UNIQUE KEY tx_hash_idx (tx_hash),
5953
KEY unlock_token_idx (unlock_token)
6054
) $charset_collate;";
6155

@@ -75,7 +69,7 @@ public static function create_tables() {
7569
used TINYINT(1) NOT NULL DEFAULT 0,
7670
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
7771
PRIMARY KEY (id),
78-
KEY tx_hash_idx (tx_hash),
72+
UNIQUE KEY tx_hash_idx (tx_hash),
7973
KEY wallet_addr_idx (wallet_address(190)),
8074
KEY used_idx (used),
8175
KEY login_token_idx (login_token)

0 commit comments

Comments
 (0)