PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.3.0
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.3.0
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.2 2.2.1 2.2.0 2.1.2 2.1.1 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 All 66 releases
better-payment / includes / Blocks / BlockActions.php

BlockActions.php in Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More 2.3.0, at includes/Blocks/BlockActions.php

944 lines 43.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Block Actions for Better Payment Gutenberg blocks.
4 *
5 * Handles payment processing for Gutenberg block forms, separate from Elementor widget actions.
6 * This class DOES NOT modify any existing Elementor methods - it provides block-specific handlers.
7 *
8 * @package Better_Payment
9 * @since 1.0.0
10 */
11
12 namespace Better_Payment\Lite\Blocks;
13
14 use Better_Payment\Lite\Classes\Handler;
15 use Better_Payment\Lite\Traits\Helper as TraitsHelper;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit;
19 }
20
21 /**
22 * Block Actions class.
23 *
24 * @since 1.0.0
25 */
26 class BlockActions {
27 use TraitsHelper;
28
29 /**
30 * Constructor.
31 *
32 * Register block-specific payment handlers with higher priority.
33 * These run BEFORE the Elementor widget handlers.
34 *
35 * @since 1.0.0
36 */
37 public function __construct() {
38 add_action( 'admin_post_paypal_form_handle', array( $this, 'block_paypal_form_handle' ) );
39 add_action( 'admin_post_nopriv_paypal_form_handle', array( $this, 'block_paypal_form_handle' ) );
40
41 add_action( 'wp_ajax_better_payment_stripe_get_token', array( $this, 'block_stripe_get_token' ) );
42 add_action( 'wp_ajax_nopriv_better_payment_stripe_get_token', array( $this, 'block_stripe_get_token' ) );
43
44 add_action( 'wp_ajax_better_payment_paystack_get_token', array( $this, 'block_paystack_get_token' ) );
45 add_action( 'wp_ajax_nopriv_better_payment_paystack_get_token', array( $this, 'block_paystack_get_token' ) );
46 }
47
48 /**
49 * Get block settings.
50 *
51 * First tries to get from transient (set during page render).
52 * If not found, parses the block directly from post content (works even with caching).
53 *
54 * @param int $page_id The page ID.
55 * @param string $widget_id The widget/block ID.
56 * @return array|false The block settings or false if not found.
57 */
58 private function get_block_settings( $page_id, $widget_id ) {
59 // First, try to get from transient (fastest).
60 $transient_key = 'bp_block_settings_' . $page_id . '_' . $widget_id;
61 $settings = get_transient( $transient_key );
62
63 if ( ! empty( $settings ) && is_array( $settings ) ) {
64 return $settings;
65 }
66
67 // Transient not found - parse block from post content.
68 // This handles cases where the page is cached.
69 $settings = $this->parse_block_settings_from_post( $page_id, $widget_id );
70
71 if ( ! empty( $settings ) && is_array( $settings ) ) {
72 // Cache in transient for future requests.
73 set_transient( $transient_key, $settings, HOUR_IN_SECONDS );
74 return $settings;
75 }
76
77 return false;
78 }
79
80 /**
81 * Parse block settings directly from post content.
82 *
83 * This method finds the Better Payment block in the post content and extracts its attributes.
84 * Similar to how Elementor's get_elementor_widget_settings works.
85 *
86 * @param int $page_id The page ID.
87 * @param string $widget_id The widget/block ID (blockId attribute).
88 * @return array|false The block settings or false if not found.
89 */
90 private function parse_block_settings_from_post( $page_id, $widget_id ) {
91 $post = get_post( $page_id );
92
93 if ( ! $post || empty( $post->post_content ) ) {
94 return false;
95 }
96
97 // Parse blocks from post content.
98 $blocks = parse_blocks( $post->post_content );
99
100 // Find the Better Payment block with matching blockId.
101 $block_attributes = $this->find_payment_block_recursive( $blocks, $widget_id );
102
103 if ( empty( $block_attributes ) ) {
104 return false;
105 }
106
107 // Get global settings.
108 $global_settings = \Better_Payment\Lite\Admin\DB::get_settings();
109
110 // Build settings using BlockManager's method.
111 $form_layout = ! empty( $block_attributes['formLayout'] ) ? $block_attributes['formLayout'] : 'layout-1';
112 $settings = BlockManager::get_instance()->build_block_settings( $block_attributes, $global_settings, $form_layout );
113
114 return $settings;
115 }
116
117 /**
118 * Recursively find a Better Payment block with matching blockId.
119 *
120 * @param array $blocks Array of parsed blocks.
121 * @param string $widget_id The blockId to find.
122 * @return array|false Block attributes or false if not found.
123 */
124 private function find_payment_block_recursive( $blocks, $widget_id ) {
125 foreach ( $blocks as $block ) {
126 // Check if this is a Better Payment block with matching blockId.
127 if ( 'better-payment/payment-form' === $block['blockName'] ) {
128 $block_id = isset( $block['attrs']['blockId'] ) ? $block['attrs']['blockId'] : '';
129
130 if ( $block_id === $widget_id ) {
131 return $block['attrs'];
132 }
133 }
134
135 // Check inner blocks.
136 if ( ! empty( $block['innerBlocks'] ) ) {
137 $found = $this->find_payment_block_recursive( $block['innerBlocks'], $widget_id );
138 if ( $found ) {
139 return $found;
140 }
141 }
142 }
143
144 return false;
145 }
146
147 /**
148 * Check if this is a block form submission.
149 *
150 * @param int $page_id The page ID.
151 * @param string $widget_id The widget/block ID.
152 * @return bool True if this is a block form, false otherwise.
153 */
154 private function is_block_form( $page_id, $widget_id ) {
155 $settings = $this->get_block_settings( $page_id, $widget_id );
156 return ! empty( $settings ) && is_array( $settings );
157 }
158
159 /**
160 * Handle PayPal form submission for Gutenberg blocks.
161 *
162 * This intercepts the form submission before the Elementor handler.
163 * If settings are found in transient (block form), process here.
164 * Otherwise, return and let the Elementor handler process it.
165 *
166 * @since 1.0.0
167 */
168 public function block_paypal_form_handle() {
169 // phpcs:disable WordPress.Security.NonceVerification.Missing
170 // Bail immediately if not a Gutenberg block submission — zero DB overhead for Elementor forms.
171 if ( empty( $_POST['better_payment_source'] ) || 'gutenberg' !== $_POST['better_payment_source'] ) {
172 return;
173 }
174 // phpcs:enable WordPress.Security.NonceVerification.Missing
175
176 // Verify nonce.
177 if ( ! check_admin_referer( 'better-payment-paypal', 'security' ) ) {
178 return;
179 }
180
181 // phpcs:disable WordPress.Security.NonceVerification.Missing
182 $page_id = ! empty( $_POST['better_payment_page_id'] ) ? intval( $_POST['better_payment_page_id'] ) : 0;
183 $widget_id = ! empty( $_POST['better_payment_widget_id'] ) ? sanitize_text_field( $_POST['better_payment_widget_id'] ) : '';
184 // phpcs:enable WordPress.Security.NonceVerification.Missing
185
186 if ( empty( $page_id ) || empty( $widget_id ) ) {
187 return; // Let the original handler deal with missing data.
188 }
189
190 // Check if this is a block form by looking for transient settings.
191 if ( ! $this->is_block_form( $page_id, $widget_id ) ) {
192 return; // Not a block form, let Elementor handler process it.
193 }
194
195 // This is a block form - handle the payment here.
196 $this->process_paypal_payment( $page_id, $widget_id );
197 }
198
199 /**
200 * Process PayPal payment for block forms.
201 *
202 * @param int $page_id The page ID.
203 * @param string $widget_id The widget/block ID.
204 */
205 private function process_paypal_payment( $page_id, $widget_id ) {
206 $el_settings = $this->get_block_settings( $page_id, $widget_id );
207
208 if ( empty( $el_settings ) ) {
209 $this->redirect_previous_page();
210 return;
211 }
212
213 if ( empty( $el_settings['better_payment_paypal_business_email'] ) ) {
214 $this->redirect_previous_page();
215 return;
216 }
217
218 if ( 'yes' === $el_settings['better_payment_paypal_live_mode'] ) {
219 $path = 'paypal';
220 } else {
221 $path = 'sandbox.paypal';
222 }
223
224 $el_settings_currency = $el_settings['better_payment_form_currency'];
225 $woo_product_id = ! empty( $el_settings['better_payment_form_woocommerce_product_id'] ) ? intval( $el_settings['better_payment_form_woocommerce_product_id'] ) : 0;
226 $woo_product_ids = ! empty( $el_settings['better_payment_form_woocommerce_product_ids'] ) ? $el_settings['better_payment_form_woocommerce_product_ids'] : array( 0 );
227 $fluentcart_product_id = ! empty( $el_settings['better_payment_form_fluentcart_product_id'] ) ? intval( $el_settings['better_payment_form_fluentcart_product_id'] ) : 0;
228 $fluentcart_product_ids = ! empty( $el_settings['better_payment_form_fluentcart_product_ids'] ) ? $el_settings['better_payment_form_fluentcart_product_ids'] : array( 0 );
229 $is_layout_6 = ! empty( $el_settings['better_payment_form_layout'] ) && 'layout-6-pro' === $el_settings['better_payment_form_layout'];
230 $is_fluentcart_layout = $is_layout_6 && ! empty( $el_settings['better_payment_form_layout_6_ecommerce_platform'] ) && 'fluentcart' === $el_settings['better_payment_form_layout_6_ecommerce_platform'];
231 $is_woo_layout = $is_layout_6 && ( empty( $el_settings['better_payment_form_layout_6_ecommerce_platform'] ) || 'woocommerce' === $el_settings['better_payment_form_layout_6_ecommerce_platform'] );
232
233 // Currency handling.
234 if ( ! empty( $el_settings['better_payment_form_currency_use_woocommerce'] ) && 'yes' === $el_settings['better_payment_form_currency_use_woocommerce'] &&
235 ! empty( $el_settings['better_payment_form_currency_woocommerce'] ) ) {
236 $el_settings_currency = $el_settings['better_payment_form_currency_woocommerce'];
237 }
238 // phpcs:disable WordPress.Security.NonceVerification.Missing
239 if ( ! empty( $_POST['campaign_currency'] ) ) {
240 $el_settings_currency = sanitize_text_field( $_POST['campaign_currency'] );
241 }
242 // phpcs:enable WordPress.Security.NonceVerification.Missing
243
244 $el_settings_currency_symbol = $this->get_currency_symbol( esc_html( $el_settings_currency ) );
245
246 // phpcs:disable WordPress.Security.NonceVerification.Missing
247 $primary_payment_amount = isset( $_POST['primary_payment_amount'] ) ? floatval( $_POST['primary_payment_amount'] ) : 0;
248
249 if ( empty( $_POST['primary_payment_amount'] ) && ! empty( $_POST['primary_payment_amount_radio'] ) ) {
250 $primary_payment_amount = floatval( $_POST['primary_payment_amount_radio'] );
251 }
252
253 $primary_payment_amount_quantity = ! empty( $_POST['payment_amount_quantity'] ) ? intval( $_POST['payment_amount_quantity'] ) : '';
254 if ( $is_woo_layout ) {
255 $primary_payment_amount_quantity = 1;
256 }
257
258 $primary_payment_amount = ! empty( $primary_payment_amount_quantity ) ? $primary_payment_amount * $primary_payment_amount_quantity : $primary_payment_amount;
259
260 if ( $primary_payment_amount <= 0 ) {
261 $this->redirect_previous_page();
262 return;
263 }
264
265 $order_id = 'paypal_' . uniqid();
266 $paypal_button_type = ! empty( $el_settings['better_payment_paypal_button_type'] ) ? $el_settings['better_payment_paypal_button_type'] : '_xclick';
267 $site_url = get_permalink( $page_id );
268 $return_url = ! empty( $_POST['return'] ) ? wp_validate_redirect( esc_url_raw( $_POST['return'] ), $site_url ) : '';
269 $cancel_return_url = ! empty( $_POST['cancel_return'] ) ? wp_validate_redirect( esc_url_raw( $_POST['cancel_return'] ), $site_url ) : '';
270 $cancel_return_url = add_query_arg( 'better_payment_paypal_id', $order_id, $cancel_return_url );
271
272 $request_data = array(
273 'business' => $el_settings['better_payment_paypal_business_email'],
274 'currency_code' => $el_settings_currency,
275 'rm' => '2',
276 'return' => $return_url,
277 'cancel_return' => $cancel_return_url,
278 'item_number' => $order_id,
279 'item_name' => ! empty( $el_settings['better_payment_form_title'] ) ? esc_html__( $el_settings['better_payment_form_title'], 'better-payment' ) : esc_html__( 'Better Payment', 'better-payment' ),
280 'amount' => $primary_payment_amount,
281 'cmd' => $paypal_button_type,
282 'notify_url' => admin_url( 'admin-post.php?action=better_payment_paypal_ipn' ),
283 );
284
285 $product_ids = array(
286 'woo_product_ids' => $woo_product_ids,
287 'fluentcart_product_ids' => $fluentcart_product_ids,
288 );
289
290 $detailed_product_info = $this->get_detailed_product_info( $product_ids );
291
292 // Form fields data to send via email.
293 $better_form_fields = array(
294 'amount' => sanitize_text_field( $el_settings_currency_symbol ) . $primary_payment_amount,
295 'referer_page_id' => $page_id,
296 'referer_widget_id' => $widget_id,
297 'woo_product_id' => $woo_product_id,
298 'woo_product_ids' => maybe_serialize( $woo_product_ids ),
299 'fluentcart_product_id' => $fluentcart_product_id,
300 'fluentcart_product_ids' => maybe_serialize( $fluentcart_product_ids ),
301 'source' => 'paypal',
302 'amount_quantity' => ! empty( $primary_payment_amount_quantity ) ? intval( $primary_payment_amount_quantity ) : '',
303 'is_woo_layout' => $is_woo_layout,
304 'is_fluentcart_layout' => $is_fluentcart_layout,
305 'detailed_product_info' => maybe_serialize( $detailed_product_info ),
306 'paypal_business_email' => sanitize_email( $el_settings['better_payment_paypal_business_email'] ),
307 // Persisted so the async PayPal IPN can rebuild the e-mail body + on/off toggle.
308 'email_settings' => Handler::extract_email_settings( $el_settings ),
309 );
310
311 $better_form_fields = array_merge( $better_form_fields, $this->fetch_better_form_fields( $el_settings, $_POST ) );
312
313 if ( ! empty( $better_form_fields['primary_first_name'] ) ) {
314 $request_data['primary_first_name'] = sanitize_text_field( $better_form_fields['primary_first_name'] );
315 }
316
317 if ( ! empty( $better_form_fields['primary_last_name'] ) ) {
318 $request_data['primary_last_name'] = sanitize_text_field( $better_form_fields['primary_last_name'] );
319 }
320
321 if ( ! empty( $better_form_fields['primary_email'] ) ) {
322 $request_data['primary_email'] = sanitize_email( $better_form_fields['primary_email'] );
323 }
324
325 if ( ! empty( $better_form_fields['primary_reference_number'] ) ) {
326 $request_data['invoice'] = sanitize_text_field( $better_form_fields['primary_reference_number'] );
327 }
328
329 $campaign_id = ! empty( $_POST['campaign_id'] ) ? sanitize_text_field( $_POST['campaign_id'] ) : '';
330 // phpcs:enable WordPress.Security.NonceVerification.Missing
331
332 Handler::payment_create(
333 array(
334 'amount' => floatval( $primary_payment_amount ),
335 'order_id' => $order_id,
336 'payment_date' => gmdate( 'Y-m-d H:i:s' ),
337 'source' => 'paypal',
338 'form_fields_info' => maybe_serialize( $better_form_fields ),
339 'currency' => sanitize_text_field( $el_settings_currency ),
340 'referer' => 'gutenberg-block',
341 'campaign_id' => $campaign_id,
342 )
343 );
344
345 $paypal_url = "https://www.$path.com/cgi-bin/webscr?";
346 $paypal_url .= http_build_query( $request_data );
347
348 wp_redirect( esc_url_raw( $paypal_url ) );
349 exit;
350 }
351
352 /**
353 * Handle Stripe token request for Gutenberg blocks.
354 *
355 * @since 1.0.0
356 */
357 public function block_stripe_get_token() {
358 // phpcs:disable WordPress.Security.NonceVerification.Missing
359 // Bail immediately if not a Gutenberg block submission — zero DB overhead for Elementor forms.
360 if ( empty( $_POST['setting_data']['source'] ) || 'gutenberg' !== $_POST['setting_data']['source'] ) {
361 return;
362 }
363 // phpcs:enable WordPress.Security.NonceVerification.Missing
364
365 // Verify nonce.
366 if ( ! check_admin_referer( 'better-payment', 'security' ) ) {
367 wp_send_json_error( esc_html__( 'Nonce verification failed.', 'better-payment' ) );
368 return;
369 }
370
371 // phpcs:disable WordPress.Security.NonceVerification.Missing
372 $page_id = isset( $_POST['setting_data']['page_id'] ) ? intval( $_POST['setting_data']['page_id'] ) : 0;
373 $widget_id = isset( $_POST['setting_data']['widget_id'] ) ? sanitize_text_field( $_POST['setting_data']['widget_id'] ) : '';
374 // phpcs:enable WordPress.Security.NonceVerification.Missing
375
376 if ( empty( $page_id ) || empty( $widget_id ) ) {
377 return; // Let the original handler deal with missing data.
378 }
379
380 // Check if this is a block form by looking for settings.
381 if ( ! $this->is_block_form( $page_id, $widget_id ) ) {
382 return; // Not a block form, let Elementor handler process it.
383 }
384
385 // This is a block form - handle the Stripe payment here.
386 $this->process_stripe_payment( $page_id, $widget_id );
387 }
388
389 /**
390 * Handle Paystack token request for Gutenberg blocks.
391 *
392 * @since 1.0.0
393 */
394 public function block_paystack_get_token() {
395 // phpcs:disable WordPress.Security.NonceVerification.Missing
396 // Bail immediately if not a Gutenberg block submission — zero DB overhead for Elementor forms.
397 if ( empty( $_POST['setting_data']['source'] ) || 'gutenberg' !== $_POST['setting_data']['source'] ) {
398 return;
399 }
400 // phpcs:enable WordPress.Security.NonceVerification.Missing
401
402 // Verify nonce.
403 if ( ! check_admin_referer( 'better-payment', 'security' ) ) {
404 return;
405 }
406
407 // phpcs:disable WordPress.Security.NonceVerification.Missing
408 $page_id = isset( $_POST['setting_data']['page_id'] ) ? intval( $_POST['setting_data']['page_id'] ) : 0;
409 $widget_id = isset( $_POST['setting_data']['widget_id'] ) ? sanitize_text_field( $_POST['setting_data']['widget_id'] ) : '';
410 // phpcs:enable WordPress.Security.NonceVerification.Missing
411
412 if ( empty( $page_id ) || empty( $widget_id ) ) {
413 return; // Let the original handler deal with missing data.
414 }
415
416 // Check if this is a block form by looking for transient settings.
417 if ( ! $this->is_block_form( $page_id, $widget_id ) ) {
418 return; // Not a block form, let Elementor handler process it.
419 }
420
421 // This is a block form - handle the Paystack payment here.
422 $this->process_paystack_payment( $page_id, $widget_id );
423 }
424
425 /**
426 * Process Stripe payment for block forms.
427 *
428 * @param int $page_id The page ID.
429 * @param string $widget_id The widget/block ID.
430 */
431 private function process_stripe_payment( $page_id, $widget_id ) {
432 $el_settings = $this->get_block_settings( $page_id, $widget_id );
433
434 if ( empty( $el_settings ) ) {
435 wp_send_json_error( esc_html__( 'Setting Data is missing', 'better-payment' ) );
436 }
437
438 $better_payment_keys = array(
439 'public_key' => 'yes' === sanitize_text_field( $el_settings['better_payment_stripe_live_mode'] ) ? sanitize_text_field( $el_settings['better_payment_stripe_public_key_live'] ) : sanitize_text_field( $el_settings['better_payment_stripe_public_key'] ),
440 'secret_key' => 'yes' === sanitize_text_field( $el_settings['better_payment_stripe_live_mode'] ) ? sanitize_text_field( $el_settings['better_payment_stripe_secret_key_live'] ) : sanitize_text_field( $el_settings['better_payment_stripe_secret_key'] ),
441 );
442
443 if ( empty( $better_payment_keys['public_key'] ) || empty( $better_payment_keys['secret_key'] ) ) {
444 wp_send_json_error( esc_html__( 'Stripe Key missing', 'better-payment' ) );
445 }
446
447 // phpcs:disable WordPress.Security.NonceVerification.Missing
448 $amount = isset( $_POST['fields']['primary_payment_amount'] ) ? floatval( $_POST['fields']['primary_payment_amount'] ) : 0;
449
450 if ( empty( $_POST['fields']['primary_payment_amount'] ) && ! empty( $_POST['fields']['primary_payment_amount_radio'] ) ) {
451 $amount = floatval( $_POST['fields']['primary_payment_amount_radio'] );
452 }
453
454 $amount_quantity = ! empty( $_POST['fields']['payment_amount_quantity'] ) ? intval( $_POST['fields']['payment_amount_quantity'] ) : '';
455 $amount = ! empty( $amount_quantity ) ? $amount * $amount_quantity : $amount;
456
457 if ( $amount <= 0 ) {
458 wp_send_json_error( esc_html__( 'Invalid payment amount.', 'better-payment' ) );
459 return;
460 }
461
462 $header_info = array(
463 'Authorization' => 'Basic ' . base64_encode( sanitize_text_field( $better_payment_keys['secret_key'] ) . ':' ),
464 'Stripe-Version' => '2019-05-16',
465 );
466
467 $order_id = 'stripe_' . uniqid();
468 $el_settings_currency = $el_settings['better_payment_form_currency'];
469
470 if ( ! empty( $el_settings['better_payment_form_currency_use_woocommerce'] ) && 'yes' === $el_settings['better_payment_form_currency_use_woocommerce'] &&
471 ! empty( $el_settings['better_payment_form_currency_woocommerce'] ) ) {
472 $el_settings_currency = $el_settings['better_payment_form_currency_woocommerce'];
473 }
474 if ( ! empty( $_POST['fields']['campaign_currency'] ) ) {
475 $el_settings_currency = sanitize_text_field( $_POST['fields']['campaign_currency'] );
476 }
477
478 $el_settings_currency_symbol = $this->get_currency_symbol( esc_html( $el_settings_currency ) );
479
480 $redirection_url_success = get_permalink( $page_id );
481 $redirection_url_error = get_permalink( $page_id );
482
483 $redirection_url_success = add_query_arg(
484 array(
485 'better_payment_stripe_status' => 'success',
486 'better_payment_widget_id' => $widget_id,
487 ),
488 $redirection_url_success
489 );
490
491 $redirection_url_error = add_query_arg(
492 array(
493 'better_payment_error_status' => 'error',
494 'better_payment_widget_id' => $widget_id,
495 ),
496 $redirection_url_error
497 );
498
499 // Build form fields.
500 $woo_product_id = ! empty( $el_settings['better_payment_form_woocommerce_product_id'] ) ? intval( $el_settings['better_payment_form_woocommerce_product_id'] ) : 0;
501 $woo_product_ids = ! empty( $el_settings['better_payment_form_woocommerce_product_ids'] ) ? $el_settings['better_payment_form_woocommerce_product_ids'] : array( 0 );
502 $fluentcart_product_id = ! empty( $el_settings['better_payment_form_fluentcart_product_id'] ) ? intval( $el_settings['better_payment_form_fluentcart_product_id'] ) : 0;
503 $fluentcart_product_ids = ! empty( $el_settings['better_payment_form_fluentcart_product_ids'] ) ? $el_settings['better_payment_form_fluentcart_product_ids'] : array( 0 );
504
505 $product_ids = array(
506 'woo_product_ids' => $woo_product_ids,
507 'fluentcart_product_ids' => $fluentcart_product_ids,
508 );
509
510 $detailed_product_info = $this->get_detailed_product_info( $product_ids );
511
512 $better_form_fields = array(
513 'amount' => sanitize_text_field( $el_settings_currency_symbol ) . $amount,
514 'referer_page_id' => $page_id,
515 'referer_widget_id' => $widget_id,
516 'woo_product_id' => $woo_product_id,
517 'woo_product_ids' => maybe_serialize( $woo_product_ids ),
518 'fluentcart_product_id' => $fluentcart_product_id,
519 'fluentcart_product_ids' => maybe_serialize( $fluentcart_product_ids ),
520 'source' => 'stripe',
521 'amount_quantity' => ! empty( $amount_quantity ) ? intval( $amount_quantity ) : '',
522 'detailed_product_info' => maybe_serialize( $detailed_product_info ),
523 );
524
525 $better_form_fields = array_merge( $better_form_fields, $this->fetch_better_form_fields( $el_settings, $_POST['fields'] ) );
526
527 $item_name = ! empty( $el_settings['better_payment_form_title'] ) ? esc_html__( $el_settings['better_payment_form_title'], 'better-payment' ) : esc_html__( 'Better Payment', 'better-payment' );
528
529 // The Stripe Price ID (when Payment Source = Stripe) is display-only —
530 // it pre-fills the amount input on the frontend (see layout-1/2/3.php line input value).
531 // The payment session always uses price_data with the submitted amount,
532 // matching the Elementor widget behaviour for layouts 1/2/3.
533 $line_item = array(
534 'price_data' => array(
535 'currency' => sanitize_text_field( $el_settings_currency ),
536 'unit_amount' => intval( $amount * 100 ),
537 'product_data' => array(
538 'name' => $item_name,
539 ),
540 ),
541 'quantity' => 1,
542 );
543
544 $request_body = array(
545 'line_items' => array( $line_item ),
546 'mode' => 'payment',
547 'locale' => 'auto',
548 'payment_method_types' => array( 'card' ),
549 'billing_address_collection' => 'required',
550 'client_reference_id' => time(),
551 'metadata' => array(
552 'order_id' => $order_id,
553 ),
554 'success_url' => esc_url_raw( $redirection_url_success ) . '&better_payment_stripe_id=' . $order_id,
555 'cancel_url' => add_query_arg(
556 array(
557 'better_payment_stripe_id' => $order_id,
558 ),
559 esc_url_raw( $redirection_url_error )
560 ),
561 );
562
563 $request_body['payment_intent_data'] = array(
564 'capture_method' => 'automatic',
565 'description' => $item_name,
566 'metadata' => array(
567 'order_id' => $order_id,
568 ),
569 );
570
571 $primary_email = ! empty( $better_form_fields['primary_email'] ) ? sanitize_email( $better_form_fields['primary_email'] ) : '';
572
573 if ( ! empty( $primary_email ) ) {
574 $request_body['customer_email'] = $primary_email;
575 $request_body['metadata']['customer_email'] = $primary_email;
576 $request_body['payment_intent_data']['metadata']['customer_email'] = $primary_email;
577 }
578
579 // Build customer name from first/last name fields (mirrors widget behaviour).
580 $customer_name = '';
581 if ( ! empty( $better_form_fields['primary_first_name'] ) ) {
582 $customer_name = sanitize_text_field( $better_form_fields['primary_first_name'] );
583 }
584 if ( ! empty( $better_form_fields['primary_last_name'] ) ) {
585 $customer_name = trim( $customer_name . ' ' . sanitize_text_field( $better_form_fields['primary_last_name'] ) );
586 }
587
588 if ( ! empty( $customer_name ) ) {
589 $request_body['metadata']['customer_name'] = $customer_name;
590 $request_body['payment_intent_data']['metadata']['customer_name'] = $customer_name;
591 }
592
593 $request = wp_remote_post(
594 'https://api.stripe.com/v1/checkout/sessions',
595 array(
596 'headers' => $header_info,
597 'body' => $request_body,
598 )
599 );
600
601 if ( is_wp_error( $request ) ) {
602 wp_send_json_error( sanitize_text_field( $request->get_error_message() ) );
603 return;
604 }
605
606 $response_ar = json_decode( wp_remote_retrieve_body( $request ) );
607
608 if ( null === $response_ar ) {
609 wp_send_json_error( esc_html__( 'Invalid response from Stripe.', 'better-payment' ) );
610 return;
611 }
612
613 if ( ! empty( $response_ar->payment_intent ) || ( ! empty( $response_ar->mode ) && 'subscription' === $response_ar->mode ) ) {
614 $campaign_id = ! empty( $_POST['fields']['campaign_id'] ) ? sanitize_text_field( $_POST['fields']['campaign_id'] ) : '';
615
616 Handler::payment_create(
617 array(
618 'amount' => floatval( $amount ),
619 'order_id' => $order_id,
620 'payment_date' => gmdate( 'Y-m-d H:i:s' ),
621 'source' => 'stripe',
622 'transaction_id' => sanitize_text_field( $response_ar->payment_intent ),
623 'customer_info' => maybe_serialize( $response_ar ),
624 'form_fields_info' => maybe_serialize( $better_form_fields ),
625 'obj_id' => sanitize_text_field( $response_ar->id ),
626 'status' => sanitize_text_field( $response_ar->payment_status ),
627 'currency' => sanitize_text_field( $el_settings_currency ),
628 'referer' => 'gutenberg-block',
629 'campaign_id' => $campaign_id,
630 )
631 );
632
633 wp_send_json_success(
634 array(
635 'stripe_data' => sanitize_text_field( $response_ar->id ),
636 'stripe_public_key' => sanitize_text_field( $better_payment_keys['public_key'] ),
637 )
638 );
639 } else {
640 $error_message = 'Something went wrong!';
641
642 if ( isset( $response_ar->error ) ) {
643 $error_message = sanitize_text_field( $response_ar->error->message );
644 }
645
646 wp_send_json_error( $error_message );
647 }
648 // phpcs:enable WordPress.Security.NonceVerification.Missing
649 }
650
651 /**
652 * Process Paystack payment for block forms.
653 *
654 * @param int $page_id The page ID.
655 * @param string $widget_id The widget/block ID.
656 */
657 private function process_paystack_payment( $page_id, $widget_id ) {
658 $el_settings = $this->get_block_settings( $page_id, $widget_id );
659
660 if ( empty( $el_settings ) ) {
661 wp_send_json_error( esc_html__( 'Setting Data is missing', 'better-payment' ) );
662 }
663
664 if ( empty( $el_settings['better_payment_paystack_public_key'] ) || empty( $el_settings['better_payment_paystack_secret_key'] ) ) {
665 wp_send_json_error( esc_html__( 'Paystack Key missing', 'better-payment' ) );
666 }
667
668 // phpcs:disable WordPress.Security.NonceVerification.Missing
669 $amount = isset( $_POST['fields']['primary_payment_amount'] ) ? floatval( $_POST['fields']['primary_payment_amount'] ) : 0;
670
671 if ( empty( $_POST['fields']['primary_payment_amount'] ) && ! empty( $_POST['fields']['primary_payment_amount_radio'] ) ) {
672 $amount = floatval( $_POST['fields']['primary_payment_amount_radio'] );
673 }
674
675 $amount_quantity = ! empty( $_POST['fields']['payment_amount_quantity'] ) ? intval( $_POST['fields']['payment_amount_quantity'] ) : '';
676 $amount = ! empty( $amount_quantity ) ? $amount * $amount_quantity : $amount;
677
678 if ( $amount <= 0 ) {
679 wp_send_json_error( esc_html__( 'Invalid payment amount.', 'better-payment' ) );
680 return;
681 }
682
683 $header_info = array(
684 'Authorization' => 'Bearer ' . sanitize_text_field( $el_settings['better_payment_paystack_secret_key'] ),
685 'Cache-Control: no-cache',
686 );
687
688 $order_id = 'paystack_' . uniqid();
689 $el_settings_currency = $el_settings['better_payment_form_currency'];
690
691 if ( ! empty( $el_settings['better_payment_form_currency_use_woocommerce'] ) && 'yes' === $el_settings['better_payment_form_currency_use_woocommerce'] &&
692 ! empty( $el_settings['better_payment_form_currency_woocommerce'] ) ) {
693 $el_settings_currency = $el_settings['better_payment_form_currency_woocommerce'];
694 }
695 if ( ! empty( $_POST['fields']['campaign_currency'] ) ) {
696 $el_settings_currency = sanitize_text_field( $_POST['fields']['campaign_currency'] );
697 }
698
699 $el_settings_currency_symbol = $this->get_currency_symbol( esc_html( $el_settings_currency ) );
700
701 $redirection_url_success = get_permalink( $page_id );
702 $redirection_url_error = get_permalink( $page_id );
703
704 $redirection_url_success = add_query_arg(
705 array(
706 'better_payment_paystack_status' => 'success',
707 'better_payment_widget_id' => $widget_id,
708 ),
709 $redirection_url_success
710 );
711
712 $redirection_url_error = add_query_arg(
713 array(
714 'better_payment_error_status' => 'error',
715 'better_payment_widget_id' => $widget_id,
716 ),
717 $redirection_url_error
718 );
719
720 // Build form fields.
721 $woo_product_id = ! empty( $el_settings['better_payment_form_woocommerce_product_id'] ) ? intval( $el_settings['better_payment_form_woocommerce_product_id'] ) : 0;
722 $woo_product_ids = ! empty( $el_settings['better_payment_form_woocommerce_product_ids'] ) ? $el_settings['better_payment_form_woocommerce_product_ids'] : array( 0 );
723 $fluentcart_product_id = ! empty( $el_settings['better_payment_form_fluentcart_product_id'] ) ? intval( $el_settings['better_payment_form_fluentcart_product_id'] ) : 0;
724 $fluentcart_product_ids = ! empty( $el_settings['better_payment_form_fluentcart_product_ids'] ) ? $el_settings['better_payment_form_fluentcart_product_ids'] : array( 0 );
725
726 $product_ids = array(
727 'woo_product_ids' => $woo_product_ids,
728 'fluentcart_product_ids' => $fluentcart_product_ids,
729 );
730
731 $detailed_product_info = $this->get_detailed_product_info( $product_ids );
732
733 $better_form_fields = array(
734 'amount' => sanitize_text_field( $el_settings_currency_symbol ) . $amount,
735 'referer_page_id' => $page_id,
736 'referer_widget_id' => $widget_id,
737 'woo_product_id' => $woo_product_id,
738 'woo_product_ids' => maybe_serialize( $woo_product_ids ),
739 'fluentcart_product_id' => $fluentcart_product_id,
740 'fluentcart_product_ids' => maybe_serialize( $fluentcart_product_ids ),
741 'source' => 'paystack',
742 'amount_quantity' => ! empty( $amount_quantity ) ? intval( $amount_quantity ) : '',
743 'detailed_product_info' => maybe_serialize( $detailed_product_info ),
744 );
745
746 $better_form_fields = array_merge( $better_form_fields, $this->fetch_better_form_fields( $el_settings, $_POST['fields'] ) );
747
748 $primary_email = ! empty( $better_form_fields['primary_email'] ) ? sanitize_email( $better_form_fields['primary_email'] ) : '';
749
750 $request_body = array(
751 'amount' => intval( $amount * 100 ),
752 'currency' => sanitize_text_field( $el_settings_currency ),
753 'email' => $primary_email,
754 'callback_url' => esc_url_raw( $redirection_url_success ) . '&better_payment_paystack_id=' . $order_id,
755 'metadata' => array(
756 'cancel_action' => add_query_arg(
757 array(
758 'better_payment_paystack_id' => $order_id,
759 ),
760 esc_url_raw( $redirection_url_error )
761 ),
762 ),
763 );
764
765 $request = wp_remote_post(
766 'https://api.paystack.co/transaction/initialize',
767 array(
768 'headers' => $header_info,
769 'body' => $request_body,
770 )
771 );
772
773 if ( is_wp_error( $request ) ) {
774 wp_send_json_error( sanitize_text_field( $request->get_error_message() ) );
775 return;
776 }
777
778 $response_ar = json_decode( wp_remote_retrieve_body( $request ) );
779
780 if ( null === $response_ar ) {
781 wp_send_json_error( esc_html__( 'Invalid response from Paystack.', 'better-payment' ) );
782 return;
783 }
784
785 if ( empty( $response_ar->status ) || empty( $response_ar->data ) ) {
786 $error_message = ! empty( $response_ar->message ) ? sanitize_text_field( $response_ar->message ) : 'Something went wrong!';
787
788 if ( isset( $response_ar->error ) ) {
789 $error_message = sanitize_text_field( $response_ar->error->message );
790 }
791
792 wp_send_json_error( $error_message );
793 }
794
795 $campaign_id = ! empty( $_POST['fields']['campaign_id'] ) ? sanitize_text_field( $_POST['fields']['campaign_id'] ) : '';
796
797 Handler::payment_create(
798 array(
799 'amount' => floatval( $amount ),
800 'order_id' => $order_id,
801 'payment_date' => gmdate( 'Y-m-d H:i:s' ),
802 'source' => 'paystack',
803 'transaction_id' => '',
804 'customer_info' => maybe_serialize( $response_ar ),
805 'form_fields_info' => maybe_serialize( $better_form_fields ),
806 'status' => 'unpaid',
807 'currency' => sanitize_text_field( $el_settings_currency ),
808 'referer' => 'gutenberg-block',
809 'campaign_id' => $campaign_id,
810 )
811 );
812
813 $authorization_url = ! empty( $response_ar->data->authorization_url ) ? esc_url_raw( $response_ar->data->authorization_url ) : '';
814
815 wp_send_json_success(
816 array(
817 'authorization_url' => $authorization_url,
818 )
819 );
820 // phpcs:enable WordPress.Security.NonceVerification.Missing
821 }
822
823 /**
824 * Redirect to referer page.
825 *
826 * @since 1.0.0
827 */
828 public function redirect_previous_page() {
829 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
830 $location = isset( $_SERVER['HTTP_REFERER'] ) ? wp_unslash( $_SERVER['HTTP_REFERER'] ) : home_url();
831 wp_safe_redirect( esc_url_raw( $location ) );
832 exit();
833 }
834
835 /**
836 * Get detailed product information.
837 *
838 * @param array $product_ids Array of product IDs.
839 * @param array $product_quantities Array of product quantities.
840 * @return array Detailed product information.
841 */
842 public function get_detailed_product_info( $product_ids, $product_quantities = array() ) {
843 $detailed_product_info = array();
844
845 if ( function_exists( 'wc_get_product' ) && ! empty( $product_ids['woo_product_ids'] ) ) {
846 foreach ( $product_ids['woo_product_ids'] as $key => $product_id ) {
847 if ( empty( $product_id ) ) {
848 continue;
849 }
850
851 $product = wc_get_product( $product_id );
852 if ( $product ) {
853 $quantity = isset( $product_quantities[ $key ] ) ? intval( $product_quantities[ $key ] ) : 1;
854 $price = floatval( $product->get_price() );
855 $total_price = $price * $quantity;
856
857 $detailed_product_info['woo_products'][ $product_id ] = array(
858 'name' => sanitize_text_field( $product->get_name() ),
859 'product_id' => intval( $product_id ),
860 'permalink' => esc_url( $product->get_permalink() ),
861 'image_src' => esc_url( wp_get_attachment_url( $product->get_image_id() ) ),
862 'price' => $price,
863 'quantity' => $quantity,
864 'total_price' => $total_price,
865 );
866 }
867 }
868 }
869
870 return $detailed_product_info;
871 }
872
873 /**
874 * Fetch form fields from POST data.
875 *
876 * @param array $el_settings Widget/block settings.
877 * @param array $post_data_form_fields POST data.
878 * @return array Form fields data.
879 */
880 public function fetch_better_form_fields( $el_settings, $post_data_form_fields ) {
881 $better_form_fields = array();
882
883 $post_data_primary_first_name = '';
884 $post_data_primary_last_name = '';
885 $post_data_primary_email = '';
886
887 $post_fields = $post_data_form_fields;
888
889 $layout = ! empty( $el_settings['better_payment_form_layout'] ) ? sanitize_text_field( $el_settings['better_payment_form_layout'] ) : 'layout-1';
890
891 // Handle different layouts.
892 switch ( $layout ) {
893 case 'layout-4-pro':
894 $el_settings['better_payment_form_fields'] = isset( $el_settings['better_payment_form_fields_layout_4_5_6'] ) ? $el_settings['better_payment_form_fields_layout_4_5_6'] : array();
895 break;
896
897 case 'layout-5-pro':
898 $el_settings['better_payment_form_fields'] = isset( $el_settings['better_payment_form_fields_layout_4_5_6_desc'] ) ? $el_settings['better_payment_form_fields_layout_4_5_6_desc'] : array();
899 break;
900
901 case 'layout-6-pro':
902 $el_settings['better_payment_form_fields'] = isset( $el_settings['better_payment_form_fields_layout_4_5_6_woo'] ) ? $el_settings['better_payment_form_fields_layout_4_5_6_woo'] : array();
903 break;
904
905 default:
906 break;
907 }
908
909 $form_fields = isset( $el_settings['better_payment_form_fields'] ) ? $el_settings['better_payment_form_fields'] : array();
910
911 if ( ! empty( $form_fields ) && is_array( $form_fields ) ) {
912 foreach ( $form_fields as $form_field ) {
913 $field_type = ! empty( $form_field['better_payment_primary_field_type'] ) ? sanitize_text_field( $form_field['better_payment_primary_field_type'] ) : '';
914 $field_name = ! empty( $form_field['better_payment_field_name_heading'] ) ? sanitize_text_field( $form_field['better_payment_field_name_heading'] ) : '';
915
916 switch ( $field_type ) {
917 case 'primary_first_name':
918 $post_data_primary_first_name = isset( $post_fields['primary_first_name'] ) ? sanitize_text_field( $post_fields['primary_first_name'] ) : '';
919 $better_form_fields['primary_first_name'] = $post_data_primary_first_name;
920 break;
921
922 case 'primary_last_name':
923 $post_data_primary_last_name = isset( $post_fields['primary_last_name'] ) ? sanitize_text_field( $post_fields['primary_last_name'] ) : '';
924 $better_form_fields['primary_last_name'] = $post_data_primary_last_name;
925 break;
926
927 case 'primary_email':
928 $post_data_primary_email = isset( $post_fields['primary_email'] ) ? sanitize_email( $post_fields['primary_email'] ) : '';
929 $better_form_fields['primary_email'] = $post_data_primary_email;
930 break;
931
932 default:
933 if ( isset( $post_fields[ $field_type ] ) ) {
934 $better_form_fields[ $field_type ] = sanitize_text_field( $post_fields[ $field_type ] );
935 }
936 break;
937 }
938 }
939 }
940
941 return $better_form_fields;
942 }
943 }
944