)
define('ONEPAYCX_SUPPORT_URL', 'https://t.me/onepaycx_official'); // Telegram channel: merchant questions and support
define('ONEPAYCX_PLUGIN_FILE', __FILE__);
define('ONEPAYCX_MIN_PHP', '7.4');
define('ONEPAYCX_MIN_WC', '7.0');
/**
* Show a dismissible-free error notice in wp-admin. Used for every "we can't run
* here" case so the plugin degrades to a message instead of a fatal.
*/
function onepaycx_admin_notice($message) {
add_action('admin_notices', function () use ($message) {
echo '
' . esc_html($message) . '
';
});
}
/**
* Returns a human-readable reason the plugin can't run (PHP too old, WooCommerce
* missing or too old), or '' when the environment is fine.
*/
function onepaycx_requirements_error() {
if (version_compare(PHP_VERSION, ONEPAYCX_MIN_PHP, '<')) {
return sprintf('1pay.cx for WooCommerce requires PHP %s or newer — this server runs PHP %s. Ask your host to upgrade PHP.', ONEPAYCX_MIN_PHP, PHP_VERSION);
}
if (!class_exists('WooCommerce')) {
return '1pay.cx for WooCommerce requires WooCommerce to be installed and active.';
}
if (defined('WC_VERSION') && version_compare(WC_VERSION, ONEPAYCX_MIN_WC, '<')) {
return sprintf('1pay.cx for WooCommerce requires WooCommerce %s or newer — this store runs WooCommerce %s.', ONEPAYCX_MIN_WC, WC_VERSION);
}
return '';
}
/**
* True only if it's safe to declare a subclass of $parent_class — i.e. the class
* exists and every abstract method it declares is one we implement ($implemented).
*
* This is the guard a try/catch cannot provide: "class X contains abstract method
* Y and must be declared abstract" is a compile-time fatal thrown when our file is
* required, not a catchable exception. By inspecting the parent's abstract methods
* FIRST (via reflection, without loading our subclass), we skip loading entirely
* when a newer WooCommerce added a method we don't implement — turning a fatal into
* a no-op. This is what makes the plugin safe across WooCommerce versions.
*/
function onepaycx_can_extend($parent_class, array $implemented) {
if (!class_exists($parent_class)) {
return false;
}
try {
$ref = new ReflectionClass($parent_class);
foreach ($ref->getMethods(ReflectionMethod::IS_ABSTRACT) as $method) {
if (!in_array($method->getName(), $implemented, true)) {
return false; // parent requires a method we don't implement — don't risk a fatal
}
}
} catch (\Throwable $e) {
return false;
}
return true;
}
/**
* Refuse activation — with a clear message — when the environment can't support
* the plugin, instead of activating and then fataling on the next page load.
* This is what stops a bad environment from ever white-screening the store.
*/
register_activation_hook(__FILE__, function () {
if (version_compare(PHP_VERSION, ONEPAYCX_MIN_PHP, '<')) {
wp_die(
esc_html(sprintf('1pay.cx for WooCommerce needs PHP %s+ (this server has %s). Upgrade PHP in your hosting panel, then activate again.', ONEPAYCX_MIN_PHP, PHP_VERSION)),
'Activation blocked',
array('back_link' => true)
);
}
$wc_active = class_exists('WooCommerce');
if (!$wc_active && function_exists('is_plugin_active')) {
$wc_active = is_plugin_active('woocommerce/woocommerce.php');
}
if (!$wc_active) {
wp_die(
esc_html('1pay.cx for WooCommerce needs WooCommerce installed and active first. Activate WooCommerce, then activate this plugin.'),
'Activation blocked',
array('back_link' => true)
);
}
});
/**
* Boot the gateway. Everything that touches WooCommerce is loaded here, behind a
* requirements gate and wrapped in try/catch, so a version mismatch degrades to
* an admin notice — never a fatal that takes the site down.
*/
add_action('plugins_loaded', function () {
$error = onepaycx_requirements_error();
if ($error !== '') {
onepaycx_admin_notice($error);
return;
}
// Canonical gate: only declare our gateway once WooCommerce's base class is
// loaded AND it declares no abstract method we don't implement (guards against
// a future WooCommerce changing the gateway contract).
if (!onepaycx_can_extend('WC_Payment_Gateway', array('process_payment'))) {
onepaycx_admin_notice('1pay.cx for WooCommerce is not compatible with this version of WooCommerce. Please update the 1pay.cx plugin.');
return;
}
try {
require_once __DIR__ . '/includes/class-onepaycx-api.php';
require_once __DIR__ . '/includes/class-onepaycx-gateway.php';
} catch (\Throwable $e) {
onepaycx_admin_notice('1pay.cx for WooCommerce could not load: ' . $e->getMessage());
return;
}
add_filter('woocommerce_payment_gateways', function ($gateways) {
$gateways[] = 'OnePayCx_Gateway';
return $gateways;
});
// Settings screen only (both handlers check the screen and manage_woocommerce):
// finish the 1pay.cx account connect round trip, and show waiting payouts.
if (is_admin()) {
add_action('admin_init', array('OnePayCx_Gateway', 'maybe_handle_connect'));
add_action('admin_notices', array('OnePayCx_Gateway', 'render_admin_notices'));
// The settings panel's CSS/JS (that screen only) and its "Create free account" action.
add_action('admin_enqueue_scripts', array('OnePayCx_Gateway', 'enqueue_admin_assets'));
add_action('admin_post_onepaycx_claim', array('OnePayCx_Gateway', 'handle_claim_request'));
}
});
// Declare compatibility with WooCommerce High-Performance Order Storage (HPOS).
// Guard the method too — older WooCommerce has the class but not this method.
add_action('before_woocommerce_init', function () {
if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)
&& method_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class, 'declare_compatibility')) {
try {
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('custom_order_tables', ONEPAYCX_PLUGIN_FILE, true);
} catch (\Throwable $e) {
// Non-fatal: HPOS declaration is optional.
}
}
});
// Register the block-based checkout integration — optional, and fully isolated so
// a Blocks version mismatch can never break the classic checkout or the site.
add_action('woocommerce_blocks_loaded', function () {
// Pre-check the abstract contract before declaring our subclass. If this
// WooCommerce's AbstractPaymentMethodType requires a method we don't implement
// (the exact thing that fataled on WooCommerce 11), skip the block integration
// entirely — the classic-checkout gateway is unaffected. This CANNOT be done
// with try/catch, because the missing-method fatal isn't a catchable exception.
if (!onepaycx_can_extend(
'Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType',
array('initialize', 'is_active', 'get_payment_method_script_handles', 'get_payment_method_data')
)) {
return;
}
try {
require_once __DIR__ . '/includes/class-onepaycx-blocks.php';
add_action('woocommerce_blocks_payment_method_type_registration', function ($registry) {
try {
$registry->register(new OnePayCx_Blocks_Support());
} catch (\Throwable $e) {
// Block checkout integration is optional; never let it break the site.
}
});
} catch (\Throwable $e) {
// Ignore — the classic checkout gateway still works without blocks.
}
});
// "Settings" and "Telegram support" links on the Plugins list row, before Deactivate.
add_filter('plugin_action_links_' . plugin_basename(__FILE__), function ($links) {
$url = admin_url('admin.php?page=wc-settings&tab=checkout§ion=onepaycx');
array_unshift(
$links,
'' . esc_html__('Settings', '1pay-cx-payments') . '',
'' . esc_html__('Telegram support', '1pay-cx-payments') . ''
);
return $links;
});