PluginProbe ʕ •ᴥ•ʔ
Payment Gateway for Authorize.net for WooCommerce / 1.0.4
Payment Gateway for Authorize.net for WooCommerce v1.0.4
1.0.19 1.0.18 1.0.17 1.0.16 1.0.15 1.0.14 1.0.13 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9
payment-gateway-for-authorize-net-for-woocommerce / includes / class-easy-payment-authorizenet-gateway.php
payment-gateway-for-authorize-net-for-woocommerce / includes Last commit date
compatibility 8 months ago class-api-handler.php 8 months ago class-easy-payment-authorizenet-gateway.php 8 months ago class-webhook-handler.php 8 months ago
class-easy-payment-authorizenet-gateway.php
1359 lines
1 <?php
2 if (!defined('ABSPATH'))
3 exit;
4
5 class EASYAUTHNET_AuthorizeNet_Gateway extends WC_Payment_Gateway_CC {
6
7 const CARD_META_PREFIX = '_authorizenet_card_';
8 const CLIENT_KEY_CACHE_PREFIX = 'easyauthnet_authorizenet_client_key_';
9 const CLIENT_KEY_CACHE_TTL = HOUR_IN_SECONDS;
10
11 public $enabled;
12 public $title;
13 public $description;
14 public $environment;
15 public $tokenization;
16 public $transaction_type;
17 public $accepted_card_logos;
18 public $debug;
19 public $live_api_login_id;
20 public $live_transaction_key;
21 public $live_signature_key;
22 public $sandbox_api_login_id;
23 public $sandbox_transaction_key;
24 public $sandbox_signature_key;
25 public $api_login_id;
26 public $transaction_key;
27 public $signature_key;
28 public $client_key;
29 public $customer_profile_id;
30 private static $easyauthnet_notice_rendered = false;
31
32 protected const CUSTOMER_PROFILE_MIGRATION_FLAG = 'easyauthnet_authorizenet_profile_migration_v1';
33
34 public function __construct() {
35 $this->id = 'easyauthnet_authorizenet';
36 $this->method_title = __('Authorize.Net Credit Card - By Easy Payment', 'payment-gateway-for-authorize-net-for-woocommerce');
37 $this->method_description = __('Securely accept credit cards using Authorize.Net.', 'payment-gateway-for-authorize-net-for-woocommerce');
38 $this->has_fields = true;
39 $this->supports = [
40 'products',
41 'refunds',
42 'subscriptions',
43 'subscription_suspension',
44 'subscription_cancellation',
45 'subscription_reactivation',
46 'subscription_amount_changes',
47 'subscription_date_changes',
48 'multiple_subscriptions',
49 'subscription_payment_method_change_customer',
50 'subscription_payment_method_change_admin',
51 'pre-orders',
52 ];
53 $this->init_form_fields();
54 $this->init_settings();
55 $this->init_properties();
56 $this->init_hooks();
57 $this->maybe_run_customer_profile_migration_once();
58 }
59
60 protected function init_properties() {
61 $this->enabled = $this->get_option('enabled', 'no');
62 $this->title = $this->get_option('title', __('Credit / Debit Card', 'payment-gateway-for-authorize-net-for-woocommerce'));
63 $this->description = $this->get_option('description', __('Pay securely using your credit card.', 'payment-gateway-for-authorize-net-for-woocommerce'));
64 $this->environment = $this->get_option('environment', 'sandbox');
65 if ($this->environment === 'live') {
66 $this->customer_profile_id = EASYAUTHNET_AUTHORIZENET_CUSTOMER_PROFILE_ID . '_live';
67 } else {
68 $this->customer_profile_id = EASYAUTHNET_AUTHORIZENET_CUSTOMER_PROFILE_ID . '_sandbox';
69 }
70 $this->tokenization = $this->get_option('tokenization', 'no');
71 $this->transaction_type = $this->get_option('transaction_type', 'auth_capture');
72 $this->accepted_card_logos = $this->get_option('accepted_card_logos', ['visa', 'mastercard', 'amex', 'discover', 'jcb', 'diners']);
73 $this->debug = $this->get_option('debug', 'no');
74 $this->sandbox_api_login_id = $this->get_option('sandbox_api_login_id', '');
75 $this->sandbox_transaction_key = $this->get_option('sandbox_transaction_key', '');
76 $this->sandbox_signature_key = $this->get_option('sandbox_signature_key', '');
77 $this->live_api_login_id = $this->get_option('live_api_login_id', '');
78 $this->live_transaction_key = $this->get_option('live_transaction_key', '');
79 $this->live_signature_key = $this->get_option('live_signature_key', '');
80 if ($this->environment === 'live') {
81 $this->api_login_id = $this->live_api_login_id;
82 $this->transaction_key = $this->live_transaction_key;
83 $this->signature_key = $this->live_signature_key;
84 } else {
85 $this->api_login_id = $this->sandbox_api_login_id;
86 $this->transaction_key = $this->sandbox_transaction_key;
87 $this->signature_key = $this->sandbox_signature_key;
88 }
89 if (empty($this->client_key)) {
90 $this->init_client_key();
91 }
92 if ($this->tokenization === 'yes') {
93 $this->supports[] = 'tokenization';
94 }
95 }
96
97 protected function init_client_key() {
98 if (is_checkout() || is_checkout_pay_page() || is_account_page()) {
99 if (!empty($this->api_login_id) && !empty($this->transaction_key) && !empty($this->signature_key)) {
100 $merchant_data = EASYAUTHNET_AuthorizeNet_API_Handler::fetch_merchant_details($this->api_login_id, $this->transaction_key, $this->signature_key, $this->environment === 'sandbox');
101 if (!is_wp_error($merchant_data) && isset($merchant_data['publicClientKey'])) {
102 $this->client_key = $merchant_data['publicClientKey'];
103 $this->log("Successfully retrieved client key", [
104 'client_key' => $this->mask_sensitive_data($this->client_key)
105 ]);
106 } else {
107 $this->client_key = '';
108 $error_message = is_wp_error($merchant_data) ? $merchant_data->get_error_message() : 'No publicClientKey in response';
109 $this->log("Failed to retrieve client key", [
110 'error' => $error_message
111 ]);
112 }
113 } else {
114 $this->client_key = '';
115 $this->log("Missing credentials for client key initialization", [
116 'has_api_login_id' => !empty($this->api_login_id),
117 'has_transaction_key' => !empty($this->transaction_key),
118 'has_signature_key' => !empty($this->signature_key)
119 ]);
120 }
121 }
122 }
123
124 private function card_labels(): array {
125 return [
126 'visa' => _x('Visa', 'Name of credit card', 'payment-gateway-for-authorize-net-for-woocommerce'),
127 'mastercard' => _x('Mastercard', 'Name of credit card', 'payment-gateway-for-authorize-net-for-woocommerce'),
128 'maestro' => _x('Maestro', 'Name of credit card', 'payment-gateway-for-authorize-net-for-woocommerce'),
129 'amex' => _x('American Express', 'Name of credit card', 'payment-gateway-for-authorize-net-for-woocommerce'),
130 'discover' => _x('Discover', 'Name of credit card', 'payment-gateway-for-authorize-net-for-woocommerce'),
131 'jcb' => _x('JCB', 'Name of credit card', 'payment-gateway-for-authorize-net-for-woocommerce'),
132 'elo' => _x('Elo', 'Name of credit card', 'payment-gateway-for-authorize-net-for-woocommerce'),
133 'hiper' => _x('Hiper', 'Name of credit card', 'payment-gateway-for-authorize-net-for-woocommerce'),
134 'diners' => _x('Diners Club', 'Name of credit card', 'payment-gateway-for-authorize-net-for-woocommerce'),
135 ];
136 }
137
138 public function get_icon() {
139 $title_options = $this->card_labels();
140 $images = [];
141 $totalIcons = 0;
142 foreach ($title_options as $icon_key => $icon_value) {
143 if (in_array($icon_key, $this->accepted_card_logos)) {
144 $iconUrl = esc_url(EASYAUTHNET_AUTHORIZENET_PLUGIN_ASSET_URL) . 'assets/css/credit-cards/' . esc_attr($icon_key) . '.svg';
145 $iconTitle = esc_attr($icon_value);
146 // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage -- Gateway icons use static <img> tags for compatibility
147 $images[] = sprintf('<img title="%s" src="%s" class="easyauthnet-card-icon ae-icon-%s" /> ', $iconTitle, $iconUrl, $iconTitle);
148 $totalIcons++;
149 }
150 }
151 return implode('', $images) . '<div class="easyauthnet-wc-payment-gateway-card-icons"></div>';
152 }
153
154 protected function init_hooks() {
155 add_action('admin_enqueue_scripts', [$this, 'admin_scripts']);
156 add_action('wp_enqueue_scripts', [$this, 'public_scripts']);
157 add_action('woocommerce_update_options_payment_gateways_' . $this->id, [$this, 'process_admin_options']);
158 add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts']);
159 add_action('woocommerce_scheduled_subscription_payment_easyauthnet_authorizenet', [$this, 'process_scheduled_payment'], 10, 2);
160 add_filter('woocommerce_payment_gateway_get_tokenization_title', [$this, 'override_tokenization_title'], 10, 2);
161 add_action('woocommerce_order_action_easyauthnet_capture_authorized_payment', [$this, 'process_order_action_capture']);
162 add_filter('woocommerce_settings_api_sanitized_fields_' . $this->id, [$this, 'validate_authorizenet_credentials_on_save'], 999, 1);
163 add_action('admin_notices', [$this, 'easyauthnet_authorizenet_show_invalid_credentials_notice']);
164 add_action('admin_notices', array($this, 'easyauthnet_leaverev'));
165 add_action('wp_ajax_easyauthnet_handle_review_action', array($this, 'easyauthnet_handle_review_action'));
166 add_action('admin_enqueue_scripts', array($this, 'easyauthnet_enqueue_scripts'));
167 add_filter('safe_style_css', array($this, 'easyauthnet_allowed_css_properties'));
168 }
169
170 public function is_credentials_set() {
171 if (!empty($this->api_login_id) && !empty($this->transaction_key) && !empty($this->signature_key)) {
172 return true;
173 } else {
174 return false;
175 }
176 }
177
178 public function is_available() {
179 if ($this->is_credentials_set() && $this->enabled === 'yes') {
180 return true;
181 }
182 return false;
183 }
184
185 public function easyauthnet_authorizenet_show_invalid_credentials_notice() {
186 if ($msg = get_transient('easyauthnet_authorizenet_invalid_credentials_message')) {
187 delete_transient('easyauthnet_authorizenet_invalid_credentials_message');
188 echo '<div class="notice notice-error"><p>' . esc_html($msg) . '</p></div>';
189 }
190 }
191
192 public function validate_authorizenet_credentials_on_save($settings) {
193 $environment = ($settings['environment'] ?? 'sandbox') === 'live' ? 'live' : 'sandbox';
194 $api_login_id = sanitize_text_field($settings["{$environment}_api_login_id"] ?? '');
195 $transaction_key = sanitize_text_field($settings["{$environment}_transaction_key"] ?? '');
196 $signature_key = sanitize_text_field($settings["{$environment}_signature_key"] ?? '');
197 $is_enabled = $settings['enabled'] ?? 'no';
198 if ($is_enabled === 'yes' && (empty($api_login_id) || empty($transaction_key) || empty($signature_key))) {
199 $error_message = __('Please enter the API Login ID, Transaction Key, and Signature Key for Authorize.Net in the selected environment.', 'payment-gateway-for-authorize-net-for-woocommerce');
200 set_transient('easyauthnet_authorizenet_invalid_credentials_message', $error_message, 60);
201 ob_get_clean();
202 wp_safe_redirect(admin_url('admin.php?page=wc-settings&tab=checkout&section=easyauthnet_authorizenet'));
203 exit;
204 }
205 if (!empty($api_login_id) && !empty($transaction_key)) {
206 $is_valid = $this->attempt_authnet_credentials_validation($api_login_id, $transaction_key, $environment === 'sandbox');
207 if (!$is_valid) {
208 $error_message = __('The Authorize.Net API Login ID and Transaction Key you entered are invalid. Ensure you are using the correct credentials for the selected environment (Sandbox or Live).', 'payment-gateway-for-authorize-net-for-woocommerce');
209 set_transient('easyauthnet_authorizenet_invalid_credentials_message', $error_message, 60);
210 $settings["{$environment}_api_login_id"] = '';
211 $settings["{$environment}_transaction_key"] = '';
212 ob_get_clean();
213 wp_safe_redirect(admin_url('admin.php?page=wc-settings&tab=checkout&section=easyauthnet_authorizenet'));
214 exit;
215 }
216 }
217 return $settings;
218 }
219
220 protected function attempt_authnet_credentials_validation($api_login_id, $transaction_key, $is_sandbox = true) {
221 $url = $is_sandbox ? 'https://apitest.authorize.net/xml/v1/request.api' : 'https://api.authorize.net/xml/v1/request.api';
222
223 // Suppress XML warnings (like non-absolute namespace URIs)
224 libxml_use_internal_errors(true);
225
226 $root_element = 'getMerchantDetailsRequest';
227 $xml = new SimpleXMLElement(
228 '<?xml version="1.0" encoding="UTF-8"?>' .
229 "<$root_element xmlns=\"AnetApi/xml/v1/schema/AnetApiSchema.xsd\"></$root_element>"
230 );
231
232 $auth = $xml->addChild('merchantAuthentication');
233 $auth->addChild('name', $api_login_id);
234 $auth->addChild('transactionKey', $transaction_key);
235
236 $request_xml = $xml->asXML();
237
238 $response = wp_remote_post($url, [
239 'headers' => [
240 'Content-Type' => 'text/xml',
241 ],
242 'body' => $request_xml,
243 'timeout' => 30,
244 ]);
245
246 if (is_wp_error($response)) {
247 return false;
248 }
249
250 $body_raw = wp_remote_retrieve_body($response);
251
252 // Again suppress errors for response parsing
253 libxml_use_internal_errors(true);
254 $xml_response = simplexml_load_string($body_raw, 'SimpleXMLElement', LIBXML_NOCDATA);
255 if (!$xml_response) {
256 return false;
257 }
258
259 $json = json_decode(json_encode($xml_response), true);
260
261 return isset($json['messages']['resultCode']) && $json['messages']['resultCode'] === 'Ok';
262 }
263
264 public function admin_options() {
265 $return_url = admin_url('admin.php?page=wc-settings&tab=checkout');
266 if (function_exists('wc_back_header')) {
267 wc_back_header($this->get_method_title(), __('Return to payments', 'payment-gateway-for-authorize-net-for-woocommerce'), $return_url);
268 } else {
269 echo '<h2>' . esc_html($this->method_title) . '</h2>';
270 wc_back_link(
271 __('Return to payments', 'payment-gateway-for-authorize-net-for-woocommerce'),
272 admin_url('admin.php?page=wc-settings&tab=checkout')
273 );
274 }
275 echo wp_kses_post(wpautop($this->get_method_description()));
276 echo '<p>';
277 echo wp_kses(
278 __('Need help? Contact <a href="https://wordpress.org/support/plugin/payment-gateway-for-authorize-net-for-woocommerce/" target="_blank">support</a>.', 'payment-gateway-for-authorize-net-for-woocommerce'),
279 array('a' => array('href' => array(), 'target' => array()))
280 );
281 echo '</p>';
282 echo '<table class="form-table" style="display:none;">' . $this->generate_settings_html($this->get_form_fields(), false) . '</table>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is safe from WooCommerce Settings API.
283 }
284
285 public function process_admin_options() {
286 parent::process_admin_options();
287 $this->easyauthnet_register_anet_webhook_on_save();
288 }
289
290 public function process_scheduled_payment($amount, $order_id) {
291 $order = wc_get_order($order_id);
292 $this->log("Processing scheduled subscription payment", ['order_id' => $order_id, 'amount' => $amount, 'order_status' => $order ? $order->get_status() : 'invalid_order']);
293 if (!$order || !is_a($order, 'WC_Order')) {
294 $this->log("Invalid order for scheduled payment", ['order_id' => $order_id]);
295 return;
296 }
297 $token_id = null;
298 $tokens = $order->get_meta('_payment_tokens', true);
299 if (is_array($tokens) && !empty($tokens)) {
300 $token_id = $tokens[0];
301 $this->log("Found token ID from order meta", ['token_id' => $token_id]);
302 }
303 if (empty($token_id) && function_exists('wcs_get_subscriptions_for_renewal_order')) {
304 $subscriptions = wcs_get_subscriptions_for_renewal_order($order);
305 foreach ($subscriptions as $subscription) {
306 $tokens = $subscription->get_meta('_payment_tokens', true);
307 if (is_array($tokens) && !empty($tokens)) {
308 $token_id = $tokens[0];
309 $this->log("Found token ID from subscription meta", ['token_id' => $token_id, 'subscription_id' => $subscription->get_id()]);
310 break;
311 }
312 }
313 }
314 $token = $token_id ? WC_Payment_Tokens::get($token_id) : false;
315 $this->log("Token details for scheduled payment", ['token_exists' => (bool) $token, 'token_type' => $token ? $token->get_type() : 'none', 'token_last4' => $token ? $token->get_last4() : 'none', 'token_card_type' => $token ? $token->get_card_type() : 'none']);
316 if (!$token || !$token->get_token()) {
317 $this->log("No valid saved token found for scheduled payment", ['order_id' => $order_id]);
318 $order->update_status('failed', __('Subscription renewal failed: no saved payment method available.', 'payment-gateway-for-authorize-net-for-woocommerce'));
319 return;
320 }
321 $response = EASYAUTHNET_AuthorizeNet_API_Handler::charge_saved_token($order, $token);
322 if (is_wp_error($response)) {
323 $this->log("Failed to charge saved token for scheduled payment", ['error_code' => $response->get_error_code(), 'error_message' => $response->get_error_message()]);
324 // translators: %1$s is the error message returned during subscription renewal failure.
325 $order->update_status('failed', sprintf(__('Subscription renewal payment failed: %1$s', 'payment-gateway-for-authorize-net-for-woocommerce'), $response->get_error_message()));
326 return;
327 }
328 $transaction_id = $response['transaction_id'] ?? 'N/A';
329 $this->log("Successfully processed scheduled payment", ['transaction_id' => $transaction_id, 'response_code' => $response['response_code'] ?? null, 'auth_code' => $response['auth_code'] ?? null]);
330 EASYAUTHNET_AuthorizeNet_API_Handler::complete_payment($order, $transaction_id);
331 }
332
333 public function override_tokenization_title($title, $token) {
334 if ($token->get_gateway_id() === 'easyauthnet_authorizenet') {
335 // translators: 1: Card type (e.g., VISA), 2: Last 4 digits of the card.
336 $title = sprintf(__('Saved Card (%1$s ending in %2$s)', 'payment-gateway-for-authorize-net-for-woocommerce'), strtoupper($token->get_card_type()), $token->get_last4());
337 }
338 return $title;
339 }
340
341 public function enqueue_scripts() {
342 if (is_checkout() || is_account_page()) {
343 $url = $this->environment === 'live' ? 'https://js.authorize.net/v1/Accept.js' : 'https://jstest.authorize.net/v1/Accept.js';
344 $this->log("Enqueueing Accept.js script", ['environment' => $this->environment, 'url' => $url]);
345 wp_enqueue_script('wc-credit-card-form');
346 // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Third-party script, version controlled by Authorize.Net
347 wp_enqueue_script('easyauthnet_authorizenet_acceptjs', $url, [], null, true);
348 wp_enqueue_script('easyauthnet-acceptjs-handler', plugin_dir_url(__FILE__) . '../assets/js/acceptjs-handler.js', ['jquery', 'easyauthnet_authorizenet_acceptjs', 'wp-i18n', 'wc-credit-card-form'], EASYAUTHNET_AUTHORIZENET_VERSION, true);
349 wp_localize_script('easyauthnet-acceptjs-handler', 'easyauthnet_authorizenet_params', [
350 'client_key' => $this->client_key,
351 'login_id' => $this->api_login_id,
352 'environment' => $this->environment,
353 'debug' => ($this->debug === 'yes') ? true : false
354 ]);
355 }
356 }
357
358 public function public_scripts() {
359 wp_enqueue_style('easyauthnet-authorizenet-public', plugin_dir_url(__FILE__) . '../assets/css/public.css', [], EASYAUTHNET_AUTHORIZENET_VERSION);
360 }
361
362 public function admin_scripts() {
363 if (!$this->is_admin_settings_page()) {
364 return;
365 }
366 wp_enqueue_style('easyauthnet-authorizenet-admin', plugin_dir_url(__FILE__) . '../assets/css/admin.css', [], EASYAUTHNET_AUTHORIZENET_VERSION);
367 wp_enqueue_script('easyauthnet-authorizenet-admin-js', plugin_dir_url(__FILE__) . '../assets/js/easyauthnet-authorizenet-admin.js', ['jquery'], EASYAUTHNET_AUTHORIZENET_VERSION, true);
368 }
369
370 protected function is_admin_settings_page() {
371 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
372 return isset($_GET['page'], $_GET['section']) && 'wc-settings' === $_GET['page'] && $this->id === $_GET['section'];
373 }
374
375 public function generate_authorizenet_signup_banner_html($field_key, $data) {
376 if (isset($data['type']) && $data['type'] === 'authorizenet_signup_banner') {
377 $field_key = $this->get_field_key($field_key);
378 ob_start();
379 ?>
380 <tr valign="top">
381 <td class="forminp" id="<?php echo esc_attr($field_key); ?>">
382 <?php
383 $signup_url = 'https://account.authorize.net/signUpNow?resellerId=27457';
384 $direct_url = 'https://account.authorize.net/signUpNow?resellerId=27457';
385 ?>
386
387 <div class="easyauthnet-signup-box" role="region" aria-labelledby="<?php echo esc_attr($field_key); ?>-heading">
388 <!-- For Business Owners -->
389 <h4 id="<?php echo esc_attr($field_key); ?>-heading">
390 <span class="dashicons dashicons-businessperson" aria-hidden="true"></span>
391 <?php esc_html_e('For Business Owners', 'payment-gateway-for-authorize-net-for-woocommerce'); ?>
392 </h4>
393 <p class="description">
394 <?php esc_html_e('Sign up your business to start accepting payments with Authorize.Net.', 'payment-gateway-for-authorize-net-for-woocommerce'); ?>
395 </p>
396 <p>
397 <a href="<?php echo esc_url($signup_url); ?>"
398 class="button button-primary easyauthnet-register-btn"
399 target="_blank"
400 aria-label="<?php esc_attr_e('Sign up with Authorize.Net', 'payment-gateway-for-authorize-net-for-woocommerce'); ?>">
401 <?php esc_html_e('Sign Up with Authorize.Net', 'payment-gateway-for-authorize-net-for-woocommerce'); ?>
402 </a>
403 </p>
404
405 <hr />
406
407 <!-- For Developers -->
408 <h4>
409 <span class="dashicons dashicons-admin-links" aria-hidden="true"></span>
410 <?php esc_html_e('For Developers', 'payment-gateway-for-authorize-net-for-woocommerce'); ?>
411 </h4>
412 <p class="description">
413 <?php esc_html_e('Share this referral link with your client for easy sign-up.', 'payment-gateway-for-authorize-net-for-woocommerce'); ?>
414 </p>
415
416 <div class="easyauthnet-copy-wrap">
417 <input type="text"
418 id="<?php echo esc_attr($field_key); ?>-ref-link"
419 class="easyauthnet-copy-input"
420 value="<?php echo esc_attr($direct_url); ?>"
421 readonly />
422 <button type="button"
423 class="button easyauthnet-copy-link"
424 data-link="<?php echo esc_attr($direct_url); ?>">
425 <?php esc_html_e('Copy Link', 'payment-gateway-for-authorize-net-for-woocommerce'); ?>
426 </button>
427 </div>
428
429 <p class="description">
430 <?php esc_html_e('This referral link ensures your client\'s Authorize.Net account is automatically linked to this plugin.', 'payment-gateway-for-authorize-net-for-woocommerce'); ?>
431 </p>
432 </div>
433 </td>
434 </tr>
435 <?php
436 return ob_get_clean();
437 }
438 }
439
440 public function init_form_fields() {
441 $this->form_fields = [
442 'section_one' => [
443 'title' => __('Step 1: Registration', 'payment-gateway-for-authorize-net-for-woocommerce'),
444 'type' => 'title',
445 'class' => 'easyauthnet-authorizenet-collapsible-section'
446 ],
447 'signup_account' => array(
448 'type' => 'authorizenet_signup_banner',
449 ),
450 'section_sandbox' => [
451 'title' => __('Step 2: Authorize.Net Gateway Settings', 'payment-gateway-for-authorize-net-for-woocommerce'),
452 'type' => 'title',
453 'class' => 'easyauthnet-authorizenet-collapsible-section'
454 ],
455 'environment' => [
456 'title' => __('Environment', 'payment-gateway-for-authorize-net-for-woocommerce'),
457 'type' => 'select',
458 'description' => __('Select whether to use Sandbox (test mode) or Live (production).', 'payment-gateway-for-authorize-net-for-woocommerce'),
459 'desc_tip' => true,
460 'options' => [
461 'sandbox' => __('Sandbox (Test Mode)', 'payment-gateway-for-authorize-net-for-woocommerce'),
462 'live' => __('Live (Production)', 'payment-gateway-for-authorize-net-for-woocommerce'),
463 ],
464 'default' => 'sandbox',
465 ],
466 'enabled' => [
467 'title' => __('Enable / Disable', 'payment-gateway-for-authorize-net-for-woocommerce'),
468 'type' => 'checkbox',
469 'label' => __('Enable Authorize.net', 'payment-gateway-for-authorize-net-for-woocommerce'),
470 'default' => 'no',
471 ],
472 'title' => [
473 'title' => __('Title', 'payment-gateway-for-authorize-net-for-woocommerce'),
474 'type' => 'text',
475 'description' => __('This controls the payment method title displayed during checkout.', 'payment-gateway-for-authorize-net-for-woocommerce'),
476 'desc_tip' => true,
477 'default' => __('Credit / Debit Card', 'payment-gateway-for-authorize-net-for-woocommerce'),
478 ],
479 'description' => [
480 'title' => __('Description', 'payment-gateway-for-authorize-net-for-woocommerce'),
481 'type' => 'textarea',
482 'css' => 'width: 400px;',
483 'description' => __('This controls the text shown below the title during checkout.', 'payment-gateway-for-authorize-net-for-woocommerce'),
484 'desc_tip' => true,
485 'default' => __('Pay securely using your credit card.', 'payment-gateway-for-authorize-net-for-woocommerce'),
486 ],
487 'sandbox_api_login_id' => [
488 'title' => __('Sandbox API Login ID', 'payment-gateway-for-authorize-net-for-woocommerce'),
489 'type' => 'text',
490 'description' => __(
491 'Get from <a href="https://sandbox.authorize.net/" target="_blank">sandbox.authorize.net</a> → <code>Account → Settings → Security Settings → API Credentials & Keys</code>.',
492 'payment-gateway-for-authorize-net-for-woocommerce'
493 ),
494 'desc_tip' => false,
495 'custom_attributes' => ['autocomplete' => 'off'],
496 ],
497 'sandbox_transaction_key' => [
498 'title' => __('Sandbox Transaction Key', 'payment-gateway-for-authorize-net-for-woocommerce'),
499 'type' => 'password',
500 'custom_attributes' => ['autocomplete' => 'off'],
501 ],
502 'sandbox_signature_key' => [
503 'title' => __('Sandbox Signature Key', 'payment-gateway-for-authorize-net-for-woocommerce'),
504 'type' => 'password',
505 'custom_attributes' => ['autocomplete' => 'off'],
506 ],
507 'live_api_login_id' => [
508 'title' => __('Live API Login ID', 'payment-gateway-for-authorize-net-for-woocommerce'),
509 'type' => 'text',
510 'description' => __(
511 'Get from <a href="https://account.authorize.net/" target="_blank">account.authorize.net</a> → <code>Account → Settings → Security Settings → API Credentials & Keys</code>.',
512 'payment-gateway-for-authorize-net-for-woocommerce'
513 ),
514 'desc_tip' => false,
515 'custom_attributes' => ['autocomplete' => 'off'],
516 ],
517 'live_transaction_key' => [
518 'title' => __('Live Transaction Key', 'payment-gateway-for-authorize-net-for-woocommerce'),
519 'type' => 'password',
520 'custom_attributes' => ['autocomplete' => 'off'],
521 ],
522 'live_signature_key' => [
523 'title' => __('Live Signature Key', 'payment-gateway-for-authorize-net-for-woocommerce'),
524 'type' => 'password',
525 'custom_attributes' => ['autocomplete' => 'off'],
526 ],
527 'section_checkout' => [
528 'title' => __('Step 3: Additional Settings', 'payment-gateway-for-authorize-net-for-woocommerce'),
529 'type' => 'title',
530 'class' => 'easyauthnet-authorizenet-collapsible-section',
531 ],
532 'tokenization' => [
533 'title' => __('Tokenization', 'payment-gateway-for-authorize-net-for-woocommerce'),
534 'type' => 'checkbox',
535 'label' => __('Enable customers to save cards for future purchases.', 'payment-gateway-for-authorize-net-for-woocommerce'),
536 'default' => 'no',
537 ],
538 'transaction_type' => [
539 'title' => __('Transaction Type', 'payment-gateway-for-authorize-net-for-woocommerce'),
540 'type' => 'select',
541 'description' => __('Choose whether to authorize only or to authorize and capture the payment immediately.', 'payment-gateway-for-authorize-net-for-woocommerce'),
542 'desc_tip' => true,
543 'default' => 'auth_capture',
544 'options' => [
545 'auth_only' => __('Authorize Only — capture manually later', 'payment-gateway-for-authorize-net-for-woocommerce'),
546 'auth_capture' => __('Authorize & Capture — charge immediately', 'payment-gateway-for-authorize-net-for-woocommerce'),
547 ],
548 ],
549 'accepted_card_logos' => [
550 'title' => __('Accepted Card Logos', 'payment-gateway-for-authorize-net-for-woocommerce'),
551 'type' => 'multiselect',
552 'class' => 'wc-enhanced-select',
553 'css' => 'width: 350px;',
554 'description' => __('Controls which card logos appear on the checkout form (visual only).', 'payment-gateway-for-authorize-net-for-woocommerce'),
555 'desc_tip' => true,
556 'default' => ['visa', 'mastercard', 'amex', 'discover', 'jcb', 'diners'],
557 'options' => [
558 'visa' => 'Visa',
559 'mastercard' => 'MasterCard',
560 'amex' => 'American Express',
561 'discover' => 'Discover',
562 'jcb' => 'JCB',
563 'diners' => 'Diners Club',
564 ],
565 ],
566 'debug' => [
567 'title' => __('Debug Mode', 'payment-gateway-for-authorize-net-for-woocommerce'),
568 'type' => 'checkbox',
569 'label' => __('Enable logging for troubleshooting in WooCommerce → Status → Logs.', 'payment-gateway-for-authorize-net-for-woocommerce'),
570 'default' => 'no',
571 ],
572 ];
573 }
574
575 public function payment_fields() {
576 if ($this->supports('tokenization') && is_checkout()) {
577 $this->tokenization_script();
578 $this->saved_payment_methods();
579 $this->form();
580 $this->save_payment_method_checkbox();
581 } else {
582 $this->form();
583 }
584 }
585
586 public function form() {
587 wp_enqueue_script('wc-credit-card-form');
588 $id = esc_attr($this->id);
589 ?>
590 <div id="wc-<?php echo $id; ?>-form" class="wc-credit-card-form wc-payment-form">
591 <div class="easyauthnet_authorizenet-field full-width">
592 <label for="<?php echo $id; ?>-card-number">
593 <?php echo esc_html_x('Card number', 'Important', 'payment-gateway-for-authorize-net-for-woocommerce'); ?>
594 </label>
595 <div id="<?php echo $id; ?>-card-number-wrapper">
596 <input
597 id="<?php echo $id; ?>-card-number"
598 class="input-text wc-credit-card-form-card-number"
599 type="tel"
600 inputmode="numeric"
601 autocomplete="cc-number"
602 autocorrect="no"
603 autocapitalize="no"
604 spellcheck="no"
605 placeholder="&bull;&bull;&bull;&bull; &bull;&bull;&bull;&bull; &bull;&bull;&bull;&bull; &bull;&bull;&bull;&bull;"
606 <?php echo $this->field_name('card-number'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
607 />
608 </div>
609 </div>
610 <div class="easyauthnet_authorizenet-field half-width">
611 <label for="<?php echo $id; ?>-card-expiry">
612 <?php echo esc_html_x('Expiration date', 'Important', 'payment-gateway-for-authorize-net-for-woocommerce'); ?>
613 </label>
614 <div id="<?php echo $id; ?>-card-expiry-wrapper">
615 <input
616 id="<?php echo $id; ?>-card-expiry"
617 class="input-text wc-credit-card-form-card-expiry"
618 type="tel"
619 inputmode="numeric"
620 autocomplete="cc-exp"
621 autocorrect="no"
622 autocapitalize="no"
623 spellcheck="no"
624 placeholder="<?php echo esc_attr__('MM / YY', 'payment-gateway-for-authorize-net-for-woocommerce'); ?>"
625 <?php echo $this->field_name('card-expiry'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
626 />
627 </div>
628 </div>
629 <div class="easyauthnet_authorizenet-field half-width">
630 <label for="<?php echo $id; ?>-card-cvc">
631 <?php echo esc_html_x('Security code', 'Important', 'payment-gateway-for-authorize-net-for-woocommerce'); ?>
632 </label>
633 <div class="easyauthnet_authorizenet-cvc-wrapper">
634 <input
635 id="<?php echo $id; ?>-card-cvc"
636 class="input-text wc-credit-card-form-card-cvc"
637 type="tel"
638 inputmode="numeric"
639 autocomplete="off"
640 autocorrect="no"
641 autocapitalize="no"
642 spellcheck="no"
643 maxlength="4"
644 placeholder="<?php echo esc_attr__('CVV', 'payment-gateway-for-authorize-net-for-woocommerce'); ?>"
645 <?php echo $this->field_name('card-cvc'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
646 />
647 <div class="easyauthnet_authorizenet-parent-card-cvv-icon">
648 <svg class="easyauthnet_authorizenet-card-cvc-icon" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="var(--colorIconCardCvc)" role="img" aria-labelledby="cvcDesc">
649 <path opacity=".2" fill-rule="evenodd" clip-rule="evenodd" d="M15.337 4A5.493 5.493 0 0013 8.5c0 1.33.472 2.55 1.257 3.5H4a1 1 0 00-1 1v1a1 1 0 001 1h16a1 1 0 001-1v-.6a5.526 5.526 0 002-1.737V18a2 2 0 01-2 2H3a2 2 0 01-2-2V6a2 2 0 012-2h12.337zm6.707.293c.239.202.46.424.662.663a2.01 2.01 0 00-.662-.663z"></path>
650 <path opacity=".4" fill-rule="evenodd" clip-rule="evenodd" d="M13.6 6a5.477 5.477 0 00-.578 3H1V6h12.6z"></path>
651 <path fill-rule="evenodd" clip-rule="evenodd" d="M18.5 14a5.5 5.5 0 110-11 5.5 5.5 0 010 11zm-2.184-7.779h-.621l-1.516.77v.786l1.202-.628v3.63h.943V6.22h-.008zm1.807.629c.448 0 .762.251.762.613 0 .393-.37.668-.904.668h-.235v.668h.283c.565 0 .95.282.95.691 0 .393-.377.66-.911.66-.393 0-.786-.126-1.194-.37v.786c.44.189.88.291 1.312.291 1.029 0 1.736-.526 1.736-1.288 0-.535-.33-.967-.88-1.14.472-.157.778-.573.778-1.045 0-.738-.652-1.241-1.595-1.241a3.143 3.143 0 00-1.234.267v.77c.378-.212.763-.33 1.132-.33zm3.394 1.713c.574 0 .974.338.974.778 0 .463-.4.785-.974.785-.346 0-.707-.11-1.076-.337v.809c.385.173.778.26 1.163.26.204 0 .392-.032.573-.08a4.313 4.313 0 00.644-2.262l-.015-.33a1.807 1.807 0 00-.967-.252 3 3 0 00-.448.032V6.944h1.132a4.423 4.423 0 00-.362-.723h-1.587v2.475a3.9 3.9 0 01.943-.133z"></path>
652 </svg>
653
654 </div>
655 </div>
656 </div>
657 </div>
658 <?php
659 }
660
661 protected function is_using_saved_payment_method() {
662 // phpcs:ignore WordPress.Security.NonceVerification.Missing
663 return isset($_POST["wc-{$this->id}-payment-token"]) && 'new' !== $_POST["wc-{$this->id}-payment-token"];
664 }
665
666 public function process_payment($order_id) {
667 $order = wc_get_order($order_id);
668 try {
669 $this->log_start_payment($order);
670 if ($this->is_subscription_checkout_missing_token($order)) {
671 return $this->fail_due_to_missing_token();
672 }
673 if ($this->is_using_saved_card()) {
674 $order_total = floatval($order->get_total());
675 if ($order_total <= 0 && $this->is_subscription_order($order)) {
676 self::log("Subscription order with free trial - skipping charge");
677 $order->payment_complete();
678 return $this->success_redirect($order);
679 } else {
680 return $this->process_with_saved_card($order);
681 }
682 }
683 return $this->process_with_new_card($order);
684 } catch (Exception $ex) {
685 return $this->handle_payment_exception($ex);
686 }
687 }
688
689 protected function log_start_payment($order) {
690 $this->log("Starting payment processing", [
691 'order_id' => $order->get_id(),
692 'amount' => $order->get_total(),
693 'currency' => $order->get_currency(),
694 'customer_id' => $order->get_customer_id(),
695 'is_subscription' => $this->is_subscription_order($order),
696 'is_renewal' => $this->is_renewal_order($order),
697 'payment_method' => $order->get_payment_method()
698 ]);
699 }
700
701 protected function is_subscription_checkout_missing_token($order) {
702 // phpcs:ignore WordPress.Security.NonceVerification.Missing
703 $missing = $this->is_subscription_order($order) && !$this->is_using_saved_card() && empty($_POST['easyauthnet_authorizenet_token']);
704 if ($missing) {
705 // phpcs:ignore WordPress.Security.NonceVerification.Missing
706 $this->log("Subscription checkout missing token", ['is_using_saved_card' => $this->is_using_saved_card(), 'has_token' => !empty($_POST['easyauthnet_authorizenet_token'])]);
707 }
708 return $missing;
709 }
710
711 protected function fail_due_to_missing_token() {
712 $message = __('Saving a payment method is required for subscription purchases.', 'payment-gateway-for-authorize-net-for-woocommerce');
713 $this->log("Subscription checkout failed - missing token", ['error' => $message]);
714 wc_add_notice($message, 'error');
715 return ['result' => 'fail'];
716 }
717
718 protected function is_using_saved_card() {
719 $token_key = 'wc-' . $this->id . '-payment-token';
720
721 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified by WooCommerce during checkout processing.
722 $token_raw = isset($_POST[$token_key]) ? sanitize_text_field(wp_unslash($_POST[$token_key])) : '';
723
724 // Validate token: must not be 'new', must be numeric, and > 0
725 $is_using_saved = $token_raw !== 'new' && is_numeric($token_raw) && (int) $token_raw > 0;
726
727 // Log result
728 $this->log('Checking if using saved card', [
729 'is_using_saved' => $is_using_saved,
730 'token_value' => $token_raw,
731 ]);
732
733 return $is_using_saved;
734 }
735
736 protected function validate_card_type($card_type) {
737 $allowed_types = $this->accepted_card_logos;
738 $this->log("Validating card type", ['card_type' => $card_type, 'allowed_types' => $allowed_types]);
739 if (!in_array($card_type, $allowed_types)) {
740 // translators: %s is the credit card type (e.g., AMEX, VISA).
741 $error = sprintf(__('%s cards are not accepted. Please use a different payment method.', 'payment-gateway-for-authorize-net-for-woocommerce'), strtoupper($card_type));
742 $this->log("Invalid card type rejected", ['error' => $error]);
743 throw new Exception(esc_html($error));
744 }
745 return true;
746 }
747
748 protected function process_with_saved_card($order) {
749 $token_key = 'wc-' . $this->id . '-payment-token';
750 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce is validated earlier
751 if (isset($_POST[$token_key])) {
752 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified earlier in the request
753 $token_id = absint(wp_unslash($_POST[$token_key]));
754 } else {
755 $token_id = 0; // or handle as needed
756 }
757 $token = WC_Payment_Tokens::get($token_id);
758 $this->log("Processing with saved card", ['token_id' => $token_id, 'token_valid' => $this->is_token_valid($token), 'token_type' => $token ? $token->get_type() : 'none', 'token_last4' => $token ? $token->get_last4() : 'none']);
759 if (!$this->is_token_valid($token)) {
760 $error = __('Invalid saved payment method selected.', 'payment-gateway-for-authorize-net-for-woocommerce');
761 $this->log("Invalid token encountered", ['error' => $error]);
762 wc_add_notice($error, 'error');
763 return ['result' => 'fail'];
764 }
765 $response = EASYAUTHNET_AuthorizeNet_API_Handler::charge_saved_token($order, $token);
766 if (is_wp_error($response)) {
767 $this->log("Failed to charge saved token", ['error_code' => $response->get_error_code(), 'error_message' => $response->get_error_message()]);
768 return $this->fail_with_error($response);
769 }
770 $this->log("Successfully charged saved token", ['transaction_id' => $response['transaction_id'] ?? 'N/A', 'response_code' => $response['response_code'] ?? null, 'auth_code' => $response['auth_code'] ?? null]);
771 EASYAUTHNET_AuthorizeNet_API_Handler::complete_payment($order, $response['transaction_id']);
772 if ($this->is_subscription_order($order)) {
773 EASYAUTHNET_Subscription_Helper::assign_token_to_order_and_subscriptions($order, $token);
774 }
775 return $this->success_redirect($order);
776 }
777
778 protected function is_token_valid($token) {
779 return $token && $token->get_user_id() === get_current_user_id();
780 }
781
782 protected function process_with_new_card($order) {
783 $opaque_data = $this->get_opaque_card_data();
784 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is verified earlier in the request
785 $card_type = isset($_POST['easyauthnet_authorizenet_card_type']) ? sanitize_text_field(wp_unslash($_POST['easyauthnet_authorizenet_card_type'])) : '';
786 $this->log("Processing with new card", ['has_opaque_data' => !empty($opaque_data['gateway_token']['dataValue']), 'card_type' => $card_type]);
787 $this->validate_card_type($card_type);
788 if (empty($opaque_data['gateway_token']['dataValue'])) {
789 $error = __('Missing or invalid card token. Please try again.', 'payment-gateway-for-authorize-net-for-woocommerce');
790 $this->log("Missing card token", ['error' => $error]);
791 wc_add_notice($error, 'error');
792 return ['result' => 'fail'];
793 }
794 if ($this->should_save_card($order)) {
795 return $this->save_card_and_charge($order, $opaque_data);
796 }
797 return $this->charge_without_saving($order, $opaque_data['gateway_token']);
798 }
799
800 /**
801 * phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce is verified earlier in the request flow.
802 * phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Input is explicitly sanitized before use.
803 */
804 protected function get_opaque_card_data() {
805 $token_value = '';
806 if (isset($_POST['easyauthnet_authorizenet_token'])) {
807 $token_value = wc_clean(wp_unslash($_POST['easyauthnet_authorizenet_token']));
808 }
809 $last4 = '';
810 if (isset($_POST['easyauthnet_authorizenet_card_last4'])) {
811 $last4 = sanitize_text_field(wp_unslash($_POST['easyauthnet_authorizenet_card_last4']));
812 }
813 $card_type = '';
814 if (isset($_POST['easyauthnet_authorizenet_card_type'])) {
815 $card_type = sanitize_text_field(wp_unslash($_POST['easyauthnet_authorizenet_card_type']));
816 }
817 $expiry = '';
818 if (isset($_POST['easyauthnet_authorizenet_card_expiry'])) {
819 $expiry = sanitize_text_field(wp_unslash($_POST['easyauthnet_authorizenet_card_expiry']));
820 }
821 $data = [
822 'gateway_token' => [
823 'dataDescriptor' => 'COMMON.ACCEPT.INAPP.PAYMENT',
824 'dataValue' => $token_value,
825 ],
826 'last4' => $last4,
827 'card_type' => $card_type,
828 'expiry' => $expiry,
829 ];
830 $this->log(
831 'Retrieved opaque card data',
832 [
833 'has_token' => !empty($data['gateway_token']['dataValue']),
834 'last4' => !empty($data['last4']) ? '****' . $data['last4'] : 'none',
835 'card_type' => $data['card_type'] ?? 'none',
836 'expiry' => $data['expiry'] ?? 'none',
837 ]
838 );
839 return $data;
840 }
841
842 protected function should_save_card($order) {
843 $field_key = 'wc-' . $this->id . '-new-payment-method';
844
845 // Sanitize input early
846 $field_value = isset($_POST[$field_key]) ? sanitize_text_field(wp_unslash($_POST[$field_key])) : '';
847
848 // Validate against allowed values
849 $user_opted_in = in_array($field_value, ['1', 'true'], true);
850
851 // Determine if it's a subscription order
852 $is_subscription = $this->is_subscription_order($order);
853
854 // Log debug values safely
855 $this->log('Checking if should save card', [
856 'field_value' => $field_value,
857 'user_opted_in' => $user_opted_in,
858 'is_subscription' => $is_subscription,
859 ]);
860
861 return $user_opted_in || $is_subscription;
862 }
863
864 protected function save_card_and_charge($order, $opaque_data) {
865 $this->log("Attempting to save card and charge");
866 $token = $this->maybe_save_payment_token($order, $opaque_data);
867 if (is_wp_error($token)) {
868 $this->log("Failed to save payment token", ['error_code' => $token->get_error_code(), 'error_message' => $token->get_error_message()]);
869 return $this->fail_with_error($token);
870 }
871 if ($this->is_subscription_order($order)) {
872 EASYAUTHNET_Subscription_Helper::assign_token_to_order_and_subscriptions($order, $token);
873 }
874 $order_total = floatval($order->get_total());
875 if ($order_total <= 0 && $this->is_subscription_order($order)) {
876 $this->log("Subscription order with free trial - skipping charge");
877 $order->payment_complete();
878 return $this->success_redirect($order);
879 }
880 $response = EASYAUTHNET_AuthorizeNet_API_Handler::charge_saved_token($order, $token);
881 if (is_wp_error($response)) {
882 $this->log("Failed to charge after saving token", ['error_code' => $response->get_error_code(), 'error_message' => $response->get_error_message()]);
883 return $this->fail_with_error($response);
884 }
885 $this->log("Successfully charged after saving token", ['transaction_id' => $response['transaction_id'] ?? 'N/A', 'response_code' => $response['response_code'] ?? null, 'auth_code' => $response['auth_code'] ?? null]);
886 EASYAUTHNET_AuthorizeNet_API_Handler::complete_payment($order, $response['transaction_id']);
887 return $this->success_redirect($order);
888 }
889
890 protected function charge_without_saving($order, $gateway_token) {
891 $this->log("Processing one-time payment without saving card");
892 $response = EASYAUTHNET_AuthorizeNet_API_Handler::charge_transaction($order, $gateway_token);
893 if (is_wp_error($response)) {
894 $this->log("Failed to process one-time payment", ['error_code' => $response->get_error_code(), 'error_message' => $response->get_error_message()]);
895 return $this->fail_with_error($response);
896 }
897 $this->log("Successfully processed one-time payment", ['transaction_id' => $response['transaction_id'] ?? 'N/A', 'response_code' => $response['response_code'] ?? null, 'auth_code' => $response['auth_code'] ?? null]);
898 EASYAUTHNET_AuthorizeNet_API_Handler::complete_payment($order, $response['transaction_id']);
899 return $this->success_redirect($order);
900 }
901
902 protected function fail_with_error($error) {
903 $this->log("Payment processing failed", ['error_code' => $error->get_error_code(), 'error_message' => $error->get_error_message()]);
904 throw new \Automattic\WooCommerce\StoreApi\Exceptions\RouteException('AuthorizeNet_API', $error->get_error_message(), 400);
905 }
906
907 protected function is_subscription_order($order) {
908 $is_subscription = class_exists('EASYAUTHNET_Subscription_Helper') && EASYAUTHNET_Subscription_Helper::order_contains_subscription($order);
909 $this->log("Checking if order contains subscription", ['order_id' => $order->get_id(), 'is_subscription' => $is_subscription]);
910 return $is_subscription;
911 }
912
913 protected function is_renewal_order($order) {
914 $is_renewal = class_exists('EASYAUTHNET_Subscription_Helper') && EASYAUTHNET_Subscription_Helper::is_renewal_order($order);
915 $this->log("Checking if order is renewal", ['order_id' => $order->get_id(), 'is_renewal' => $is_renewal]);
916 return $is_renewal;
917 }
918
919 protected function success_redirect($order) {
920 $this->log("Payment processing successful - redirecting to thank you page", ['order_id' => $order->get_id(), 'status' => $order->get_status()]);
921 return [
922 'result' => 'success',
923 'redirect' => $this->get_return_url($order),
924 ];
925 }
926
927 protected function handle_payment_exception($e) {
928 $this->log("Payment processing exception", ['exception' => get_class($e), 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine()]);
929 throw new \Automattic\WooCommerce\StoreApi\Exceptions\RouteException('AuthorizeNet_API', $e->getMessage(), 400);
930 }
931
932 protected function store_card_details($order) {
933 $meta_updates = [];
934 if (isset($_POST['easyauthnet_authorizenet_card_last4'])) {
935 $last4 = wc_clean(wp_unslash($_POST['easyauthnet_authorizenet_card_last4']));
936 $order->update_meta_data(self::CARD_META_PREFIX . 'last4', $last4);
937 $meta_updates['last4'] = '****' . $last4;
938 }
939 if (isset($_POST['easyauthnet_authorizenet_card_expiry'])) {
940 $expiry = wc_clean(wp_unslash($_POST['easyauthnet_authorizenet_card_expiry']));
941 $order->update_meta_data(self::CARD_META_PREFIX . 'expiry', $expiry);
942 $meta_updates['expiry'] = $expiry;
943 }
944 if (isset($_POST['easyauthnet_authorizenet_card_type'])) {
945 $type = wc_clean(wp_unslash($_POST['easyauthnet_authorizenet_card_type']));
946 $order->update_meta_data(self::CARD_META_PREFIX . 'type', $type);
947 $meta_updates['type'] = $type;
948 }
949 $order->save();
950 $this->log('Stored card details in order meta', $meta_updates);
951 }
952
953 public function maybe_save_payment_token($order, $payment_data) {
954 $user_id = $order->get_user_id();
955 $this->log("Attempting to save payment token", ['user_id' => $user_id, 'has_token_data' => !empty($payment_data['gateway_token']['dataValue']), 'card_type' => $payment_data['card_type'] ?? 'none', 'last4' => !empty($payment_data['last4']) ? '****' . $payment_data['last4'] : 'none']);
956 if (!$user_id || empty($payment_data['gateway_token']['dataValue'])) {
957 $error = __('Missing token or user information for saving payment method.', 'payment-gateway-for-authorize-net-for-woocommerce');
958 $this->log("Cannot save token - missing requirements", ['error' => $error]);
959 return throw new Exception($error);
960 }
961 $customer_profile_id = get_user_meta($user_id, $this->customer_profile_id, true);
962 if ($customer_profile_id && !EASYAUTHNET_AuthorizeNet_API_Handler::validate_customer_profile($customer_profile_id)) {
963 $this->log("Existing customer profile not valid - recreating", ['customer_profile_id' => $this->mask_sensitive_data($customer_profile_id)]);
964 delete_user_meta($user_id, $this->customer_profile_id);
965 $customer_profile_id = '';
966 }
967 if (!$customer_profile_id) {
968 $create_result = EASYAUTHNET_AuthorizeNet_API_Handler::create_customer_profile($order, $payment_data['gateway_token']);
969 if (is_wp_error($create_result)) {
970 $this->log("Failed to create customer profile", ['error_code' => $create_result->get_error_code(), 'error_message' => $create_result->get_error_message()]);
971 if ($create_result->get_error_code() === 'duplicate_profile') {
972 $customer_profile_id = $create_result->get_error_data();
973 $this->log("Using duplicate profile ID", ['customer_profile_id' => $this->mask_sensitive_data($customer_profile_id)]);
974 } else {
975 return $create_result;
976 }
977 } else {
978 $customer_profile_id = $create_result['customerProfileId'];
979 $this->log("Successfully created new customer profile", ['customer_profile_id' => $this->mask_sensitive_data($customer_profile_id)]);
980 }
981 update_user_meta($user_id, $this->customer_profile_id, $customer_profile_id);
982 }
983 $payment_profile_id = EASYAUTHNET_AuthorizeNet_API_Handler::get_latest_payment_profile_id($customer_profile_id);
984 if (!$payment_profile_id) {
985 $error = __('Failed to retrieve saved payment method.', 'payment-gateway-for-authorize-net-for-woocommerce');
986 $this->log("No payment profile ID found", ['error' => $error]);
987 return throw new Exception($error);
988 }
989 $token = new WC_Payment_Token_CC();
990 $token->set_token($payment_profile_id);
991 $token->set_gateway_id($this->id);
992 $token->set_card_type(strtolower($payment_data['card_type']));
993 $token->set_last4($payment_data['last4']);
994 $token->set_expiry_month(substr($payment_data['expiry'], 0, 2));
995 $token->set_expiry_year('20' . substr($payment_data['expiry'], -2));
996 $token->set_user_id($user_id);
997 $token->set_default(true);
998 $token->add_meta_data('customer_profile_id', $customer_profile_id, true);
999 $token->add_meta_data('payment_profile_id', $payment_profile_id, true);
1000 $token->save();
1001 $this->log("Successfully saved payment token", ['token_id' => $token->get_id(), 'payment_profile_id' => $this->mask_sensitive_data($payment_profile_id), 'card_type' => $token->get_card_type(), 'last4' => $token->get_last4(), 'expiry' => $token->get_expiry_month() . '/' . substr($token->get_expiry_year(), -2)]);
1002 return $token;
1003 }
1004
1005 public function process_refund($order_id, $amount = null, $reason = '') {
1006 try {
1007 $order = wc_get_order($order_id);
1008 $this->log("Processing refund", ['order_id' => $order_id, 'amount' => $amount, 'reason' => $reason]);
1009 return EASYAUTHNET_AuthorizeNet_API_Handler::process_payment_refund($order, $amount, $reason);
1010 } catch (Exception $e) {
1011 $this->log("Refund processing failed", ['exception' => get_class($e), 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine()]);
1012 return new WP_Error('error', $e->getMessage());
1013 }
1014 }
1015
1016 public function easyauthnet_register_anet_webhook_on_save() {
1017 try {
1018 EASYAUTHNET_AuthorizeNet_API_Handler::register_webhook();
1019 } catch (Exception $ex) {
1020
1021 }
1022 }
1023
1024 public function add_payment_method() {
1025 try {
1026 $this->log("Attempting to add payment method");
1027 if (!is_user_logged_in()) {
1028 throw new Exception(__('You must be logged in to add a payment method.', 'payment-gateway-for-authorize-net-for-woocommerce'));
1029 }
1030 $user_id = get_current_user_id();
1031 $required_fields = [
1032 'easyauthnet_authorizenet_token' => __('Missing payment token. Please try again.', 'payment-gateway-for-authorize-net-for-woocommerce'),
1033 'easyauthnet_authorizenet_card_last4' => __('Missing card last 4 digits.', 'payment-gateway-for-authorize-net-for-woocommerce'),
1034 'easyauthnet_authorizenet_card_type' => __('Missing card type.', 'payment-gateway-for-authorize-net-for-woocommerce'),
1035 'easyauthnet_authorizenet_card_expiry' => __('Missing card expiration date.', 'payment-gateway-for-authorize-net-for-woocommerce')
1036 ];
1037 foreach ($required_fields as $field => $error_message) {
1038 if (empty($_POST[$field])) {
1039 throw new Exception($error_message);
1040 }
1041 }
1042 $token_data = [
1043 'dataDescriptor' => 'COMMON.ACCEPT.INAPP.PAYMENT',
1044 'dataValue' => isset($_POST['easyauthnet_authorizenet_token']) ? wc_clean(wp_unslash($_POST['easyauthnet_authorizenet_token'])) : '',
1045 ];
1046 $card_last4 = isset($_POST['easyauthnet_authorizenet_card_last4']) ? sanitize_text_field(wp_unslash($_POST['easyauthnet_authorizenet_card_last4'])) : '';
1047 $card_type = isset($_POST['easyauthnet_authorizenet_card_type']) ? sanitize_text_field(wp_unslash($_POST['easyauthnet_authorizenet_card_type'])) : '';
1048 $card_expiry = isset($_POST['easyauthnet_authorizenet_card_expiry']) ? sanitize_text_field(wp_unslash($_POST['easyauthnet_authorizenet_card_expiry'])) : '';
1049 $expiry_parts = array_map('trim', explode('/', $card_expiry));
1050 if (count($expiry_parts) !== 2) {
1051 throw new Exception(__('Invalid card expiration date format. Use MM/YY.', 'payment-gateway-for-authorize-net-for-woocommerce'));
1052 }
1053 $expiry_month = str_pad($expiry_parts[0], 2, '0', STR_PAD_LEFT);
1054 $expiry_year = '20' . $expiry_parts[1];
1055 $this->log("Creating customer profile from token", [
1056 'user_id' => $user_id,
1057 'card_type' => $card_type,
1058 'last4' => '****' . $card_last4,
1059 'expiry' => $expiry_month . '/' . substr($expiry_year, -2)
1060 ]);
1061 $token = EASYAUTHNET_AuthorizeNet_API_Handler::create_customer_profile_from_token(
1062 $user_id,
1063 $token_data,
1064 [
1065 'last4' => $card_last4,
1066 'type' => $card_type,
1067 'expiry_month' => $expiry_month,
1068 'expiry_year' => $expiry_year
1069 ]
1070 );
1071 if (is_wp_error($token)) {
1072 throw new Exception($token->get_error_message());
1073 }
1074 if (!empty($_POST['wc-' . $this->id . '-new-payment-method-default'])) {
1075 WC_Payment_Tokens::set_users_default($user_id, $token->get_id());
1076 $this->log("Set new payment method as default", ['token_id' => $token->get_id()]);
1077 }
1078 $this->log("Successfully added payment method", ['token_id' => $token->get_id(), 'card_type' => $card_type, 'last4' => '****' . $card_last4, 'expiry' => $expiry_month . '/' . substr($expiry_year, -2)]);
1079 return ['result' => 'success', 'redirect' => wc_get_endpoint_url('payment-methods')];
1080 } catch (Exception $e) {
1081 $this->log("Failed to add payment method", ['exception' => get_class($e), 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine()]);
1082 wc_add_notice($e->getMessage(), 'error');
1083 return ['result' => 'failure', 'redirect' => wc_get_endpoint_url('add-payment-method')];
1084 }
1085 }
1086
1087 protected function mask_sensitive_data($data) {
1088 if (is_string($data) && strlen($data) > 4) {
1089 return substr($data, 0, 2) . '***' . substr($data, -2);
1090 }
1091 return '***MASKED***';
1092 }
1093
1094 protected function format_log_message($message, $context = []) {
1095 $formatted = $message;
1096 if (!empty($context)) {
1097 $formatted .= "\n" . json_encode($this->mask_sensitive_data_in_context($context), JSON_PRETTY_PRINT);
1098 }
1099 return $formatted;
1100 }
1101
1102 protected function mask_sensitive_data_in_context($context) {
1103 if (!is_array($context)) {
1104 return $context;
1105 }
1106 $masked = [];
1107 $sensitive_keys = ['key', 'token', 'password', 'login', 'id', 'secret', 'signature'];
1108 foreach ($context as $key => $value) {
1109 $should_mask = false;
1110 foreach ($sensitive_keys as $sensitive) {
1111 if (stripos($key, $sensitive) !== false) {
1112 $should_mask = true;
1113 break;
1114 }
1115 }
1116 $masked[$key] = $should_mask ? $this->mask_sensitive_data($value) : $value;
1117 }
1118 return $masked;
1119 }
1120
1121 protected function log($message, $context = []) {
1122 if ($this->debug !== 'yes') {
1123 return;
1124 }
1125 $formatted_message = $this->format_log_message($message, $context);
1126 wc_get_logger()->info($formatted_message, ['source' => 'easyauthnet_authorizenet']);
1127 }
1128
1129 public function process_capture($order_id) {
1130 $order = wc_get_order($order_id);
1131 $transaction_id = $order->get_meta('_easyauthnet_authorize_net_auth_transaction_id');
1132 $this->log("Attempting to capture authorized payment", [
1133 'order_id' => $order_id,
1134 'transaction_id' => $transaction_id ?: 'none'
1135 ]);
1136 if (empty($transaction_id)) {
1137 $error = __('No authorized transaction found to capture.', 'payment-gateway-for-authorize-net-for-woocommerce');
1138 $this->log("Capture failed - no transaction ID", ['error' => $error]);
1139 return throw new Exception($error);
1140 }
1141 $response = EASYAUTHNET_AuthorizeNet_API_Handler::capture_authorized_transaction($order, $transaction_id);
1142 if (is_wp_error($response)) {
1143 $this->log("Capture API request failed", ['error_code' => $response->get_error_code(), 'error_message' => $response->get_error_message()]);
1144 return $response;
1145 }
1146 $this->log("Successfully captured authorized payment", ['transaction_id' => $transaction_id, 'response_code' => $response['response_code'] ?? null, 'auth_code' => $response['auth_code'] ?? null]);
1147 $order->payment_complete($transaction_id);
1148 // translators: 1: Transaction ID, 2: Authorization code.
1149 $order->add_order_note(sprintf(__('Authorized payment captured successfully. Transaction ID: %1$s, Auth Code: %2$s.', 'payment-gateway-for-authorize-net-for-woocommerce'), $transaction_id, $response['auth_code'] ?? null));
1150 return true;
1151 }
1152
1153 public function process_order_action_capture($order) {
1154 $this->log("Processing order capture action", ['order_id' => $order->get_id()]);
1155 $result = $this->process_capture($order->get_id());
1156 if (is_wp_error($result)) {
1157 $this->log("Order capture action failed", ['error_code' => $result->get_error_code(), 'error_message' => $result->get_error_message()]);
1158 $order->add_order_note(__('Capture failed: ', 'payment-gateway-for-authorize-net-for-woocommerce') . $result->get_error_message(), false, true);
1159 } else {
1160 $this->log("Order capture action completed successfully", ['order_id' => $order->get_id()]);
1161 }
1162 }
1163
1164 protected function maybe_run_customer_profile_migration_once() {
1165 $flag = get_option(self::CUSTOMER_PROFILE_MIGRATION_FLAG, '');
1166 if (!empty($flag)) {
1167 return;
1168 }
1169 if (class_exists('EASYAUTHNET_AuthorizeNet_API_Handler') && method_exists('EASYAUTHNET_AuthorizeNet_API_Handler', 'prime_runtime_credentials')) {
1170 EASYAUTHNET_AuthorizeNet_API_Handler::prime_runtime_credentials(
1171 $this->environment ?? 'sandbox',
1172 $this->api_login_id ?? '',
1173 $this->transaction_key ?? ''
1174 );
1175 }
1176 try {
1177 $result = ( class_exists('EASYAUTHNET_AuthorizeNet_API_Handler') && method_exists('EASYAUTHNET_AuthorizeNet_API_Handler', 'migrate_customer_profile_ids') ) ? EASYAUTHNET_AuthorizeNet_API_Handler::migrate_customer_profile_ids() : ['migrated' => 0, 'failed' => 0, 'total' => 0];
1178 update_option(
1179 self::CUSTOMER_PROFILE_MIGRATION_FLAG,
1180 wp_json_encode([
1181 'done' => true,
1182 'time' => time(),
1183 'result' => [
1184 'migrated' => (int) ( $result['migrated'] ?? 0 ),
1185 'failed' => (int) ( $result['failed'] ?? 0 ),
1186 'total' => (int) ( $result['total'] ?? 0 ),
1187 ],
1188 ]),
1189 false
1190 );
1191 if (method_exists($this, 'log')) {
1192 $this->log(__METHOD__, 'Customer profile migration finished', $result);
1193 }
1194 } catch (\Throwable $e) {
1195 if (method_exists($this, 'log')) {
1196 $this->log(__METHOD__, 'Customer profile migration threw', [
1197 'exception' => get_class($e),
1198 'message' => $e->getMessage(),
1199 ]);
1200 }
1201 update_option(
1202 self::CUSTOMER_PROFILE_MIGRATION_FLAG,
1203 wp_json_encode([
1204 'done' => false,
1205 'time' => time(),
1206 'error' => $e->getMessage(),
1207 ]),
1208 false
1209 );
1210 }
1211 }
1212
1213 public function easyauthnet_leaverev() {
1214 if (self::$easyauthnet_notice_rendered) {
1215 return;
1216 }
1217 self::$easyauthnet_notice_rendered = true;
1218
1219 // Only on WooCommerce > Settings > Payments > Authorize.Net section
1220 if (
1221 !isset($_GET['page'], $_GET['tab'], $_GET['section']) ||
1222 $_GET['page'] !== 'wc-settings' ||
1223 $_GET['tab'] !== 'checkout' ||
1224 $_GET['section'] !== 'easyauthnet_authorizenet'
1225 ) {
1226 return;
1227 }
1228
1229 if (!current_user_can('manage_woocommerce')) {
1230 return;
1231 }
1232
1233 $plugin_name = 'Authorize.Net Gateway by Easy Payment';
1234 $review_url = 'https://wordpress.org/support/plugin/payment-gateway-for-authorize-net-for-woocommerce/reviews/#new-post';
1235 $text_domain = 'payment-gateway-for-authorize-net-for-woocommerce';
1236
1237 // Ensure activation time exists
1238 $activation_time = (int) get_option('easyauthnet_activation_time');
1239 if (empty($activation_time)) {
1240 $activation_time = time();
1241 update_option('easyauthnet_activation_time', $activation_time);
1242 }
1243
1244 $hide_state = get_option('easyauthnet_review_notice_hide_v1', ''); // '', 'later', 'never'
1245 $next_show_time = (int) get_option('easyauthnet_next_show_time', time());
1246 $since_activation = time() - $activation_time;
1247
1248 // Show after 1 day from activation; respect snooze; skip if permanently hidden
1249 $required_activation_delay = DAY_IN_SECONDS;
1250 if ('never' === $hide_state || $since_activation < $required_activation_delay || time() < $next_show_time) {
1251 return;
1252 }
1253
1254 // Notice HTML
1255 $html = '<div class="notice notice-success easyauthnet-review-notice">';
1256 $html .= '<p style="font-size:100%"></p>';
1257 $html .= '<h2 style="margin:0" class="title">' .
1258 sprintf(
1259 esc_html__('Thank you for using %s 💕', $text_domain),
1260 '<b>' . esc_html($plugin_name) . '</b>'
1261 ) .
1262 '</h2>';
1263
1264 $html .= '<p>' .
1265 sprintf(
1266 wp_kses(
1267 __(
1268 'If you have a moment, we’d love it if you could leave us a <b><a target="_blank" href="%1$s">quick review</a>.</b> It motivates us and helps us keep improving. 💫 <br>Have feature ideas? Include them in your review — your feedback shapes our roadmap, and we love turning your ideas into reality.',
1269 $text_domain
1270 ),
1271 ['b' => [], 'a' => ['href' => [], 'target' => []], 'br' => []]
1272 ),
1273 esc_url($review_url)
1274 ) .
1275 '</p>';
1276
1277 $html .= '<div style="padding:5px 0 12px 0;display:flex;align-items:center;">';
1278 $html .= '<a target="_blank" class="button button-primary easyauthnet-action-button" data-action="reviewed" style="margin-right:10px;" href="' . esc_url($review_url) . '">✏️ ' .
1279 esc_html__('Write Review', $text_domain) .
1280 '</a>';
1281 $html .= '<button type="button" class="button button-secondary easyauthnet-action-button" data-action="never" style="margin-right:10px;">✌️ ' .
1282 esc_html__('Done!', $text_domain) .
1283 '</button>';
1284 $html .= '<div style="flex:auto;"></div>';
1285 $html .= '<button type="button" class="button button-secondary easyauthnet-action-button" data-action="later" style="margin-right:10px;">⏰ ' .
1286 esc_html__('Remind me later', $text_domain) .
1287 '</button>';
1288 $html .= '<a href="#" class="button-link easyauthnet-action-button" data-action="never" style="font-size:small;">' .
1289 esc_html__('Hide', $text_domain) .
1290 '</a>';
1291 $html .= '</div>';
1292 $html .= '</div>';
1293
1294 echo wp_kses($html, [
1295 'div' => ['class' => [], 'style' => []],
1296 'p' => ['style' => []],
1297 'h2' => ['class' => [], 'style' => []],
1298 'b' => [],
1299 'br' => [],
1300 'a' => ['href' => [], 'target' => [], 'class' => [], 'style' => [], 'data-action' => []],
1301 'button' => ['type' => [], 'class' => [], 'style' => [], 'data-action' => []],
1302 'span' => ['style' => [], 'class' => []],
1303 ]);
1304 }
1305
1306 public function easyauthnet_handle_review_action() {
1307 check_ajax_referer('easyauthnet_review_nonce', 'nonce');
1308 $action = isset($_POST['review_action']) ? sanitize_text_field($_POST['review_action']) : '';
1309
1310 if ($action === 'later') {
1311 // Snooze 7 days
1312 update_option('easyauthnet_next_show_time', time() + (7 * DAY_IN_SECONDS));
1313 update_option('easyauthnet_review_notice_hide_v1', 'later');
1314 } elseif ($action === 'never' || $action === 'reviewed') {
1315 update_option('easyauthnet_review_notice_hide_v1', 'never');
1316 } else {
1317 wp_send_json_error('Invalid action');
1318 }
1319
1320 wp_send_json_success();
1321 }
1322
1323 public function easyauthnet_enqueue_scripts($hook) {
1324 // Enqueue JS only on the Authorize.Net section
1325 if (
1326 !isset($_GET['page'], $_GET['tab'], $_GET['section']) ||
1327 $_GET['page'] !== 'wc-settings' ||
1328 $_GET['tab'] !== 'checkout' ||
1329 $_GET['section'] !== 'easyauthnet_authorizenet'
1330 ) {
1331 return;
1332 }
1333
1334 wp_enqueue_script(
1335 'easyauthnet-review-ajax',
1336 EASYAUTHNET_AUTHORIZENET_PLUGIN_ASSET_URL . '/assets/js/easyauthnet-review-ajax.js',
1337 array('jquery'),
1338 '1.0',
1339 true
1340 );
1341
1342 wp_localize_script('easyauthnet-review-ajax', 'easyauthnetAjax', array(
1343 'ajax_url' => admin_url('admin-ajax.php'),
1344 'nonce' => wp_create_nonce('easyauthnet_review_nonce'),
1345 'review_url' => 'https://wordpress.org/support/plugin/payment-gateway-for-authorize-net-for-woocommerce/reviews/#new-post'
1346 ));
1347 }
1348
1349 public function easyauthnet_allowed_css_properties($styles) {
1350 $styles[] = 'display';
1351 $styles[] = 'align-items';
1352 $styles[] = 'flex';
1353 $styles[] = 'auto';
1354 $styles[] = 'margin';
1355 $styles[] = 'padding';
1356 return $styles;
1357 }
1358 }
1359