Skip to content
This repository was archived by the owner on Apr 21, 2026. It is now read-only.

Commit 0cfaa02

Browse files
KONFeatureclaude
andcommitted
feat: add webhook setup and configuration feature
- Added Composer dependencies for Keccak hashing and elliptic curve cryptography - Created Frak_Webhook_Helper class for webhook management - Implemented webhook setup UI in admin settings page - Added webhook sending functionality to WooCommerce integration - Created admin JavaScript for webhook operations - Added webhook status checking and testing capabilities - Implemented webhook logging and statistics - Updated build script to include composer installation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 1b32ff2 commit 0cfaa02

9 files changed

Lines changed: 861 additions & 42 deletions

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
dist/
22
test/db_data
33
test/plugins
4-
test/wordpress
4+
test/wordpress
5+
vendor/
6+
composer.lock

admin/class-frak-admin.php

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ private function __construct() {
1515
add_action('admin_menu', array($this, 'add_admin_menu'));
1616
add_action('admin_init', array($this, 'register_settings'));
1717
add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts'));
18+
19+
// Add AJAX handlers for webhook operations
20+
add_action('wp_ajax_frak_generate_webhook_secret', array($this, 'ajax_generate_webhook_secret'));
21+
add_action('wp_ajax_frak_test_webhook', array($this, 'ajax_test_webhook'));
22+
add_action('wp_ajax_frak_clear_webhook_logs', array($this, 'ajax_clear_webhook_logs'));
23+
add_action('wp_ajax_frak_check_webhook_status', array($this, 'ajax_check_webhook_status'));
1824
}
1925

