PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.8
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.8
2.12.8 2.12.7 2.12.6 2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 All 98 releases
sureforms / inc / payments / stripe / payments-settings.php

payments-settings.php in SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz 2.12.8, at inc/payments/stripe/payments-settings.php

1,048 lines 31.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Payments Settings Handler
4 *
5 * @package sureforms
6 * @since 2.0.0
7 */
8
9 namespace SRFM\Inc\Payments\Stripe;
10
11 use SRFM\Inc\Payments\Payment_Helper;
12 use SRFM\Inc\Traits\Get_Instance;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit; // Exit if accessed directly.
16 }
17
18 /**
19 * Payments Settings Class
20 *
21 * @since 2.0.0
22 */
23 class Payments_Settings {
24 use Get_Instance;
25
26 /**
27 * Constructor
28 *
29 * @since 2.0.0
30 */
31 public function __construct() {
32 add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
33 add_filter( 'srfm_global_settings_data', [ $this, 'add_payments_settings' ] );
34 add_action( 'admin_init', [ $this, 'intercept_stripe_callback' ] );
35 add_filter( 'srfm_entry_value', [ $this, 'filter_entry_value_for_payment' ], 10, 2 );
36 }
37
38 /**
39 * Filter entry value for payment blocks to display clickable link to payment admin page.
40 *
41 * This filter checks if the field block is a payment block and converts the payment ID
42 * into a clickable link that directs to the payment details page in admin.
43 *
44 * @param mixed $value The current field value (payment ID).
45 * @param array<mixed> $args Arguments containing field_name, label, and field_block_name.
46 * @since 2.0.0
47 * @return mixed The modified field value (clickable link) or original value if not a payment block.
48 */
49 public function filter_entry_value_for_payment( $value, $args ) {
50 // Check if this is a payment block.
51 if ( ! isset( $args['field_block_name'] ) || 'srfm-payment' !== $args['field_block_name'] ) {
52 return $value;
53 }
54
55 // Get the payment ID from the field value.
56 $payment_id = is_numeric( $value ) ? intval( $value ) : 0;
57
58 // If payment ID is not valid, return original value.
59 if ( $payment_id <= 0 ) {
60 return $value;
61 }
62
63 /**
64 * Generate the payment admin URL with hash-based routing.
65 * Example: http://localhost:10008/wp-admin/admin.php?page=sureforms_payments#/payment/323
66 */
67 $base_url = add_query_arg(
68 [
69 'page' => 'sureforms_payments',
70 ],
71 admin_url( 'admin.php' )
72 );
73
74 // Append hash route for specific payment.
75 $url = $base_url . '#/payment/' . $payment_id;
76
77 return sprintf(
78 '<a type="button" href="%s" class="outline-1 border-none cursor-pointer transition-colors duration-300 ease-in-out font-semibold focus:ring-2 focus:ring-toggle-on focus:ring-offset-2 disabled:text-text-disabled rounded-md text-sm [&>svg]:size-5 gap-1 outline-none text-link-primary bg-transparent hover:text-link-primary-hover p-0 border-0 leading-none no-underline hover:underline" target="_blank">%s</a>',
79 esc_url( $url ),
80 esc_html__( 'View Payment', 'sureforms' )
81 );
82 }
83
84 /**
85 * Register REST routes
86 *
87 * @since 2.0.0
88 * @return void
89 */
90 public function register_rest_routes() {
91 register_rest_route(
92 'sureforms/v1',
93 '/payments/stripe-connect',
94 [
95 [
96 'methods' => \WP_REST_Server::READABLE,
97 'callback' => [ Stripe_Helper::class, 'get_stripe_connect_url' ],
98 'permission_callback' => [ $this, 'permission_check' ],
99 ],
100 ]
101 );
102
103 register_rest_route(
104 'sureforms/v1',
105 '/payments/stripe-disconnect',
106 [
107 [
108 'methods' => \WP_REST_Server::CREATABLE,
109 'callback' => [ $this, 'disconnect_stripe' ],
110 'permission_callback' => [ $this, 'permission_check' ],
111 ],
112 ]
113 );
114
115 register_rest_route(
116 'sureforms/v1',
117 '/payments/stripe-callback',
118 [
119 [
120 'methods' => \WP_REST_Server::READABLE,
121 'callback' => [ $this, 'handle_stripe_callback' ],
122 'permission_callback' => [ $this, 'permission_check' ],
123 ],
124 ]
125 );
126
127 register_rest_route(
128 'sureforms/v1',
129 '/payments/create-payment-webhook',
130 [
131 [
132 'methods' => \WP_REST_Server::CREATABLE,
133 'callback' => [ $this, 'handle_webhook_creation_request' ],
134 'permission_callback' => [ $this, 'permission_check' ],
135 ],
136 ]
137 );
138 }
139
140 /**
141 * Permission callback
142 *
143 * @since 2.0.0
144 * @return bool
145 */
146 public function permission_check() {
147 return current_user_can( 'manage_options' );
148 }
149
150 /**
151 * Add payments settings to global settings
152 *
153 * Returns complete payment settings structure including global settings and all gateway settings.
154 *
155 * @param array<mixed> $settings Existing settings.
156 * @since 2.0.0
157 * @return array<mixed>
158 */
159 public function add_payments_settings( $settings ) {
160 // Get all payment settings (includes global + all gateways).
161 $payment_settings = Payment_Helper::get_all_payment_settings();
162
163 $settings['payment_settings'] = $payment_settings;
164
165 return apply_filters( 'srfm_get_payments_settings', $settings );
166 }
167
168 /**
169 * Intercept Stripe OAuth callback
170 *
171 * This function validates the OAuth callback from Stripe Connect by:
172 * 1. Verifying user has admin capabilities
173 * 2. Checking for required page/tab parameters
174 * 3. Validating the nonce using wp_verify_nonce()
175 * 4. Comparing with stored transient for additional security
176 *
177 * @since 2.0.0
178 * @return void
179 */
180 public function intercept_stripe_callback() {
181 // Check if user has permission to connect Stripe.
182 if ( ! current_user_can( 'manage_options' ) ) {
183 return;
184 }
185
186 // Check if this is a Stripe callback for our flow.
187 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
188 if ( ! isset( $_GET['page'] ) || 'sureforms_form_settings' !== $_GET['page'] ) {
189 return;
190 }
191
192 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
193 if ( ! isset( $_GET['tab'] ) || 'payments-settings' !== $_GET['tab'] ) {
194 return;
195 }
196
197 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
198 if ( ! isset( $_GET['srfm_stripe_connect_nonce'] ) ) {
199 return;
200 }
201
202 // Get and sanitize the nonce from URL.
203 $nonce = sanitize_text_field( wp_unslash( $_GET['srfm_stripe_connect_nonce'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
204
205 // Verify the nonce using WordPress's built-in verification.
206 if ( ! wp_verify_nonce( $nonce, 'stripe-connect' ) ) {
207 wp_die(
208 esc_html__( 'Security verification failed. Invalid nonce.', 'sureforms' ),
209 esc_html__( 'Stripe Connect Error', 'sureforms' ),
210 [ 'response' => 403 ]
211 );
212 }
213
214 // Additional verification: Compare with stored transient.
215 $saved_nonce = get_transient( 'srfm_stripe_connect_nonce_' . get_current_user_id() );
216
217 if ( $nonce !== $saved_nonce ) {
218 wp_die(
219 esc_html__( 'Security verification failed. Nonce mismatch.', 'sureforms' ),
220 esc_html__( 'Stripe Connect Error', 'sureforms' ),
221 [ 'response' => 403 ]
222 );
223 }
224
225 // This is our callback, handle it.
226 $this->handle_stripe_callback();
227 }
228
229 /**
230 * Handle Stripe OAuth callback
231 *
232 * @since 2.0.0
233 * @return void
234 */
235 public function handle_stripe_callback() {
236 // Check if we have OAuth response data.
237 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
238 if ( isset( $_GET['response'] ) ) {
239 $this->process_oauth_success();
240 return;
241 }
242
243 // Check if we have OAuth error.
244 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
245 if ( isset( $_GET['error'] ) ) {
246 $this->process_oauth_error();
247 return;
248 }
249
250 // No response or error, redirect with generic error.
251 $redirect_url = add_query_arg(
252 [
253 'page' => 'sureforms_form_settings',
254 'tab' => 'payments-settings',
255 'subpage' => 'payment-methods',
256 'gateway' => 'stripe',
257 'error' => rawurlencode( __( 'OAuth callback missing response data.', 'sureforms' ) ),
258 ],
259 admin_url( 'admin.php' )
260 );
261
262 wp_safe_redirect( $redirect_url );
263 exit;
264 }
265
266 /**
267 * Disconnect Stripe account
268 *
269 * @since 2.0.0
270 * @return \WP_REST_Response
271 */
272 public function disconnect_stripe() {
273 // Delete Stripe webhook endpoints for both test and live modes.
274 $this->delete_stripe_webhooks();
275
276 $settings = Stripe_Helper::get_all_stripe_settings();
277 if ( ! is_array( $settings ) ) {
278 $settings = Stripe_Helper::get_all_stripe_settings();
279 }
280
281 $settings['stripe_connected'] = false;
282 $settings['stripe_account_id'] = '';
283 $settings['stripe_account_email'] = '';
284 $settings['stripe_live_publishable_key'] = '';
285 $settings['stripe_live_secret_key'] = '';
286 $settings['stripe_test_publishable_key'] = '';
287 $settings['stripe_test_secret_key'] = '';
288 $settings['webhook_test_secret'] = '';
289 $settings['webhook_test_url'] = '';
290 $settings['webhook_test_id'] = '';
291 $settings['webhook_live_secret'] = '';
292 $settings['webhook_live_url'] = '';
293 $settings['webhook_live_id'] = '';
294 $settings['account_name'] = '';
295
296 $updated = Stripe_Helper::update_all_stripe_settings( $settings );
297
298 return rest_ensure_response(
299 [
300 'success' => true,
301 'message' => __( 'Stripe account disconnected successfully!', 'sureforms' ),
302 'updated' => $updated,
303 ]
304 );
305 }
306
307 /**
308 * Handle webhook creation request (REST API handler)
309 *
310 * @param \WP_REST_Request $request The REST request object.
311 * @since 2.0.0
312 * @return \WP_REST_Response
313 */
314 public function handle_webhook_creation_request( $request ) {
315 // Get mode parameter from request (defaults to current payment mode).
316 $mode = $request->get_param( 'mode' );
317
318 // Validate mode parameter.
319 if ( ! in_array( $mode, [ 'test', 'live' ], true ) ) {
320 $settings = Stripe_Helper::get_all_stripe_settings();
321 $mode = is_array( $settings ) && isset( $settings['payment_mode'] ) ? $settings['payment_mode'] : 'test';
322 }
323
324 $mode = ! empty( $mode ) && is_string( $mode ) ? $mode : 'test';
325
326 // Create webhook for the specified mode only.
327 $result = $this->create_webhook_for_mode( $mode );
328
329 return rest_ensure_response( $result );
330 }
331
332 /**
333 * Create Stripe webhook for a specific mode (test or live)
334 *
335 * @param string $mode The payment mode ('test' or 'live').
336 * @since 2.0.0
337 * @return array<mixed> Array containing webhook creation results and details
338 * @throws \Exception When the Stripe API request fails for any mode.
339 */
340 public function create_webhook_for_mode( $mode ) {
341 $settings = Stripe_Helper::get_all_stripe_settings();
342 if ( ! is_array( $settings ) ) {
343 $settings = Stripe_Helper::get_all_stripe_settings();
344 }
345
346 if ( empty( $settings['stripe_connected'] ) ) {
347 return [
348 'success' => false,
349 'message' => __( 'Stripe is not connected.', 'sureforms' ),
350 ];
351 }
352
353 // Validate mode.
354 if ( ! in_array( $mode, [ 'test', 'live' ], true ) ) {
355 return [
356 'success' => false,
357 'message' => __( 'Invalid payment mode.', 'sureforms' ),
358 ];
359 }
360
361 // Get secret key for the mode.
362 $secret_key = 'live' === $mode
363 ? ( $settings['stripe_live_secret_key'] ?? '' )
364 : ( $settings['stripe_test_secret_key'] ?? '' );
365
366 if ( empty( $secret_key ) ) {
367 return [
368 'success' => false,
369 'message' => sprintf(
370 /* translators: %s: payment mode (test/live) */
371 __( 'Stripe %s secret key is missing.', 'sureforms' ),
372 $mode
373 ),
374 ];
375 }
376
377 $webhook_url = Stripe_Helper::get_webhook_url( $mode );
378
379 try {
380 $webhook_data = [
381 'api_version' => '2025-07-30.basil',
382 'url' => $webhook_url,
383 'enabled_events' => [
384 'charge.failed',
385 'charge.succeeded',
386 'payment_intent.succeeded',
387 'charge.refund.updated',
388 'charge.dispute.created',
389 'charge.dispute.closed',
390 'invoice.payment_succeeded',
391 'customer.subscription.created',
392 'customer.subscription.updated',
393 'customer.subscription.deleted',
394 ],
395 ];
396
397 $api_response = Stripe_Helper::stripe_api_request( 'webhook_endpoints', 'POST', $webhook_data, '', [ 'mode' => $mode ] );
398
399 if ( ! isset( $api_response['success'] ) || ! $api_response['success'] ) {
400 $error_details = $api_response['error'] ?? [];
401 $error_message = $error_details['message'] ?? __( 'Unable to create webhook.', 'sureforms' );
402 throw new \Exception( $error_message );
403 }
404
405 $webhook = $api_response['data'] ?? [];
406
407 // Validate webhook response structure.
408 if ( ! is_array( $webhook ) ) {
409 throw new \Exception( __( 'Invalid webhook response format.', 'sureforms' ) );
410 }
411
412 if ( empty( $webhook['id'] ) ) {
413 throw new \Exception( __( 'Webhook created but no ID returned.', 'sureforms' ) );
414 }
415
416 if ( empty( $webhook['secret'] ) ) {
417 throw new \Exception( __( 'Webhook created but no secret returned.', 'sureforms' ) );
418 }
419
420 // Store webhook data in settings.
421 if ( 'live' === $mode ) {
422 $settings['webhook_live_secret'] = $webhook['secret'];
423 $settings['webhook_live_id'] = $webhook['id'];
424 $settings['webhook_live_url'] = $webhook_url;
425 } else {
426 $settings['webhook_test_secret'] = $webhook['secret'];
427 $settings['webhook_test_id'] = $webhook['id'];
428 $settings['webhook_test_url'] = $webhook_url;
429 }
430
431 Stripe_Helper::update_all_stripe_settings( $settings );
432
433 // Prepare response with webhook details.
434 return [
435 'success' => true,
436 'message' => sprintf(
437 /* translators: %s: payment mode (test/live) */
438 __( 'Webhook created successfully for %s mode.', 'sureforms' ),
439 $mode
440 ),
441 'webhook_details' => [
442 $mode => [
443 'webhook_secret' => $webhook['secret'],
444 'webhook_id' => $webhook['id'],
445 'webhook_url' => $webhook_url,
446 ],
447 ],
448 ];
449
450 } catch ( \Exception $e ) {
451 return [
452 'success' => false,
453 'message' => $e->getMessage(),
454 ];
455 }
456 }
457
458 /**
459 * Setup Stripe webhooks for both test and live modes
460 *
461 * @since 2.0.0
462 * @return array<mixed> Array containing webhook creation results and details
463 * @throws \Exception When the Stripe API request fails for any mode.
464 */
465 public function setup_stripe_webhooks() {
466 $settings = Stripe_Helper::get_all_stripe_settings();
467 if ( ! is_array( $settings ) ) {
468 $settings = Stripe_Helper::get_all_stripe_settings();
469 }
470
471 if ( empty( $settings['stripe_connected'] ) ) {
472 return [
473 'success' => false,
474 'message' => __( 'Stripe is not connected.', 'sureforms' ),
475 ];
476 }
477
478 $webhooks_created = 0;
479 $error_message = '';
480 $modes = [ 'test', 'live' ];
481
482 foreach ( $modes as $mode ) {
483 $secret_key = 'live' === $mode
484 ? ( $settings['stripe_live_secret_key'] ?? '' )
485 : ( $settings['stripe_test_secret_key'] ?? '' );
486
487 if ( empty( $secret_key ) ) {
488 continue;
489 }
490
491 $webhook_url = Stripe_Helper::get_webhook_url( $mode );
492
493 try {
494 $webhook_data = [
495 'api_version' => '2025-07-30.basil',
496 'url' => $webhook_url,
497 'enabled_events' => [
498 'charge.failed',
499 'charge.succeeded',
500 'payment_intent.succeeded',
501 'charge.refund.updated',
502 'charge.dispute.created',
503 'charge.dispute.closed',
504 'invoice.payment_succeeded',
505 'customer.subscription.created',
506 'customer.subscription.updated',
507 'customer.subscription.deleted',
508 ],
509 ];
510
511 $api_response = Stripe_Helper::stripe_api_request( 'webhook_endpoints', 'POST', $webhook_data, '', [ 'mode' => $mode ] );
512
513 if ( ! isset( $api_response['success'] ) || ! $api_response['success'] ) {
514 $error_details = $api_response['error'] ?? [];
515 $error_message = $error_details['message'] ?? '';
516 throw new \Exception( $error_message );
517 }
518
519 $webhook = $api_response['data'] ?? [];
520
521 // Validate webhook response structure.
522 if ( ! is_array( $webhook ) ) {
523 throw new \Exception( __( 'Invalid webhook response format.', 'sureforms' ) );
524 }
525
526 if ( empty( $webhook['id'] ) ) {
527 throw new \Exception( __( 'Webhook created but no ID returned.', 'sureforms' ) );
528 }
529
530 if ( empty( $webhook['secret'] ) ) {
531 throw new \Exception( __( 'Webhook created but no secret returned.', 'sureforms' ) );
532 }
533
534 // Store webhook data in settings.
535 if ( 'live' === $mode ) {
536 $settings['webhook_live_secret'] = $webhook['secret'];
537 $settings['webhook_live_id'] = $webhook['id'];
538 $settings['webhook_live_url'] = $webhook_url;
539 } else {
540 $settings['webhook_test_secret'] = $webhook['secret'];
541 $settings['webhook_test_id'] = $webhook['id'];
542 $settings['webhook_test_url'] = $webhook_url;
543 }
544
545 $webhooks_created++;
546
547 } catch ( \Exception $e ) {
548 $error_message = $e->getMessage();
549 }
550 }
551
552 // Update settings if any webhooks were created.
553 if ( $webhooks_created > 0 ) {
554 Stripe_Helper::update_all_stripe_settings( $settings );
555 }
556
557 // Prepare response with webhook details.
558 $response_data = [
559 'success' => $webhooks_created > 0,
560 ];
561
562 if ( $webhooks_created > 0 ) {
563 $response_data['webhook_details'] = [
564 'test' => [
565 'webhook_secret' => $settings['webhook_test_secret'] ?? '',
566 'webhook_id' => $settings['webhook_test_id'] ?? '',
567 'webhook_url' => Stripe_Helper::get_webhook_url( 'test' ),
568 ],
569 'live' => [
570 'webhook_secret' => $settings['webhook_live_secret'] ?? '',
571 'webhook_id' => $settings['webhook_live_id'] ?? '',
572 'webhook_url' => Stripe_Helper::get_webhook_url( 'live' ),
573 ],
574 ];
575 }
576
577 // Set appropriate message.
578 if ( count( $modes ) === $webhooks_created ) {
579 $response_data['message'] = sprintf(
580 /* translators: %1$d: number of webhooks created */
581 __( 'Webhooks created successfully for %1$d mode(s).', 'sureforms' ),
582 $webhooks_created
583 );
584 } elseif ( $webhooks_created > 0 ) {
585 $response_data['message'] = sprintf(
586 /* translators: %1$d: number of webhooks created, %2$s: error message */
587 __( 'Webhooks created for %1$d mode(s). Some modes may have failed: %2$s', 'sureforms' ),
588 $webhooks_created,
589 $error_message
590 );
591 } else {
592 $response_data['message'] = $error_message ? $error_message : __( 'Unable to create webhooks.', 'sureforms' );
593 }
594
595 return $response_data;
596 }
597
598 /**
599 * Delete Stripe webhooks for both test and live modes
600 *
601 * @since 2.0.0
602 * @return array<mixed> Array containing deletion results
603 */
604 public function delete_stripe_webhooks() {
605 $settings = Stripe_Helper::get_all_stripe_settings();
606 if ( ! is_array( $settings ) ) {
607 $settings = Stripe_Helper::get_all_stripe_settings();
608 }
609
610 if ( empty( $settings['stripe_connected'] ) ) {
611 return [
612 'success' => false,
613 'message' => __( 'Stripe is not connected.', 'sureforms' ),
614 ];
615 }
616
617 $webhooks_deleted = 0;
618 $error_message = '';
619 $modes = [ 'test', 'live' ];
620
621 $return_response = [];
622
623 foreach ( $modes as $mode ) {
624 $secret_key = 'live' === $mode
625 ? ( $settings['stripe_live_secret_key'] ?? '' )
626 : ( $settings['stripe_test_secret_key'] ?? '' );
627
628 $webhook_id = 'live' === $mode
629 ? ( $settings['webhook_live_id'] ?? '' )
630 : ( $settings['webhook_test_id'] ?? '' );
631
632 if ( empty( $secret_key ) || empty( $webhook_id ) || ! is_string( $webhook_id ) ) {
633 continue;
634 }
635
636 try {
637 $api_response = Stripe_Helper::stripe_api_request( 'webhook_endpoints', 'DELETE', [], (string) $webhook_id, [ 'mode' => $mode ] );
638
639 if ( ! isset( $api_response['success'] ) || ! $api_response['success'] ) {
640 $error_details = $api_response['error'] ?? [];
641 $error_message = $error_details['message'] ?? '';
642 $return_response[] = [
643 'success' => false,
644 'message' => $error_message,
645 ];
646 continue;
647 }
648
649 $delete_result = $api_response['data'] ?? [];
650
651 // Validate deletion response.
652 if ( ! is_array( $delete_result ) ) {
653 $return_response[] = [
654 'success' => false,
655 'message' => __( 'Invalid webhook deletion response format.', 'sureforms' ),
656 ];
657 continue;
658 }
659
660 if ( empty( $delete_result['deleted'] ) || true !== $delete_result['deleted'] ) {
661 $return_response[] = [
662 'success' => false,
663 'message' => __( 'Webhook deletion was not confirmed by Stripe.', 'sureforms' ),
664 ];
665 continue;
666 }
667
668 // Clean up stored webhook data from settings.
669 if ( 'live' === $mode ) {
670 $settings['webhook_live_secret'] = '';
671 $settings['webhook_live_id'] = '';
672 $settings['webhook_live_url'] = '';
673 } else {
674 $settings['webhook_test_secret'] = '';
675 $settings['webhook_test_id'] = '';
676 $settings['webhook_test_url'] = '';
677 }
678
679 $webhooks_deleted++;
680 $return_response[] = [
681 'success' => true,
682 'message' => __( 'Webhook deleted successfully!', 'sureforms' ),
683 ];
684
685 } catch ( \Exception $e ) {
686 $error_message = $e->getMessage();
687 $return_response[] = [
688 'success' => false,
689 'message' => $error_message,
690 ];
691 }
692 }
693
694 // Prepare response.
695 $response_data = [
696 'success' => $webhooks_deleted > 0,
697 ];
698
699 // Update settings if any webhooks were deleted.
700 if ( $webhooks_deleted > 0 ) {
701 Stripe_Helper::update_all_stripe_settings( $settings );
702 $response_data['message'] = sprintf(
703 /* translators: %d: number of webhooks deleted */
704 __( 'Webhooks deleted successfully for %d mode(s).', 'sureforms' ),
705 $webhooks_deleted
706 );
707 } else {
708 $message = '';
709
710 foreach ( $return_response as $response ) {
711 // Since $response is always array{success: bool, message: string}, isset() is redundant.
712 if ( $response['success'] && is_string( $response['message'] ) ) {
713 $message .= $response['message'] . '<br>';
714 }
715 }
716
717 $response_data['message'] = $message ? $message : __( 'Unable to delete webhooks.', 'sureforms' );
718 }
719
720 return $response_data;
721 }
722
723 /**
724 * Delete payment webhooks
725 *
726 * @param \WP_REST_Request|array<int, string>|null $request_or_modes Request object or array of modes to delete.
727 * @since 2.0.0
728 * @return \WP_REST_Response
729 */
730 public function delete_payment_webhooks( $request_or_modes = null ) {
731 $settings = Stripe_Helper::get_all_stripe_settings();
732 if ( ! is_array( $settings ) ) {
733 $settings = Stripe_Helper::get_all_stripe_settings();
734 }
735
736 if ( empty( $settings['stripe_connected'] ) ) {
737 return rest_ensure_response(
738 [
739 'success' => false,
740 'message' => __( 'Stripe is not connected.', 'sureforms' ),
741 ]
742 );
743 }
744
745 // Determine modes to delete.
746 $modes = [];
747
748 if ( is_array( $request_or_modes ) ) {
749 // Direct array of modes passed.
750 $modes = $request_or_modes;
751 } elseif ( $request_or_modes && method_exists( $request_or_modes, 'get_param' ) ) {
752 // REST request object - check for modes parameter first, then mode parameter.
753 $request_modes = $request_or_modes->get_param( 'modes' );
754 if ( ! empty( $request_modes ) && is_array( $request_modes ) ) {
755 $modes = $request_modes;
756 } else {
757 // Fallback to single mode parameter for backward compatibility.
758 $mode_to_delete = $request_or_modes->get_param( 'mode' ) ?? ( $settings['payment_mode'] ?? 'test' );
759 $modes = [ $mode_to_delete ];
760 }
761 } else {
762 // Default to current payment mode.
763 $modes = [ $settings['payment_mode'] ?? 'test' ];
764 }
765
766 // Validate modes.
767 foreach ( $modes as $mode ) {
768 if ( ! in_array( $mode, [ 'test', 'live' ], true ) ) {
769 return rest_ensure_response(
770 [
771 'success' => false,
772 'message' => __( 'Invalid payment mode specified.', 'sureforms' ),
773 ]
774 );
775 }
776 }
777
778 $webhooks_deleted = 0;
779 $error_message = '';
780
781 foreach ( $modes as $mode ) {
782 $secret_key = 'live' === $mode
783 ? ( $settings['stripe_live_secret_key'] ?? '' )
784 : ( $settings['stripe_test_secret_key'] ?? '' );
785
786 $webhook_id = 'live' === $mode ? ( $settings['webhook_live_id'] ?? '' ) : ( $settings['webhook_test_id'] ?? '' );
787
788 if ( empty( $secret_key ) || empty( $webhook_id ) || ! is_string( $webhook_id ) ) {
789 continue;
790 }
791
792 try {
793 $api_response = Stripe_Helper::stripe_api_request( 'webhook_endpoints', 'DELETE', [], (string) $webhook_id, [ 'mode' => $mode ] );
794
795 if ( ! isset( $api_response['success'] ) || ! $api_response['success'] ) {
796 $error_details = $api_response['error'] ?? [];
797 $error_message = $error_details['message'] ?? '';
798
799 return rest_ensure_response(
800 [
801 'success' => false,
802 'message' => $error_message,
803 ]
804 );
805 }
806
807 $delete_result = $api_response['data'] ?? [];
808
809 // Validate deletion response.
810 if ( ! is_array( $delete_result ) ) {
811 return rest_ensure_response(
812 [
813 'success' => false,
814 'message' => __( 'Invalid webhook deletion response format.', 'sureforms' ),
815 ]
816 );
817 }
818
819 if ( empty( $delete_result['deleted'] ) || true !== $delete_result['deleted'] ) {
820 return rest_ensure_response(
821 [
822 'success' => false,
823 'message' => __( 'Webhook deletion was not confirmed by Stripe.', 'sureforms' ),
824 ]
825 );
826 }
827
828 // Clean up stored webhook data from settings.
829 if ( 'live' === $mode ) {
830 $settings['webhook_live_secret'] = '';
831 $settings['webhook_live_id'] = '';
832 $settings['webhook_live_url'] = '';
833 } else {
834 $settings['webhook_test_secret'] = '';
835 $settings['webhook_test_id'] = '';
836 $settings['webhook_test_url'] = '';
837 }
838
839 $webhooks_deleted++;
840
841 } catch ( \Exception $e ) {
842 $error_message = $e->getMessage();
843 }
844 }
845
846 // Update settings if any webhooks were deleted.
847 if ( $webhooks_deleted > 0 ) {
848 Stripe_Helper::update_all_stripe_settings( $settings );
849 }
850
851 if ( $webhooks_deleted > 0 ) {
852 if ( count( $modes ) === 1 ) {
853 $mode_label = 'live' === $modes[0] ? __( 'live', 'sureforms' ) : __( 'test', 'sureforms' );
854 $message = sprintf(
855 /* translators: %s: mode name (test/live) */
856 __( 'Webhook deleted successfully for %s mode.', 'sureforms' ),
857 $mode_label
858 );
859 } else {
860 $message = sprintf(
861 /* translators: %d: number of modes */
862 __( 'Webhooks deleted successfully for %d mode(s).', 'sureforms' ),
863 $webhooks_deleted
864 );
865 }
866 return rest_ensure_response(
867 [
868 'success' => true,
869 'message' => $message,
870 ]
871 );
872 }
873 return rest_ensure_response(
874 [
875 'success' => false,
876 'message' => $error_message ? $error_message : __( 'Unable to delete webhook.', 'sureforms' ),
877 ]
878 );
879 }
880
881 /**
882 * Get Stripe account information using stored account ID
883 *
884 * @since 2.0.0
885 * @return string containing account name or empty string if not found
886 */
887 public function get_account_name() {
888 $settings = Stripe_Helper::get_all_stripe_settings();
889 if ( ! is_array( $settings ) ) {
890 $settings = Stripe_Helper::get_all_stripe_settings();
891 }
892
893 // Check if Stripe is connected.
894 if ( empty( $settings['stripe_connected'] ) ) {
895 return '';
896 }
897
898 // Get account ID.
899 $account_id = $settings['stripe_account_id'] ?? '';
900 if ( empty( $account_id ) || ! is_string( $account_id ) ) {
901 return '';
902 }
903
904 // Call Stripe API to get account information.
905 $api_response = Stripe_Helper::stripe_api_request( 'accounts', 'GET', [], (string) $account_id );
906
907 $get_data = isset( $api_response['data'] ) && is_array( $api_response['data'] ) ? $api_response['data'] : [];
908 $get_settings = isset( $get_data['settings'] ) && is_array( $get_data['settings'] ) ? $get_data['settings'] : [];
909 $get_dashboard = isset( $get_settings['dashboard'] ) && is_array( $get_settings['dashboard'] ) ? $get_settings['dashboard'] : [];
910 return isset( $get_dashboard['display_name'] ) && is_string( $get_dashboard['display_name'] ) ? $get_dashboard['display_name'] : '';
911 }
912
913 /**
914 * Process OAuth success response.
915 *
916 * This function processes the successful OAuth callback from Stripe and saves
917 * the API keys. Security checks have already been performed in intercept_stripe_callback().
918 *
919 * @since 2.0.0
920 * @return void
921 */
922 private function process_oauth_success() {
923 $response_data = isset( $_GET['response'] ) ? sanitize_text_field( wp_unslash( $_GET['response'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
924 $decoded = base64_decode( $response_data, true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
925 $response = false;
926 if ( is_string( $decoded ) ) {
927 $response = json_decode( $decoded, true );
928 }
929
930 if ( ! is_array( $response ) ) {
931 wp_die(
932 esc_html__( 'Invalid OAuth response format.', 'sureforms' ),
933 esc_html__( 'Stripe Connect Error', 'sureforms' ),
934 [ 'response' => 400 ]
935 );
936 }
937
938 // Extract OAuth data following checkout-plugins-stripe-woo pattern.
939 $settings = Stripe_Helper::get_all_stripe_settings();
940 $settings = is_array( $settings ) && ! empty( $settings ) ? $settings : Stripe_Helper::get_all_stripe_settings();
941
942 // Store live keys.
943 if ( isset( $response['live'] ) && is_array( $response['live'] ) ) {
944 $settings['stripe_live_publishable_key'] = sanitize_text_field( $response['live']['stripe_publishable_key'] ?? '' );
945 $settings['stripe_live_secret_key'] = sanitize_text_field( $response['live']['access_token'] ?? '' );
946 $settings['stripe_account_id'] = sanitize_text_field( $response['live']['stripe_user_id'] ?? '' );
947 }
948
949 // Store test keys.
950 if ( isset( $response['test'] ) && is_array( $response['test'] ) ) {
951 $settings['stripe_test_publishable_key'] = sanitize_text_field( $response['test']['stripe_publishable_key'] ?? '' );
952 $settings['stripe_test_secret_key'] = sanitize_text_field( $response['test']['access_token'] ?? '' );
953 }
954
955 // Mark as connected.
956 $settings['stripe_connected'] = true;
957 $settings['stripe_account_email'] = isset( $response['account'], $response['account']['email'] )
958 ? sanitize_email( $response['account']['email'] )
959 : '';
960
961 // Save settings.
962 Stripe_Helper::update_all_stripe_settings( $settings );
963
964 $account_name = $this->get_account_name();
965
966 if ( ! empty( $account_name ) && is_string( $account_name ) ) {
967 $settings['account_name'] = $account_name;
968 Stripe_Helper::update_all_stripe_settings( $settings );
969 }
970
971 // Clean up transients.
972 delete_transient( 'srfm_stripe_connect_nonce_' . get_current_user_id() );
973
974 // Create webhooks for both live and test mode.
975 $this->setup_stripe_webhooks();
976
977 // Redirect to SureForms payments settings with proper subpage and gateway parameters.
978 $redirect_url = add_query_arg(
979 [
980 'page' => 'sureforms_form_settings',
981 'tab' => 'payments-settings',
982 'subpage' => 'payment-methods',
983 'gateway' => 'stripe',
984 'connected' => '1',
985 ],
986 admin_url( 'admin.php' )
987 );
988
989 wp_safe_redirect( $redirect_url );
990 exit;
991 }
992
993 /**
994 * Process OAuth error response
995 *
996 * This function handles errors from the Stripe OAuth callback.
997 * Security checks have already been performed in intercept_stripe_callback().
998 *
999 * @since 2.0.0
1000 * @return void
1001 */
1002 private function process_oauth_error() {
1003 // Additional security check: Verify user capabilities.
1004 if ( ! current_user_can( 'manage_options' ) ) {
1005 wp_die(
1006 esc_html__( 'You do not have permission to connect Stripe.', 'sureforms' ),
1007 esc_html__( 'Permission Denied', 'sureforms' ),
1008 [ 'response' => 403 ]
1009 );
1010 }
1011
1012 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1013 if ( isset( $_GET['error'] ) ) {
1014 $error_data = sanitize_text_field( wp_unslash( $_GET['error'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1015 $decoded = base64_decode( $error_data, true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
1016 $error = is_string( $decoded ) ? json_decode( $decoded, true ) : [];
1017 if ( ! is_array( $error ) ) {
1018 $error = [];
1019 }
1020 } else {
1021 $error = [];
1022 }
1023
1024 $error_message = __( 'Unable to connect to Stripe.', 'sureforms' );
1025 if ( isset( $error['message'] ) && is_string( $error['message'] ) ) {
1026 $error_message = sanitize_text_field( $error['message'] );
1027 }
1028
1029 // Clean up transients.
1030 delete_transient( 'srfm_stripe_connect_nonce_' . get_current_user_id() );
1031
1032 // Redirect with error including proper subpage and gateway parameters.
1033 $redirect_url = add_query_arg(
1034 [
1035 'page' => 'sureforms_form_settings',
1036 'tab' => 'payments-settings',
1037 'subpage' => 'payment-methods',
1038 'gateway' => 'stripe',
1039 'error' => rawurlencode( $error_message ),
1040 ],
1041 admin_url( 'admin.php' )
1042 );
1043
1044 wp_safe_redirect( $redirect_url );
1045 exit;
1046 }
1047 }
1048