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

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