2026
public function add_admin_menu() {
@@ -39,6 +45,7 @@ public function register_settings() {
3945
register_setting('frak_settings', 'frak_enable_floating_button');
4046
register_setting('frak_settings', 'frak_show_reward');
4147
register_setting('frak_settings', 'frak_button_classname');
48+
register_setting('frak_settings', 'frak_webhook_secret');
4249
}
4350

4451
public function enqueue_scripts($hook) {
@@ -47,6 +54,11 @@ public function enqueue_scripts($hook) {
4754
}
4855

4956
wp_enqueue_code_editor(array('type' => 'text/javascript'));
57+
wp_enqueue_script('frak-admin', plugin_dir_url(dirname(__FILE__)) . 'admin/js/admin.js', array('jquery'), '1.0', true);
58+
wp_localize_script('frak-admin', 'frak_ajax', array(
59+
'ajax_url' => admin_url('admin-ajax.php'),
60+
'nonce' => wp_create_nonce('frak_ajax_nonce')
61+
));
5062

5163
wp_add_inline_style('wp-admin', '
5264
.frak-links {
@@ -63,6 +75,51 @@ public function enqueue_scripts($hook) {
6375
.frak-links a:hover {
6476
text-decoration: underline;
6577
}
78+
.frak-webhook-status {
79+
display: inline-block;
80+
padding: 3px 8px;
81+
border-radius: 3px;
82+
font-size: 12px;
83+
font-weight: 600;
84+
}
85+
.frak-webhook-status.status-active {
86+
background: #d4edda;
87+
color: #155724;
88+
}
89+
.frak-webhook-status.status-inactive {
90+
background: #f8d7da;
91+
color: #721c24;
92+
}
93+
.frak-stats-grid {
94+
display: grid;
95+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
96+
gap: 15px;
97+
margin: 20px 0;
98+
}
99+
.frak-stat-box {
100+
background: #fff;
101+
border: 1px solid #ccd0d4;
102+
padding: 15px;
103+
text-align: center;
104+
}
105+
.frak-stat-box h3 {
106+
margin: 0;
107+
font-size: 24px;
108+
}
109+
.frak-stat-box p {
110+
margin: 5px 0 0;
111+
color: #666;
112+
}
113+
.frak-webhook-logs table {
114+
margin-top: 20px;
115+
}
116+
.frak-webhook-section {
117+
margin-top: 30px;
118+
padding: 20px;
119+
background: #fff;
120+
border: 1px solid #ccd0d4;
121+
box-shadow: 0 1px 1px rgba(0,0,0,.04);
122+
}
66123
');
67124
}
68125

@@ -155,4 +212,71 @@ private function get_default_config($app_name, $logo_url) {
155212
};
156213
JS;
157214
}
215+
216+
// AJAX Handlers
217+
public function ajax_generate_webhook_secret() {
218+
check_ajax_referer('frak_ajax_nonce', 'nonce');
219+
220+
if (!current_user_can('manage_options')) {
221+
wp_die('Unauthorized');
222+
}
223+
224+
$secret = wp_generate_password(32, false);
225+
update_option('frak_webhook_secret', $secret);
226+
227+
wp_send_json_success(array(
228+
'secret' => $secret,
229+
'message' => __('Webhook secret generated successfully', 'frak')
230+
));
231+
}
232+
233+
public function ajax_test_webhook() {
234+
check_ajax_referer('frak_ajax_nonce', 'nonce');
235+
236+
if (!current_user_can('manage_options')) {
237+
wp_die('Unauthorized');
238+
}
239+
240+
$result = Frak_Webhook_Helper::test_webhook();
241+
242+
if ($result['success']) {
243+
wp_send_json_success(array(
244+
'message' => sprintf(__('Webhook test successful (%dms)', 'frak'), $result['execution_time']),
245+
'details' => $result
246+
));
247+
} else {
248+
wp_send_json_error(array(
249+
'message' => __('Webhook test failed: ', 'frak') . $result['error'],
250+
'details' => $result
251+
));
252+
}
253+
}
254+
255+
public function ajax_clear_webhook_logs() {
256+
check_ajax_referer('frak_ajax_nonce', 'nonce');
257+
258+
if (!current_user_can('manage_options')) {
259+
wp_die('Unauthorized');
260+
}
261+
262+
Frak_Webhook_Helper::clear_webhook_logs();
263+
264+
wp_send_json_success(array(
265+
'message' => __('Webhook logs cleared successfully', 'frak')
266+
));
267+
}
268+
269+
public function ajax_check_webhook_status() {
270+
check_ajax_referer('frak_ajax_nonce', 'nonce');
271+
272+
if (!current_user_can('manage_options')) {
273+
wp_die('Unauthorized');
274+
}
275+
276+
$status = Frak_Webhook_Helper::get_webhook_status();
277+
278+
wp_send_json_success(array(
279+
'status' => $status
280+
));
281+
}
158282
}

admin/js/admin.js

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
jQuery(document).ready(function($) {
2+
var editor;
3+
4+
// Initialize code editor if available
5+
if (typeof wp.codeEditor !== 'undefined' && $('#frak_custom_config').length) {
6+
var editorSettings = wp.codeEditor.defaultSettings ? _.clone(wp.codeEditor.defaultSettings) : {};
7+
editorSettings.codemirror = _.extend({}, editorSettings.codemirror, {
8+
indentUnit: 4,
9+
tabSize: 4,
10+
mode: 'javascript',
11+
lineNumbers: true,
12+
matchBrackets: true,
13+
autoCloseBrackets: true,
14+
extraKeys: {"Ctrl-Space": "autocomplete"},
15+
theme: 'default'
16+
});
17+
editor = wp.codeEditor.initialize($('#frak_custom_config'), editorSettings);
18+
}
19+
20+
// Update config function
21+
function updateConfig() {
22+
if (!editor) return;
23+
24+
var appName = $('#frak_app_name').val();
25+
var logoUrl = $('#frak_logo_url').val();
26+
var currentConfig = editor.codemirror.getValue();
27+
28+
currentConfig = currentConfig.replace(
29+
/(metadata:\s*{\s*name:\s*")[^"]*(")/,
30+
'$1' + appName + '$2'
31+
);
32+
33+
currentConfig = currentConfig.replace(
34+
/(logoUrl:\s*")[^"]*(")/,
35+
'$1' + logoUrl + '$2'
36+
);
37+
38+
editor.codemirror.setValue(currentConfig);
39+
}
40+
41+
// Toggle floating button settings
42+
function toggleFloatingButtonSettings() {
43+
var enabled = $('#frak_enable_floating_button').is(':checked');
44+
$('#frak_show_reward, #frak_button_classname').prop('disabled', !enabled);
45+
}
46+
47+
// Bind events
48+
$('#frak_app_name, #frak_logo_url').on('input', updateConfig);
49+
$('#frak_enable_floating_button').on('change', toggleFloatingButtonSettings);
50+
toggleFloatingButtonSettings();
51+
52+
// Generate webhook secret
53+
$('#generate-webhook-secret').on('click', function(e) {
54+
e.preventDefault();
55+
56+
if (!confirm('Are you sure you want to regenerate the webhook secret? This will break the integration if you have already configured it on Frak.')) {
57+
return;
58+
}
59+
60+
$.post(frak_ajax.ajax_url, {
61+
action: 'frak_generate_webhook_secret',
62+
nonce: frak_ajax.nonce
63+
}, function(response) {
64+
if (response.success) {
65+
$('#frak_webhook_secret').val(response.data.secret);
66+
showNotice(response.data.message, 'success');
67+
} else {
68+
showNotice('Error generating webhook secret', 'error');
69+
}
70+
});
71+
});
72+
73+
// Test webhook
74+
$('#test-webhook').on('click', function(e) {
75+
e.preventDefault();
76+
77+
var $button = $(this);
78+
$button.prop('disabled', true).text('Testing...');
79+
80+
$.post(frak_ajax.ajax_url, {
81+
action: 'frak_test_webhook',
82+
nonce: frak_ajax.nonce
83+
}, function(response) {
84+
if (response.success) {
85+
showNotice(response.data.message, 'success');
86+
} else {
87+
showNotice(response.data.message, 'error');
88+
}
89+
}).always(function() {
90+
$button.prop('disabled', false).text('Test Webhook');
91+
});
92+
});
93+
94+
// Clear webhook logs
95+
$('#clear-webhook-logs').on('click', function(e) {
96+
e.preventDefault();
97+
98+
if (!confirm('Are you sure you want to clear all webhook logs?')) {
99+
return;
100+
}
101+
102+
$.post(frak_ajax.ajax_url, {
103+
action: 'frak_clear_webhook_logs',
104+
nonce: frak_ajax.nonce
105+
}, function(response) {
106+
if (response.success) {
107+
showNotice(response.data.message, 'success');
108+
setTimeout(function() {
109+
location.reload();
110+
}, 1000);
111+
}
112+
});
113+
});
114+
115+
// Open webhook setup popup
116+
$('#open-webhook-popup').on('click', function(e) {
117+
e.preventDefault();
118+
119+
var productId = $(this).data('product-id');
120+
var webhookSecret = $('#frak_webhook_secret').val();
121+
122+
if (!webhookSecret) {
123+
alert('Please generate a webhook secret first');
124+
return;
125+
}
126+
127+
var createUrl = new URL('https://business.frak.id');
128+
createUrl.pathname = '/embedded/purchase-tracker';
129+
createUrl.searchParams.append('pid', productId);
130+
createUrl.searchParams.append('s', webhookSecret);
131+
createUrl.searchParams.append('p', 'custom');
132+
133+
var openedWindow = window.open(
134+
createUrl.href,
135+
'frak-business',
136+
'menubar=no,status=no,scrollbars=no,fullscreen=no,width=500,height=800'
137+
);
138+
139+
if (openedWindow) {
140+
openedWindow.focus();
141+
142+
// Check when window is closed and refresh status
143+
var timer = setInterval(function() {
144+
if (openedWindow.closed) {
145+
clearInterval(timer);
146+
setTimeout(function() {
147+
checkWebhookStatus();
148+
}, 1000);
149+
}
150+
}, 500);
151+
}
152+
});
153+
154+
// Check webhook status
155+
function checkWebhookStatus() {
156+
$.post(frak_ajax.ajax_url, {
157+
action: 'frak_check_webhook_status',
158+
nonce: frak_ajax.nonce
159+
}, function(response) {
160+
if (response.success) {
161+
var $status = $('.frak-webhook-status');
162+
if (response.data.status) {
163+
$status.removeClass('status-inactive').addClass('status-active').text('Active');
164+
showNotice('Webhook is now active!', 'success');
165+
} else {
166+
$status.removeClass('status-active').addClass('status-inactive').text('Inactive');
167+
}
168+
}
169+
});
170+
}
171+
172+
// Show admin notice
173+
function showNotice(message, type) {
174+
var $notice = $('<div class="notice notice-' + type + ' is-dismissible"><p>' + message + '</p></div>');
175+
$('.wrap h1').after($notice);
176+
177+
// Auto dismiss after 5 seconds
178+
setTimeout(function() {
179+
$notice.fadeOut(function() {
180+
$(this).remove();
181+
});
182+
}, 5000);
183+
}
184+
});

0 commit comments

Comments
 (0)