-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwpforms-paystack.php
More file actions
472 lines (429 loc) · 16.3 KB
/
wpforms-paystack.php
File metadata and controls
472 lines (429 loc) · 16.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
<?php
/**
* Plugin Name: WPForms Paystack Gateway
* Plugin URI: https://theophilusadegbohungbe.com/plugins/wpforms-paystack
* Description: Paystack payment gateway integration for WPForms Pro. Supports one-time payments, multi-currency (NGN, GHS, ZAR, USD, KES, EGP, XOF, RWF), and webhook verification. Requires WPForms Pro.
* Version: 1.0.1
* Author: Theophilus Adegbohungbe
* Author URI: https://theophilusadegbohungbe.com
* License: GPL-2.0-or-later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: wpforms-paystack
* Domain Path: /languages
* Requires at least: 5.8
* Requires PHP: 7.4
* WPForms requires: 1.7.0 Pro
*
* @package WPFormsPaystack
* @author Theophilus Adegbohungbe <hello@theophilusadegbohungbe.com>
* @copyright 2024 Theophilus Adegbohungbe
* @link https://theophilusadegbohungbe.com
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
// Plugin constants.
define( 'WPFORMS_PAYSTACK_VERSION', '1.0.1' );
define( 'WPFORMS_PAYSTACK_FILE', __FILE__ );
define( 'WPFORMS_PAYSTACK_PATH', plugin_dir_path( __FILE__ ) );
define( 'WPFORMS_PAYSTACK_URL', plugin_dir_url( __FILE__ ) );
define( 'WPFORMS_PAYSTACK_MIN_WPFORMS', '1.7.0' );
/**
* Main plugin bootstrap class.
*
* @since 1.0.0
*/
final class WPForms_Paystack {
/**
* Singleton instance.
*
* @since 1.0.0
* @var WPForms_Paystack
*/
private static $instance = null;
/**
* Get singleton instance.
*
* @since 1.0.0
* @return WPForms_Paystack
*/
public static function instance() {
if ( is_null( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor.
*
* @since 1.0.0
*/
private function __construct() {
add_action( 'plugins_loaded', array( $this, 'init' ) );
register_activation_hook( WPFORMS_PAYSTACK_FILE, array( $this, 'activate' ) );
register_deactivation_hook( WPFORMS_PAYSTACK_FILE, array( $this, 'deactivate' ) );
}
/**
* Initialise plugin after plugins are loaded.
*
* @since 1.0.0
*/
public function init() {
// Check WPForms is active.
if ( ! $this->check_wpforms() ) {
return;
}
$this->load_textdomain();
$this->includes();
$this->hooks();
/**
* Fires after the plugin is fully initialised.
*
* @since 1.0.0
*/
do_action( 'wpforms_paystack_loaded' );
}
/**
* Check WPForms is active and meets the minimum version.
*
* @since 1.0.0
* @return bool
*/
private function check_wpforms() {
if ( ! class_exists( 'WPForms' ) && ! class_exists( 'WPForms_Lite' ) ) {
add_action( 'admin_notices', array( $this, 'notice_wpforms_missing' ) );
return false;
}
$wpforms_version = defined( 'WPFORMS_VERSION' ) ? WPFORMS_VERSION : '0';
if ( version_compare( $wpforms_version, WPFORMS_PAYSTACK_MIN_WPFORMS, '<' ) ) {
add_action( 'admin_notices', array( $this, 'notice_wpforms_outdated' ) );
return false;
}
return true;
}
/**
* Load plugin text domain.
*
* @since 1.0.0
*/
private function load_textdomain() {
load_plugin_textdomain(
'wpforms-paystack',
false,
dirname( plugin_basename( WPFORMS_PAYSTACK_FILE ) ) . '/languages'
);
}
/**
* Include required files.
*
* @since 1.0.0
*/
private function includes() {
require_once WPFORMS_PAYSTACK_PATH . 'includes/functions.php';
require_once WPFORMS_PAYSTACK_PATH . 'includes/class-paystack-gateway.php';
require_once WPFORMS_PAYSTACK_PATH . 'includes/class-paystack-webhook.php';
require_once WPFORMS_PAYSTACK_PATH . 'includes/class-paystack-admin.php';
// Register the Paystack draggable field in the Payment Fields builder panel.
require_once WPFORMS_PAYSTACK_PATH . 'includes/class-paystack-field.php';
// Instantiate the gateway once so its constructor hooks (process_payment,
// AJAX handlers, builder sections) are all registered.
new WPForms_Paystack_Gateway();
}
/**
* Register hooks.
*
* @since 1.0.0
*/
private function hooks() {
// Register the gateway with WPForms.
add_filter( 'wpforms_payments_available', array( $this, 'register_gateway' ) );
// Inject Paystack-supported currencies into the WPForms global currency list.
add_filter( 'wpforms_currencies', array( $this, 'register_currencies' ) );
// Register the Paystack field (Payment Fields panel button).
add_action( 'wpforms_loaded', array( $this, 'register_field' ) );
// Enqueue frontend assets.
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_assets' ) );
// Enqueue admin assets.
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
// Initialise webhook listener.
add_action( 'init', array( 'WPForms_Paystack_Webhook', 'listen' ) );
}
/**
* Register Paystack gateway with WPForms.
*
* @since 1.0.0
* @param array $gateways Existing gateways.
* @return array
*/
/**
* Inject Paystack-supported currencies into the WPForms global currency
* selector (WPForms → Settings → Payments → Currency dropdown).
*
* WPForms uses the wpforms_currencies filter to build that dropdown.
* Each entry must follow the format WPForms expects:
* 'CODE' => array( 'name', 'symbol', 'symbol_pos', 'thousands_sep',
* 'decimal_sep', 'decimals' )
*
* @since 1.0.0
*
* @param array $currencies Existing WPForms currencies.
* @return array
*/
public function register_currencies( $currencies ) {
$paystack_currencies = array(
'NGN' => array(
'name' => esc_html__( 'Nigerian Naira', 'wpforms-paystack' ),
'symbol' => '₦',
'symbol_pos' => 'left',
'thousands_sep' => ',',
'decimal_sep' => '.',
'decimals' => 2,
),
'GHS' => array(
'name' => esc_html__( 'Ghanaian Cedi', 'wpforms-paystack' ),
'symbol' => '₵',
'symbol_pos' => 'left',
'thousands_sep' => ',',
'decimal_sep' => '.',
'decimals' => 2,
),
'ZAR' => array(
'name' => esc_html__( 'South African Rand', 'wpforms-paystack' ),
'symbol' => 'R',
'symbol_pos' => 'left',
'thousands_sep' => ',',
'decimal_sep' => '.',
'decimals' => 2,
),
'KES' => array(
'name' => esc_html__( 'Kenyan Shilling', 'wpforms-paystack' ),
'symbol' => 'KSh',
'symbol_pos' => 'left',
'thousands_sep' => ',',
'decimal_sep' => '.',
'decimals' => 2,
),
'EGP' => array(
'name' => esc_html__( 'Egyptian Pound', 'wpforms-paystack' ),
'symbol' => 'E£',
'symbol_pos' => 'left',
'thousands_sep' => ',',
'decimal_sep' => '.',
'decimals' => 2,
),
'XOF' => array(
'name' => esc_html__( 'West African CFA Franc', 'wpforms-paystack' ),
'symbol' => 'CFA',
'symbol_pos' => 'left',
'thousands_sep' => ',',
'decimal_sep' => '.',
'decimals' => 0,
),
'RWF' => array(
'name' => esc_html__( 'Rwandan Franc', 'wpforms-paystack' ),
'symbol' => 'Fr',
'symbol_pos' => 'left',
'thousands_sep' => ',',
'decimal_sep' => '.',
'decimals' => 0,
),
);
// Merge — Paystack currencies come first so they are easy to find.
return array_merge( $paystack_currencies, $currencies );
}
public function register_gateway( $gateways ) {
// WPForms Pro iterates $gateways and calls new $class_name() on each string.
// WPForms Lite does not use this filter but our gateway self-registers via
// its constructor hooks when included, so both paths are covered.
$gateways['paystack'] = 'WPForms_Paystack_Gateway';
return $gateways;
}
/**
* Instantiate the WPForms_Field_Paystack class so WPForms registers the
* "Paystack" button in the Payment Fields panel of the form builder.
*
* WPForms auto-discovers field classes when they are instantiated after the
* wpforms_loaded action fires. The parent WPForms_Field constructor calls
* wpforms()->fields->register( $this ) internally.
*
* @since 1.0.0
*/
public function register_field() {
if ( class_exists( 'WPForms_Field' ) ) {
new WPForms_Field_Paystack();
}
}
/**
* Enqueue frontend scripts and styles.
*
* @since 1.0.0
*/
/**
* Check whether the current page has at least one WPForms form that has
* Paystack enabled, so we only load scripts when truly needed.
*
* Falls back to TRUE when we cannot determine at enqueue time (e.g. forms
* loaded via shortcode in a widget or block), letting the JS self-abort.
*
* @since 1.0.0
* @return bool
*/
private function page_has_paystack_form() {
global $post;
if ( ! $post instanceof WP_Post ) {
return true; // Cannot determine — load anyway to be safe.
}
// Quick scan: does the post content reference [wpforms id="..."]?
if ( ! has_shortcode( $post->post_content, 'wpforms' ) && ! has_block( 'wpforms/form-selector', $post ) ) {
return false;
}
// Check all forms on this post for Paystack being enabled.
preg_match_all( '/\[wpforms[^\]]+id=["\']?(\d+)["\']?/i', $post->post_content, $matches );
$form_ids = $matches[1] ?? array();
foreach ( $form_ids as $form_id ) {
$form_data = wpforms()->form->get( absint( $form_id ), array( 'content_only' => true ) );
if ( ! empty( $form_data['payments']['paystack']['enable'] ) ) {
return true;
}
}
// Block editor: trust that wpforms block might use Paystack.
if ( has_block( 'wpforms/form-selector', $post ) ) {
return true;
}
return false;
}
public function enqueue_frontend_assets() {
if ( ! wpforms_is_amp() ) {
wp_enqueue_style(
'wpforms-paystack',
WPFORMS_PAYSTACK_URL . 'assets/css/wpforms-paystack.css',
array(),
WPFORMS_PAYSTACK_VERSION
);
wp_enqueue_script(
'paystack-inline',
'https://js.paystack.co/v2/inline.js',
array(),
null,
true
);
wp_enqueue_script(
'wpforms-paystack',
WPFORMS_PAYSTACK_URL . 'assets/js/wpforms-paystack.js',
array( 'jquery', 'paystack-inline' ),
WPFORMS_PAYSTACK_VERSION,
true
);
wp_localize_script(
'wpforms-paystack',
'wpformsPaystack',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'wpforms_paystack_nonce' ),
'i18n' => array(
'processing' => esc_html__( 'Opening payment window…', 'wpforms-paystack' ),
'verifying' => esc_html__( 'Verifying payment, please wait…', 'wpforms-paystack' ),
'error' => esc_html__( 'Payment failed. Please try again.', 'wpforms-paystack' ),
'cancelled' => esc_html__( 'Payment was cancelled. Please try again.', 'wpforms-paystack' ),
'emailRequired' => esc_html__( 'Please enter your email address before paying.', 'wpforms-paystack' ),
'amountRequired' => esc_html__( 'A payment amount greater than zero is required.', 'wpforms-paystack' ),
),
)
);
}
}
/**
* Enqueue admin scripts and styles.
*
* @since 1.0.0
* @param string $hook Current admin page hook.
*/
public function enqueue_admin_assets( $hook ) {
// Load on: settings, overview, and the form builder (where Payment Fields panel lives).
$allowed_hooks = array(
'wpforms_page_wpforms-settings',
'toplevel_page_wpforms-overview',
'wpforms_page_wpforms-builder',
);
if ( ! in_array( $hook, $allowed_hooks, true ) ) {
return;
}
wp_enqueue_style(
'wpforms-paystack-admin',
WPFORMS_PAYSTACK_URL . 'assets/css/wpforms-paystack-admin.css',
array(),
WPFORMS_PAYSTACK_VERSION
);
}
/**
* Plugin activation routine.
*
* @since 1.0.0
*/
public function activate() {
// Create a custom DB table for payment logs.
global $wpdb;
$table_name = $wpdb->prefix . 'wpforms_paystack_log';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
form_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
entry_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
reference VARCHAR(100) NOT NULL DEFAULT '',
amount BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
currency VARCHAR(10) NOT NULL DEFAULT 'NGN',
status VARCHAR(30) NOT NULL DEFAULT 'pending',
customer_email VARCHAR(200) NOT NULL DEFAULT '',
meta LONGTEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY form_id (form_id),
KEY entry_id (entry_id),
KEY reference (reference),
KEY status (status)
) {$charset_collate};";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );
update_option( 'wpforms_paystack_db_version', WPFORMS_PAYSTACK_VERSION );
}
/**
* Plugin deactivation routine.
*
* @since 1.0.0
*/
public function deactivate() {
// Clean up scheduled events if any.
wp_clear_scheduled_hook( 'wpforms_paystack_verify_pending' );
}
/**
* Admin notice: WPForms missing.
*
* @since 1.0.0
*/
public function notice_wpforms_missing() {
echo '<div class="notice notice-error"><p>' .
sprintf(
/* translators: %s: WPForms plugin link */
esc_html__( 'WPForms Paystack requires %s to be installed and activated.', 'wpforms-paystack' ),
'<strong><a href="https://wordpress.org/plugins/wpforms-lite/" target="_blank">WPForms</a></strong>'
) .
'</p></div>';
}
/**
* Admin notice: WPForms outdated.
*
* @since 1.0.0
*/
public function notice_wpforms_outdated() {
echo '<div class="notice notice-warning"><p>' .
sprintf(
/* translators: %s: minimum version */
esc_html__( 'WPForms Paystack requires WPForms version %s or higher. Please update WPForms.', 'wpforms-paystack' ),
'<strong>' . esc_html( WPFORMS_PAYSTACK_MIN_WPFORMS ) . '</strong>'
) .
'</p></div>';
}
}
// Boot the plugin.
WPForms_Paystack::instance();