PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.2.1
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.2.1
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.2.1, at includes/Blocks/BlockActions.php

942 lines 43.0 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 );
308
309 $better_form_fields = array_merge( $better_form_fields, $this->fetch_better_form_fields( $el_settings, $_POST ) );
310
311 if ( ! empty( $better_form_fields['primary_first_name'] ) ) {
312 $request_data['primary_first_name'] = sanitize_text_field( $better_form_fields['primary_first_name'] );
313 }
314
315 if ( ! empty( $better_form_fields['primary_last_name'] ) ) {
316 $request_data['primary_last_name'] = sanitize_text_field( $better_form_fields['primary_last_name'] );
317 }
318
319 if ( ! empty( $better_form_fields['primary_email'] ) ) {
320 $request_data['primary_email'] = sanitize_email( $better_form_fields['primary_email'] );
321 }
322
323 if ( ! empty( $better_form_fields['primary_reference_number'] ) ) {
324 $request_data['invoice'] = sanitize_text_field( $better_form_fields['primary_reference_number'] );
325 }
326
327 $campaign_id = ! empty( $_POST['campaign_id'] ) ? sanitize_text_field( $_POST['campaign_id'] ) : '';
328 // phpcs:enable WordPress.Security.NonceVerification.Missing
329
330 Handler::payment_create(
331 array(
332 'amount' => floatval( $primary_payment_amount ),
333 'order_id' => $order_id,
334 'payment_date' => gmdate( 'Y-m-d H:i:s' ),
335 'source' => 'paypal',
336 'form_fields_info' => maybe_serialize( $better_form_fields ),
337 'currency' => sanitize_text_field( $el_settings_currency ),
338 'referer' => 'gutenberg-block',
339 'campaign_id' => $campaign_id,
340 )
341 );
342
343 $paypal_url = "https://www.$path.com/cgi-bin/webscr?";
344 $paypal_url .= http_build_query( $request_data );
345
346 wp_redirect( esc_url_raw( $paypal_url ) );
347 exit;
348 }
349
350 /**
351 * Handle Stripe token request for Gutenberg blocks.
352 *
353 * @since 1.0.0
354 */
355 public function block_stripe_get_token() {
356 // phpcs:disable WordPress.Security.NonceVerification.Missing
357 // Bail immediately if not a Gutenberg block submission — zero DB overhead for Elementor forms.
358 if ( empty( $_POST['setting_data']['source'] ) || 'gutenberg' !== $_POST['setting_data']['source'] ) {
359 return;
360 }
361 // phpcs:enable WordPress.Security.NonceVerification.Missing
362
363 // Verify nonce.
364 if ( ! check_admin_referer( 'better-payment', 'security' ) ) {
365 wp_send_json_error( esc_html__( 'Nonce verification failed.', 'better-payment' ) );
366 return;
367 }
368
369 // phpcs:disable WordPress.Security.NonceVerification.Missing
370 $page_id = isset( $_POST['setting_data']['page_id'] ) ? intval( $_POST['setting_data']['page_id'] ) : 0;
371 $widget_id = isset( $_POST['setting_data']['widget_id'] ) ? sanitize_text_field( $_POST['setting_data']['widget_id'] ) : '';
372 // phpcs:enable WordPress.Security.NonceVerification.Missing
373
374 if ( empty( $page_id ) || empty( $widget_id ) ) {
375 return; // Let the original handler deal with missing data.
376 }
377
378 // Check if this is a block form by looking for settings.
379 if ( ! $this->is_block_form( $page_id, $widget_id ) ) {
380 return; // Not a block form, let Elementor handler process it.
381 }
382
383 // This is a block form - handle the Stripe payment here.
384 $this->process_stripe_payment( $page_id, $widget_id );
385 }
386
387 /**
388 * Handle Paystack token request for Gutenberg blocks.
389 *
390 * @since 1.0.0
391 */
392 public function block_paystack_get_token() {
393 // phpcs:disable WordPress.Security.NonceVerification.Missing
394 // Bail immediately if not a Gutenberg block submission — zero DB overhead for Elementor forms.
395 if ( empty( $_POST['setting_data']['source'] ) || 'gutenberg' !== $_POST['setting_data']['source'] ) {
396 return;
397 }
398 // phpcs:enable WordPress.Security.NonceVerification.Missing
399
400 // Verify nonce.
401 if ( ! check_admin_referer( 'better-payment', 'security' ) ) {
402 return;
403 }
404
405 // phpcs:disable WordPress.Security.NonceVerification.Missing
406 $page_id = isset( $_POST['setting_data']['page_id'] ) ? intval( $_POST['setting_data']['page_id'] ) : 0;
407 $widget_id = isset( $_POST['setting_data']['widget_id'] ) ? sanitize_text_field( $_POST['setting_data']['widget_id'] ) : '';
408 // phpcs:enable WordPress.Security.NonceVerification.Missing
409
410 if ( empty( $page_id ) || empty( $widget_id ) ) {
411 return; // Let the original handler deal with missing data.
412 }
413
414 // Check if this is a block form by looking for transient settings.
415 if ( ! $this->is_block_form( $page_id, $widget_id ) ) {
416 return; // Not a block form, let Elementor handler process it.
417 }
418
419 // This is a block form - handle the Paystack payment here.
420 $this->process_paystack_payment( $page_id, $widget_id );
421 }
422
423 /**
424 * Process Stripe payment for block forms.
425 *
426 * @param int $page_id The page ID.
427 * @param string $widget_id The widget/block ID.
428 */
429 private function process_stripe_payment( $page_id, $widget_id ) {
430 $el_settings = $this->get_block_settings( $page_id, $widget_id );
431
432 if ( empty( $el_settings ) ) {
433 wp_send_json_error( esc_html__( 'Setting Data is missing', 'better-payment' ) );
434 }
435
436 $better_payment_keys = array(
437 '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'] ),
438 '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'] ),
439 );
440
441 if ( empty( $better_payment_keys['public_key'] ) || empty( $better_payment_keys['secret_key'] ) ) {
442 wp_send_json_error( esc_html__( 'Stripe Key missing', 'better-payment' ) );
443 }
444
445 // phpcs:disable WordPress.Security.NonceVerification.Missing
446 $amount = isset( $_POST['fields']['primary_payment_amount'] ) ? floatval( $_POST['fields']['primary_payment_amount'] ) : 0;
447
448 if ( empty( $_POST['fields']['primary_payment_amount'] ) && ! empty( $_POST['fields']['primary_payment_amount_radio'] ) ) {
449 $amount = floatval( $_POST['fields']['primary_payment_amount_radio'] );
450 }
451
452 $amount_quantity = ! empty( $_POST['fields']['payment_amount_quantity'] ) ? intval( $_POST['fields']['payment_amount_quantity'] ) : '';
453 $amount = ! empty( $amount_quantity ) ? $amount * $amount_quantity : $amount;
454
455 if ( $amount <= 0 ) {
456 wp_send_json_error( esc_html__( 'Invalid payment amount.', 'better-payment' ) );
457 return;
458 }
459
460 $header_info = array(
461 'Authorization' => 'Basic ' . base64_encode( sanitize_text_field( $better_payment_keys['secret_key'] ) . ':' ),
462 'Stripe-Version' => '2019-05-16',
463 );
464
465 $order_id = 'stripe_' . uniqid();
466 $el_settings_currency = $el_settings['better_payment_form_currency'];
467
468 if ( ! empty( $el_settings['better_payment_form_currency_use_woocommerce'] ) && 'yes' === $el_settings['better_payment_form_currency_use_woocommerce'] &&
469 ! empty( $el_settings['better_payment_form_currency_woocommerce'] ) ) {
470 $el_settings_currency = $el_settings['better_payment_form_currency_woocommerce'];
471 }
472 if ( ! empty( $_POST['fields']['campaign_currency'] ) ) {
473 $el_settings_currency = sanitize_text_field( $_POST['fields']['campaign_currency'] );
474 }
475
476 $el_settings_currency_symbol = $this->get_currency_symbol( esc_html( $el_settings_currency ) );
477
478 $redirection_url_success = get_permalink( $page_id );
479 $redirection_url_error = get_permalink( $page_id );
480
481 $redirection_url_success = add_query_arg(
482 array(
483 'better_payment_stripe_status' => 'success',
484 'better_payment_widget_id' => $widget_id,
485 ),
486 $redirection_url_success
487 );
488
489 $redirection_url_error = add_query_arg(
490 array(
491 'better_payment_error_status' => 'error',
492 'better_payment_widget_id' => $widget_id,
493 ),
494 $redirection_url_error
495 );
496
497 // Build form fields.
498 $woo_product_id = ! empty( $el_settings['better_payment_form_woocommerce_product_id'] ) ? intval( $el_settings['better_payment_form_woocommerce_product_id'] ) : 0;
499 $woo_product_ids = ! empty( $el_settings['better_payment_form_woocommerce_product_ids'] ) ? $el_settings['better_payment_form_woocommerce_product_ids'] : array( 0 );
500 $fluentcart_product_id = ! empty( $el_settings['better_payment_form_fluentcart_product_id'] ) ? intval( $el_settings['better_payment_form_fluentcart_product_id'] ) : 0;
501 $fluentcart_product_ids = ! empty( $el_settings['better_payment_form_fluentcart_product_ids'] ) ? $el_settings['better_payment_form_fluentcart_product_ids'] : array( 0 );
502
503 $product_ids = array(
504 'woo_product_ids' => $woo_product_ids,
505 'fluentcart_product_ids' => $fluentcart_product_ids,
506 );
507
508 $detailed_product_info = $this->get_detailed_product_info( $product_ids );
509
510 $better_form_fields = array(
511 'amount' => sanitize_text_field( $el_settings_currency_symbol ) . $amount,
512 'referer_page_id' => $page_id,
513 'referer_widget_id' => $widget_id,
514 'woo_product_id' => $woo_product_id,
515 'woo_product_ids' => maybe_serialize( $woo_product_ids ),
516 'fluentcart_product_id' => $fluentcart_product_id,
517 'fluentcart_product_ids' => maybe_serialize( $fluentcart_product_ids ),
518 'source' => 'stripe',
519 'amount_quantity' => ! empty( $amount_quantity ) ? intval( $amount_quantity ) : '',
520 'detailed_product_info' => maybe_serialize( $detailed_product_info ),
521 );
522
523 $better_form_fields = array_merge( $better_form_fields, $this->fetch_better_form_fields( $el_settings, $_POST['fields'] ) );
524
525 $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' );
526
527 // The Stripe Price ID (when Payment Source = Stripe) is display-only —
528 // it pre-fills the amount input on the frontend (see layout-1/2/3.php line input value).
529 // The payment session always uses price_data with the submitted amount,
530 // matching the Elementor widget behaviour for layouts 1/2/3.
531 $line_item = array(
532 'price_data' => array(
533 'currency' => sanitize_text_field( $el_settings_currency ),
534 'unit_amount' => intval( $amount * 100 ),
535 'product_data' => array(
536 'name' => $item_name,
537 ),
538 ),
539 'quantity' => 1,
540 );
541
542 $request_body = array(
543 'line_items' => array( $line_item ),
544 'mode' => 'payment',
545 'locale' => 'auto',
546 'payment_method_types' => array( 'card' ),
547 'billing_address_collection' => 'required',
548 'client_reference_id' => time(),
549 'metadata' => array(
550 'order_id' => $order_id,
551 ),
552 'success_url' => esc_url_raw( $redirection_url_success ) . '&better_payment_stripe_id=' . $order_id,
553 'cancel_url' => add_query_arg(
554 array(
555 'better_payment_stripe_id' => $order_id,
556 ),
557 esc_url_raw( $redirection_url_error )
558 ),
559 );
560
561 $request_body['payment_intent_data'] = array(
562 'capture_method' => 'automatic',
563 'description' => $item_name,
564 'metadata' => array(
565 'order_id' => $order_id,
566 ),
567 );
568
569 $primary_email = ! empty( $better_form_fields['primary_email'] ) ? sanitize_email( $better_form_fields['primary_email'] ) : '';
570
571 if ( ! empty( $primary_email ) ) {
572 $request_body['customer_email'] = $primary_email;
573 $request_body['metadata']['customer_email'] = $primary_email;
574 $request_body['payment_intent_data']['metadata']['customer_email'] = $primary_email;
575 }
576
577 // Build customer name from first/last name fields (mirrors widget behaviour).
578 $customer_name = '';
579 if ( ! empty( $better_form_fields['primary_first_name'] ) ) {
580 $customer_name = sanitize_text_field( $better_form_fields['primary_first_name'] );
581 }
582 if ( ! empty( $better_form_fields['primary_last_name'] ) ) {
583 $customer_name = trim( $customer_name . ' ' . sanitize_text_field( $better_form_fields['primary_last_name'] ) );
584 }
585
586 if ( ! empty( $customer_name ) ) {
587 $request_body['metadata']['customer_name'] = $customer_name;
588 $request_body['payment_intent_data']['metadata']['customer_name'] = $customer_name;
589 }
590
591 $request = wp_remote_post(
592 'https://api.stripe.com/v1/checkout/sessions',
593 array(
594 'headers' => $header_info,
595 'body' => $request_body,
596 )
597 );
598
599 if ( is_wp_error( $request ) ) {
600 wp_send_json_error( sanitize_text_field( $request->get_error_message() ) );
601 return;
602 }
603
604 $response_ar = json_decode( wp_remote_retrieve_body( $request ) );
605
606 if ( null === $response_ar ) {
607 wp_send_json_error( esc_html__( 'Invalid response from Stripe.', 'better-payment' ) );
608 return;
609 }
610
611 if ( ! empty( $response_ar->payment_intent ) || ( ! empty( $response_ar->mode ) && 'subscription' === $response_ar->mode ) ) {
612 $campaign_id = ! empty( $_POST['fields']['campaign_id'] ) ? sanitize_text_field( $_POST['fields']['campaign_id'] ) : '';
613
614 Handler::payment_create(
615 array(
616 'amount' => floatval( $amount ),
617 'order_id' => $order_id,
618 'payment_date' => gmdate( 'Y-m-d H:i:s' ),
619 'source' => 'stripe',
620 'transaction_id' => sanitize_text_field( $response_ar->payment_intent ),
621 'customer_info' => maybe_serialize( $response_ar ),
622 'form_fields_info' => maybe_serialize( $better_form_fields ),
623 'obj_id' => sanitize_text_field( $response_ar->id ),
624 'status' => sanitize_text_field( $response_ar->payment_status ),
625 'currency' => sanitize_text_field( $el_settings_currency ),
626 'referer' => 'gutenberg-block',
627 'campaign_id' => $campaign_id,
628 )
629 );
630
631 wp_send_json_success(
632 array(
633 'stripe_data' => sanitize_text_field( $response_ar->id ),
634 'stripe_public_key' => sanitize_text_field( $better_payment_keys['public_key'] ),
635 )
636 );
637 } else {
638 $error_message = 'Something went wrong!';
639
640 if ( isset( $response_ar->error ) ) {
641 $error_message = sanitize_text_field( $response_ar->error->message );
642 }
643
644 wp_send_json_error( $error_message );
645 }
646 // phpcs:enable WordPress.Security.NonceVerification.Missing
647 }
648
649 /**
650 * Process Paystack payment for block forms.
651 *
652 * @param int $page_id The page ID.
653 * @param string $widget_id The widget/block ID.
654 */
655 private function process_paystack_payment( $page_id, $widget_id ) {
656 $el_settings = $this->get_block_settings( $page_id, $widget_id );
657
658 if ( empty( $el_settings ) ) {
659 wp_send_json_error( esc_html__( 'Setting Data is missing', 'better-payment' ) );
660 }
661
662 if ( empty( $el_settings['better_payment_paystack_public_key'] ) || empty( $el_settings['better_payment_paystack_secret_key'] ) ) {
663 wp_send_json_error( esc_html__( 'Paystack Key missing', 'better-payment' ) );
664 }
665
666 // phpcs:disable WordPress.Security.NonceVerification.Missing
667 $amount = isset( $_POST['fields']['primary_payment_amount'] ) ? floatval( $_POST['fields']['primary_payment_amount'] ) : 0;
668
669 if ( empty( $_POST['fields']['primary_payment_amount'] ) && ! empty( $_POST['fields']['primary_payment_amount_radio'] ) ) {
670 $amount = floatval( $_POST['fields']['primary_payment_amount_radio'] );
671 }
672
673 $amount_quantity = ! empty( $_POST['fields']['payment_amount_quantity'] ) ? intval( $_POST['fields']['payment_amount_quantity'] ) : '';
674 $amount = ! empty( $amount_quantity ) ? $amount * $amount_quantity : $amount;
675
676 if ( $amount <= 0 ) {
677 wp_send_json_error( esc_html__( 'Invalid payment amount.', 'better-payment' ) );
678 return;
679 }
680
681 $header_info = array(
682 'Authorization' => 'Bearer ' . sanitize_text_field( $el_settings['better_payment_paystack_secret_key'] ),
683 'Cache-Control: no-cache',
684 );
685
686 $order_id = 'paystack_' . uniqid();
687 $el_settings_currency = $el_settings['better_payment_form_currency'];
688
689 if ( ! empty( $el_settings['better_payment_form_currency_use_woocommerce'] ) && 'yes' === $el_settings['better_payment_form_currency_use_woocommerce'] &&
690 ! empty( $el_settings['better_payment_form_currency_woocommerce'] ) ) {
691 $el_settings_currency = $el_settings['better_payment_form_currency_woocommerce'];
692 }
693 if ( ! empty( $_POST['fields']['campaign_currency'] ) ) {
694 $el_settings_currency = sanitize_text_field( $_POST['fields']['campaign_currency'] );
695 }
696
697 $el_settings_currency_symbol = $this->get_currency_symbol( esc_html( $el_settings_currency ) );
698
699 $redirection_url_success = get_permalink( $page_id );
700 $redirection_url_error = get_permalink( $page_id );
701
702 $redirection_url_success = add_query_arg(
703 array(
704 'better_payment_paystack_status' => 'success',
705 'better_payment_widget_id' => $widget_id,
706 ),
707 $redirection_url_success
708 );
709
710 $redirection_url_error = add_query_arg(
711 array(
712 'better_payment_error_status' => 'error',
713 'better_payment_widget_id' => $widget_id,
714 ),
715 $redirection_url_error
716 );
717
718 // Build form fields.
719 $woo_product_id = ! empty( $el_settings['better_payment_form_woocommerce_product_id'] ) ? intval( $el_settings['better_payment_form_woocommerce_product_id'] ) : 0;
720 $woo_product_ids = ! empty( $el_settings['better_payment_form_woocommerce_product_ids'] ) ? $el_settings['better_payment_form_woocommerce_product_ids'] : array( 0 );
721 $fluentcart_product_id = ! empty( $el_settings['better_payment_form_fluentcart_product_id'] ) ? intval( $el_settings['better_payment_form_fluentcart_product_id'] ) : 0;
722 $fluentcart_product_ids = ! empty( $el_settings['better_payment_form_fluentcart_product_ids'] ) ? $el_settings['better_payment_form_fluentcart_product_ids'] : array( 0 );
723
724 $product_ids = array(
725 'woo_product_ids' => $woo_product_ids,
726 'fluentcart_product_ids' => $fluentcart_product_ids,
727 );
728
729 $detailed_product_info = $this->get_detailed_product_info( $product_ids );
730
731 $better_form_fields = array(
732 'amount' => sanitize_text_field( $el_settings_currency_symbol ) . $amount,
733 'referer_page_id' => $page_id,
734 'referer_widget_id' => $widget_id,
735 'woo_product_id' => $woo_product_id,
736 'woo_product_ids' => maybe_serialize( $woo_product_ids ),
737 'fluentcart_product_id' => $fluentcart_product_id,
738 'fluentcart_product_ids' => maybe_serialize( $fluentcart_product_ids ),
739 'source' => 'paystack',
740 'amount_quantity' => ! empty( $amount_quantity ) ? intval( $amount_quantity ) : '',
741 'detailed_product_info' => maybe_serialize( $detailed_product_info ),
742 );
743
744 $better_form_fields = array_merge( $better_form_fields, $this->fetch_better_form_fields( $el_settings, $_POST['fields'] ) );
745
746 $primary_email = ! empty( $better_form_fields['primary_email'] ) ? sanitize_email( $better_form_fields['primary_email'] ) : '';
747
748 $request_body = array(
749 'amount' => intval( $amount * 100 ),
750 'currency' => sanitize_text_field( $el_settings_currency ),
751 'email' => $primary_email,
752 'callback_url' => esc_url_raw( $redirection_url_success ) . '&better_payment_paystack_id=' . $order_id,
753 'metadata' => array(
754 'cancel_action' => add_query_arg(
755 array(
756 'better_payment_paystack_id' => $order_id,
757 ),
758 esc_url_raw( $redirection_url_error )
759 ),
760 ),
761 );
762
763 $request = wp_remote_post(
764 'https://api.paystack.co/transaction/initialize',
765 array(
766 'headers' => $header_info,
767 'body' => $request_body,
768 )
769 );
770
771 if ( is_wp_error( $request ) ) {
772 wp_send_json_error( sanitize_text_field( $request->get_error_message() ) );
773 return;
774 }
775
776 $response_ar = json_decode( wp_remote_retrieve_body( $request ) );
777
778 if ( null === $response_ar ) {
779 wp_send_json_error( esc_html__( 'Invalid response from Paystack.', 'better-payment' ) );
780 return;
781 }
782
783 if ( empty( $response_ar->status ) || empty( $response_ar->data ) ) {
784 $error_message = ! empty( $response_ar->message ) ? sanitize_text_field( $response_ar->message ) : 'Something went wrong!';
785
786 if ( isset( $response_ar->error ) ) {
787 $error_message = sanitize_text_field( $response_ar->error->message );
788 }
789
790 wp_send_json_error( $error_message );
791 }
792
793 $campaign_id = ! empty( $_POST['fields']['campaign_id'] ) ? sanitize_text_field( $_POST['fields']['campaign_id'] ) : '';
794
795 Handler::payment_create(
796 array(
797 'amount' => floatval( $amount ),
798 'order_id' => $order_id,
799 'payment_date' => gmdate( 'Y-m-d H:i:s' ),
800 'source' => 'paystack',
801 'transaction_id' => '',
802 'customer_info' => maybe_serialize( $response_ar ),
803 'form_fields_info' => maybe_serialize( $better_form_fields ),
804 'status' => 'unpaid',
805 'currency' => sanitize_text_field( $el_settings_currency ),
806 'referer' => 'gutenberg-block',
807 'campaign_id' => $campaign_id,
808 )
809 );
810
811 $authorization_url = ! empty( $response_ar->data->authorization_url ) ? esc_url_raw( $response_ar->data->authorization_url ) : '';
812
813 wp_send_json_success(
814 array(
815 'authorization_url' => $authorization_url,
816 )
817 );
818 // phpcs:enable WordPress.Security.NonceVerification.Missing
819 }
820
821 /**
822 * Redirect to referer page.
823 *
824 * @since 1.0.0
825 */
826 public function redirect_previous_page() {
827 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
828 $location = isset( $_SERVER['HTTP_REFERER'] ) ? wp_unslash( $_SERVER['HTTP_REFERER'] ) : home_url();
829 wp_safe_redirect( esc_url_raw( $location ) );
830 exit();
831 }
832
833 /**
834 * Get detailed product information.
835 *
836 * @param array $product_ids Array of product IDs.
837 * @param array $product_quantities Array of product quantities.
838 * @return array Detailed product information.
839 */
840 public function get_detailed_product_info( $product_ids, $product_quantities = array() ) {
841 $detailed_product_info = array();
842
843 if ( function_exists( 'wc_get_product' ) && ! empty( $product_ids['woo_product_ids'] ) ) {
844 foreach ( $product_ids['woo_product_ids'] as $key => $product_id ) {
845 if ( empty( $product_id ) ) {
846 continue;
847 }
848
849 $product = wc_get_product( $product_id );
850 if ( $product ) {
851 $quantity = isset( $product_quantities[ $key ] ) ? intval( $product_quantities[ $key ] ) : 1;
852 $price = floatval( $product->get_price() );
853 $total_price = $price * $quantity;
854
855 $detailed_product_info['woo_products'][ $product_id ] = array(
856 'name' => sanitize_text_field( $product->get_name() ),
857 'product_id' => intval( $product_id ),
858 'permalink' => esc_url( $product->get_permalink() ),
859 'image_src' => esc_url( wp_get_attachment_url( $product->get_image_id() ) ),
860 'price' => $price,
861 'quantity' => $quantity,
862 'total_price' => $total_price,
863 );
864 }
865 }
866 }
867
868 return $detailed_product_info;
869 }
870
871 /**
872 * Fetch form fields from POST data.
873 *
874 * @param array $el_settings Widget/block settings.
875 * @param array $post_data_form_fields POST data.
876 * @return array Form fields data.
877 */
878 public function fetch_better_form_fields( $el_settings, $post_data_form_fields ) {
879 $better_form_fields = array();
880
881 $post_data_primary_first_name = '';
882 $post_data_primary_last_name = '';
883 $post_data_primary_email = '';
884
885 $post_fields = $post_data_form_fields;
886
887 $layout = ! empty( $el_settings['better_payment_form_layout'] ) ? sanitize_text_field( $el_settings['better_payment_form_layout'] ) : 'layout-1';
888
889 // Handle different layouts.
890 switch ( $layout ) {
891 case 'layout-4-pro':
892 $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();
893 break;
894
895 case 'layout-5-pro':
896 $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();
897 break;
898
899 case 'layout-6-pro':
900 $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();
901 break;
902
903 default:
904 break;
905 }
906
907 $form_fields = isset( $el_settings['better_payment_form_fields'] ) ? $el_settings['better_payment_form_fields'] : array();
908
909 if ( ! empty( $form_fields ) && is_array( $form_fields ) ) {
910 foreach ( $form_fields as $form_field ) {
911 $field_type = ! empty( $form_field['better_payment_primary_field_type'] ) ? sanitize_text_field( $form_field['better_payment_primary_field_type'] ) : '';
912 $field_name = ! empty( $form_field['better_payment_field_name_heading'] ) ? sanitize_text_field( $form_field['better_payment_field_name_heading'] ) : '';
913
914 switch ( $field_type ) {
915 case 'primary_first_name':
916 $post_data_primary_first_name = isset( $post_fields['primary_first_name'] ) ? sanitize_text_field( $post_fields['primary_first_name'] ) : '';
917 $better_form_fields['primary_first_name'] = $post_data_primary_first_name;
918 break;
919
920 case 'primary_last_name':
921 $post_data_primary_last_name = isset( $post_fields['primary_last_name'] ) ? sanitize_text_field( $post_fields['primary_last_name'] ) : '';
922 $better_form_fields['primary_last_name'] = $post_data_primary_last_name;
923 break;
924
925 case 'primary_email':
926 $post_data_primary_email = isset( $post_fields['primary_email'] ) ? sanitize_email( $post_fields['primary_email'] ) : '';
927 $better_form_fields['primary_email'] = $post_data_primary_email;
928 break;
929
930 default:
931 if ( isset( $post_fields[ $field_type ] ) ) {
932 $better_form_fields[ $field_type ] = sanitize_text_field( $post_fields[ $field_type ] );
933 }
934 break;
935 }
936 }
937 }
938
939 return $better_form_fields;
940 }
941 }
942