diff --git a/.DS_Store b/.DS_Store
deleted file mode 100644
index 8246bd1..0000000
Binary files a/.DS_Store and /dev/null differ
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4f7599c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+# macOS
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+apdisk
diff --git a/class-gf-paystack-api.php b/class-gf-paystack-api.php
index 6827632..d3a5be8 100644
--- a/class-gf-paystack-api.php
+++ b/class-gf-paystack-api.php
@@ -1,7 +1,14 @@
$this->plugin_name,
- // 'public_key' => $this->public_key,
- // 'transaction_reference' => $reference
- // ];
-
$params = [
'plugin_name' => 'pstk-gravityforms',
'public_key' => $this->public_key,
@@ -55,7 +56,8 @@ public function log_transaction_success($reference)
* @param string $method API request method
* @param string $domain API request uri
*
- * @return object|null JSON decoded transaction object. NULL on API error.
+ * @return array JSON decoded transaction object.
+ * @throws Exception on API error.
*/
public function send_request(
$endpoint,
@@ -91,19 +93,27 @@ public function send_request(
} else { // Un-decipherable message
throw new Exception(sprintf(__('There was an issue connecting with the payment processor. Try again later.', 'gravityformspaystack'), $this->name));
}
-
- return false;
}
/**
* Validate Webhook Signature
*
- * @param $input
- * @return boolean
+ * Uses hash_equals() for timing-safe comparison to prevent timing attacks.
+ *
+ * @param string $input Raw request body
+ * @return boolean True if signature is valid, false otherwise
*/
public function validate_webhook($input)
{
- return $_SERVER['HTTP_X_PAYSTACK_SIGNATURE'] == hash_hmac('sha512', $input, $this->secret_key);
+ $signature = isset($_SERVER['HTTP_X_PAYSTACK_SIGNATURE']) ? $_SERVER['HTTP_X_PAYSTACK_SIGNATURE'] : '';
+
+ if (empty($signature)) {
+ return false;
+ }
+
+ $expected = hash_hmac('sha512', $input, $this->secret_key);
+
+ return hash_equals($expected, $signature);
}
/**
diff --git a/class-gf-paystack.php b/class-gf-paystack.php
index 0627d9b..dbaca85 100644
--- a/class-gf-paystack.php
+++ b/class-gf-paystack.php
@@ -1,7 +1,20 @@
-
-
-
+
+
-
-
-
+
+ -
+
+
https://dashboard.paystack.com/#/settings/developer
+
+ -
+
+
get_webhook_url($this->get_current_feed_id())); ?>
+
+ -
+
+
+
array('label' => esc_html__('Hourly', 'gravityformspaystack')),
- 'daily' => array('label' => esc_html__('Daily', 'gravityformspaystack')),
- 'weekly' => array('label' => esc_html__('Weekly', 'gravityformspaystack')),
- 'monthly' => array('label' => esc_html__('Monthly', 'gravityformspaystack')),
- 'annually' => array('label' => esc_html__('Annually', 'gravityformspaystack')),
+ 'hourly' => array('label' => esc_html__('Hourly', 'gravityformspaystack')),
+ 'daily' => array('label' => esc_html__('Daily', 'gravityformspaystack')),
+ 'weekly' => array('label' => esc_html__('Weekly', 'gravityformspaystack')),
+ 'monthly' => array('label' => esc_html__('Monthly', 'gravityformspaystack')),
+ 'quarterly' => array('label' => esc_html__('Quarterly', 'gravityformspaystack')),
'biannually' => array('label' => esc_html__('Biannually', 'gravityformspaystack')),
+ 'annually' => array('label' => esc_html__('Annually', 'gravityformspaystack')),
);
}
@@ -933,11 +942,11 @@ public function redirect_url($feed, $submission_data, $form, $entry)
// 'value' => $this->paystack_api->plugin_name
// ];
- $custom_data[] = [
+ $custom_data[] = array(
'display_name' => 'Plugin Name',
'variable_name' => 'plugin_name',
'value' => 'pstk-gravityforms'
- ];
+ );
// Generate transaction reference
$reference = uniqid("gf-{$entry['id']}-");
@@ -984,7 +993,7 @@ public function redirect_url($feed, $submission_data, $form, $entry)
$args['invoice_limit'] = (int) $invoice_limit;
}
- $args['channels'] = ['card'];
+ $args['channels'] = array('card');
gform_update_meta($entry['id'], 'paystack_plan_code', $plan['plan_code']);
}
@@ -1009,7 +1018,7 @@ public function redirect_url($feed, $submission_data, $form, $entry)
public function get_fields_meta_data($feed, $entry, $fields)
{
- $data = [];
+ $data = array();
foreach ($fields as $field) {
$field_id = $feed['meta'][$field['meta_name']];
@@ -1160,7 +1169,7 @@ public function maybe_thankyou_page()
$reference = sanitize_text_field(rgget('reference'));
try {
- $response = $this->paystack_api->send_request("transaction/verify/{$reference}", [], 'get');
+ $response = $this->paystack_api->send_request("transaction/verify/{$reference}", array(), 'get');
$this->log_debug(__METHOD__ . "(): Transaction verified. " . print_r($response, 1));
} catch (\Exception $e) {
@@ -1224,6 +1233,9 @@ public function maybe_thankyou_page()
*/
public function callback()
{
+ // Initialize action array to prevent undefined variable errors
+ $action = array();
+
if (!$this->is_gravityforms_supported()) {
return;
}
@@ -1245,9 +1257,10 @@ public function callback()
return false;
}
- $entry_id = rgars($event, 'data/metadata/entry_id');
+ // Sanitize entry_id from webhook metadata
+ $entry_id = absint(rgars($event, 'data/metadata/entry_id'));
- if (!$entry_id && $reference = rgars($event, 'data/reference')) {
+ if (!$entry_id && $reference = sanitize_text_field(rgars($event, 'data/reference'))) {
$entry_id = $this->get_entry_id_by_reference($reference);
}
@@ -1330,13 +1343,13 @@ public function callback()
$action['payment_method'] = $this->_slug;
$action['ready_to_fulfill'] = !$entry['is_fulfilled'] ? true : false;
- $action['add_subscription_payment'] = [
+ $action['add_subscription_payment'] = array(
'entry_id' => $entry_id,
'subscription_id' => rgars($subscription, 'data/subscription_code'),
'transaction_id' => rgar($transaction, 'id'),
'amount' => $this->get_amount_import(rgar($transaction, 'amount'), rgar($entry, 'currency')),
'payment_method' => $this->_slug
- ];
+ );
break;
case 'subscription.disable':
@@ -1429,7 +1442,7 @@ public function callback()
$transaction = $subscription['data']['invoices'][$key];
} else {
- $transaction = $this->paystack_api->send_request("transaction/verify/{$reference}", [], 'get');
+ $transaction = $this->paystack_api->send_request("transaction/verify/{$reference}", array(), 'get');
}
$action['type'] = 'add_subscription_payment';
@@ -1581,7 +1594,7 @@ public function is_webhook_enabled()
public function get_plan($plan_id_or_code)
{
// Get Paystack plan.
- $response = (object) $this->paystack_api->send_request("plan/{$plan_id_or_code}", [], 'get');
+ $response = (object) $this->paystack_api->send_request("plan/{$plan_id_or_code}", array(), 'get');
$plan = $response->data;
@@ -1612,7 +1625,7 @@ public function create_plan($feed, $payment_amount, $currency)
$amount = $this->get_amount_export($payment_amount, $currency);
$recurring_times = (int) rgar($feed['meta'], 'recurringTimes');
- $send_invoices = (int) rgar($feed['meta'], 'sendInvoices') == 1 ? 'true' : 'false';
+ $send_invoices = (bool) rgar($feed['meta'], 'sendInvoices');
$args = array(
'name' => $name,
@@ -1662,7 +1675,7 @@ public function get_subscription($subscription_id_or_code)
$this->log_debug(__METHOD__ . '(): Getting subscription ' . $subscription_id_or_code);
try {
- $subscription = $this->paystack_api->send_request("subscription/{$subscription_id_or_code}", [], 'get');
+ $subscription = $this->paystack_api->send_request("subscription/{$subscription_id_or_code}", array(), 'get');
} catch (\Exception $e) {
$this->log_error(__METHOD__ . '(): Unable to get subscription. Reason: ' . $e->getMessage());
@@ -1713,11 +1726,11 @@ public function get_paystack_meta_data($feed, $entry, $form)
$field_value = substr($field_value, 0, 500);
// Add to metadata array.
- $metadata[] = [
+ $metadata[] = array(
'display_name' => $meta['custom_key'],
'variable_name' => sanitize_title($meta['custom_key']),
'value' => $field_value,
- ];
+ );
}
}
diff --git a/paystack.php b/paystack.php
index ff17b8f..37e093a 100644
--- a/paystack.php
+++ b/paystack.php
@@ -32,11 +32,21 @@
defined('ABSPATH') || die();
define('GF_PAYSTACK_VERSION', '2.0.6');
+define('GF_PAYSTACK_MIN_GF_VERSION', '2.0');
+// Register the addon - only runs if Gravity Forms is loaded
add_action('gform_loaded', array('GF_Paystack_Bootstrap', 'load'), 5);
+// Show admin notice if Gravity Forms is not active
+add_action('admin_notices', array('GF_Paystack_Bootstrap', 'admin_notice'));
+
class GF_Paystack_Bootstrap
{
+ /**
+ * Load the add-on if Gravity Forms is available.
+ *
+ * @return void
+ */
public static function load()
{
if (!method_exists('GFForms', 'include_payment_addon_framework')) {
@@ -44,14 +54,53 @@ public static function load()
}
require_once('class-gf-paystack.php');
-
require_once('class-gf-paystack-api.php');
GFAddOn::register('GFPaystack');
}
+
+ /**
+ * Display admin notice if Gravity Forms is not active.
+ *
+ * @return void
+ */
+ public static function admin_notice()
+ {
+ // Only show on plugins page or dashboard
+ $screen = get_current_screen();
+ if (!$screen || !in_array($screen->id, array('plugins', 'plugins-network', 'dashboard'))) {
+ return;
+ }
+
+ // Check if Gravity Forms is active
+ if (!class_exists('GFForms')) {
+ printf(
+ 'Paystack for Gravity Forms requires Gravity Forms to be installed and active. Get Gravity Forms
',
+ esc_url('https://www.gravityforms.com/')
+ );
+ return;
+ }
+
+ // Check if minimum version requirement is met
+ if (method_exists('GFForms', 'version') && version_compare(GFForms::version(), GF_PAYSTACK_MIN_GF_VERSION, '<')) {
+ printf(
+ 'Paystack for Gravity Forms requires Gravity Forms %s or higher. Please update Gravity Forms.
',
+ esc_html(GF_PAYSTACK_MIN_GF_VERSION)
+ );
+ return;
+ }
+ }
}
+/**
+ * Returns an instance of the GFPaystack class.
+ *
+ * @return GFPaystack|false
+ */
function gf_paystack()
{
+ if (!class_exists('GFPaystack')) {
+ return false;
+ }
return GFPaystack::get_instance();
}