PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.3.4
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.3.4
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 / Classes / Handler.php

Handler.php in Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More 2.3.4, at includes/Classes/Handler.php

2,044 lines 116.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Better_Payment\Lite\Classes;
4
5 use Better_Payment\Lite\Admin\DB;
6 use Better_Payment\Lite\Controller;
7 use Better_Payment\Lite\Classes\Helper;
8 use Better_Payment\Lite\Traits\Helper as TraitsHelper;
9
10 /**
11 * Exit if accessed directly
12 */
13 if (!defined('ABSPATH')) {
14 exit;
15 }
16
17 /**
18 * The plugin handler class
19 *
20 * @since 0.0.1
21 */
22 class Handler extends Controller{
23 use TraitsHelper;
24
25 /**
26 * PayPal button
27 *
28 * @since 0.0.1
29 */
30 public static function paypal_button( $widget_id = '' , $settings = [], $args = [] ) {
31 global $wp;
32
33 $args['extra_classes'] = ! empty( $args['extra_classes'] ) ? $args['extra_classes'] : '';
34 $args['extra_classes'] = is_array($args['extra_classes']) ? implode(' ', $args['extra_classes']) : $args['extra_classes'];
35 $args['button_text_default'] = ! empty( $args['button_text_default'] ) ? sanitize_text_field( $args['button_text_default'] ) : 'Proceed to Payment';
36
37 $paypal_button_text = ! empty( $settings["better_payment_form_form_buttons_paypal_button_text"] ) ? sanitize_text_field( $settings["better_payment_form_form_buttons_paypal_button_text"] ) : __( $args['button_text_default'], 'better-payment' );
38
39 $return_url = add_query_arg( $wp->query_vars, get_the_permalink() . '?better_payment_paypal_status=success&better_payment_widget_id=' . $widget_id );
40 $cancel_url = add_query_arg( $wp->query_vars, get_the_permalink() . '?better_payment_error_status=error&better_payment_widget_id=' . $widget_id );
41
42 ob_start();
43 $error_info = [
44 'business_email' => !empty($settings['better_payment_paypal_business_email'])
45 ];
46 ?>
47 <input type="hidden" name="return" value="<?php echo esc_url( $return_url ); ?>">
48 <input type="hidden" name="action" value="paypal_form_handle">
49 <input type="hidden" name="security" value="<?php echo esc_attr( wp_create_nonce('better-payment-paypal') ); ?>">
50 <input type="hidden" name="cancel_return" value="<?php echo esc_url( $cancel_url ); ?>">
51 <button data-paypal-info = <?php echo esc_attr(wp_json_encode( $error_info) ) ; ?> class="button is-medium is-fullwidth better-payment-paypal-bt <?php echo esc_attr( self::hide_show_payment_button_class($settings, 'paypal') ); ?> <?php echo esc_attr( $args['extra_classes'] ); ?>" ><?php echo esc_html( $paypal_button_text ); ?></button>
52
53 <?php
54 $paypal_button_html = ob_get_clean();
55 $paypal_button_html = apply_filters( 'better_payment/elementor/editor/paypal_button_html', $paypal_button_html, $widget_id, $settings );
56
57 echo $paypal_button_html;
58 }
59
60 /**
61 * Stripe button
62 *
63 * @since 0.0.1
64 */
65 public static function stripe_button( $widget_id = '' , $settings = [], $args = [] ) {
66 $args['extra_classes'] = ! empty( $args['extra_classes'] ) ? $args['extra_classes'] : '';
67 $args['extra_classes'] = is_array($args['extra_classes']) ? implode(' ', $args['extra_classes']) : $args['extra_classes'];
68 $args['button_text_default'] = ! empty( $args['button_text_default'] ) ? sanitize_text_field( $args['button_text_default'] ) : 'Proceed to Payment';
69
70 $stripe_button_text = ! empty( $settings["better_payment_form_form_buttons_stripe_button_text"] ) ? sanitize_text_field( $settings["better_payment_form_form_buttons_stripe_button_text"] ) : __( $args['button_text_default'], 'better-payment' );
71
72 ob_start();
73 ?>
74 <button class="button is-medium is-fullwidth better-payment-stripe-bt <?php echo esc_attr( self::hide_show_payment_button_class($settings, 'stripe') ); ?> <?php echo esc_attr( $args['extra_classes'] ); ?>" ><?php echo esc_html( $stripe_button_text ); ?></button>
75 <?php
76 $stripe_button_html = ob_get_clean();
77 $stripe_button_html = apply_filters( 'better_payment/elementor/editor/stripe_button_html', $stripe_button_html, $widget_id, $settings );
78
79 echo wp_kses( $stripe_button_html, (new Handler())->bp_allowed_tags() );
80 }
81
82 /**
83 * Paystack button
84 *
85 * @since 0.0.1
86 */
87 public static function paystack_button( $widget_id = '' , $settings = [], $args = [] ) {
88 $args['extra_classes'] = ! empty( $args['extra_classes'] ) ? $args['extra_classes'] : '';
89 $args['extra_classes'] = is_array($args['extra_classes']) ? implode(' ', $args['extra_classes']) : $args['extra_classes'];
90 $args['button_text_default'] = ! empty( $args['button_text_default'] ) ? sanitize_text_field( $args['button_text_default'] ) : 'Proceed to Payment';
91
92 $paystack_button_text = ! empty( $settings["better_payment_form_form_buttons_paystack_button_text"] ) ? sanitize_text_field( $settings["better_payment_form_form_buttons_paystack_button_text"] ) : __( $args['button_text_default'], 'better-payment' );
93
94 ob_start();
95 ?>
96 <button class="button is-medium is-fullwidth better-payment-paystack-bt <?php echo esc_attr( self::hide_show_payment_button_class($settings, 'paystack') ); ?> <?php echo esc_attr( $args['extra_classes'] ); ?>" ><?php echo esc_html( $paystack_button_text ); ?></button>
97 <?php
98 $paystack_button_html = ob_get_clean();
99 $paystack_button_html = apply_filters( 'better_payment/elementor/editor/paystack_button_html', $paystack_button_html, $widget_id, $settings );
100
101 echo wp_kses( $paystack_button_html, (new Handler())->bp_allowed_tags() );
102 }
103
104 public static function hide_show_payment_button_class( $settings, $type = 'paypal' ){
105 $button_hidden_class = 'is-hidden';
106 $types = ['paypal', 'stripe', 'paystack'];
107
108 if( ! in_array($type, $types) ){
109 return $button_hidden_class;
110 }
111
112 $is_type_enable = ! empty( $settings[ "better_payment_form_{$type}_enable" ] ) && 'yes' === $settings[ "better_payment_form_{$type}_enable" ];
113
114 if( ! $is_type_enable ){
115 return $button_hidden_class;
116 }
117
118 $is_paypal_enable = ! empty( $settings[ "better_payment_form_paypal_enable" ] ) && 'yes' === $settings[ "better_payment_form_paypal_enable" ];
119 $is_stripe_enable = ! empty( $settings[ "better_payment_form_stripe_enable" ] ) && 'yes' === $settings[ "better_payment_form_stripe_enable" ];
120 $is_paystack_enable = ! empty( $settings[ "better_payment_form_paystack_enable" ] ) && 'yes' === $settings[ "better_payment_form_paystack_enable" ];
121
122 switch( $type ){
123 case 'paypal':
124 $button_hidden_class = '';
125 break;
126
127 case 'stripe':
128 if( ( ! $is_paypal_enable ) ){
129 $button_hidden_class = '';
130 }
131
132 break;
133
134 case 'paystack':
135 if( ( ! $is_paypal_enable ) && ( ! $is_stripe_enable ) ){
136 $button_hidden_class = '';
137 }
138 break;
139
140 default:
141 break;
142 }
143
144 return $button_hidden_class;
145 }
146
147 /**
148 * Payment create in db
149 *
150 * @since 0.0.1
151 */
152 public static function payment_create( $data ) {
153 global $wpdb;
154 $table = "{$wpdb->prefix}better_payment";
155 $wpdb->insert( $table, $data );
156 if ( $wpdb->insert_id ) {
157 return $wpdb->insert_id;
158 } else {
159 return false;
160 }
161 }
162
163 /**
164 * A new, unguessable order id: `{gateway}_{24 hex chars}`.
165 *
166 * The order id is the only key the public return pages and the PayPal status poll look
167 * an order up by. uniqid() is the current time in microseconds, so ids created around a
168 * known moment could be enumerated to read other visitors' order status and receipts.
169 * 96 random bits cannot be. Fits the 50-char `order_id` column with room to spare.
170 *
171 * @since 2.3.4
172 *
173 * @param string $gateway 'paypal' | 'stripe' | 'paystack'.
174 * @return string
175 */
176 public static function new_order_id( $gateway ) {
177 try {
178 $random = bin2hex( random_bytes( 12 ) );
179 } catch ( \Exception $e ) {
180 $random = strtolower( wp_generate_password( 24, false ) );
181 }
182
183 return sanitize_key( $gateway ) . '_' . $random;
184 }
185
186 /**
187 * Mamage response
188 *
189 * @since 0.0.1
190 */
191 public static function manage_response( $settings = [], $widget_id = '' ) {
192 $status = false;
193
194 //if widget id is different then return false
195 if ( !empty( $_REQUEST[ 'better_payment_widget_id' ] ) && $_REQUEST[ 'better_payment_widget_id' ] !== $widget_id ) {
196 return $status;
197 }
198
199 if ( !empty( $_REQUEST[ 'better_payment_error_status' ] ) ) {
200 // Determine order_id based on payment method
201 $order_id = '';
202 if ( !empty( $_REQUEST['better_payment_stripe_id'] ) ) {
203 $order_id = sanitize_text_field( $_REQUEST['better_payment_stripe_id'] );
204 } elseif ( !empty( $_REQUEST['better_payment_paystack_id'] ) ) {
205 $order_id = sanitize_text_field( $_REQUEST['better_payment_paystack_id'] );
206 } elseif ( !empty( $_REQUEST['better_payment_paypal_id'] ) ) {
207 $order_id = sanitize_text_field( $_REQUEST['better_payment_paypal_id'] );
208 }
209
210 // Fetch transaction data from database
211 $transaction_data = self::get_failed_transaction_data( $order_id );
212
213 self::failed_message_template( $settings, $transaction_data );
214 $redirection_url_error = ! empty( $settings['better_payment_form_error_page_url']['url'] ) ? esc_url( $settings['better_payment_form_error_page_url']['url'] ) : '';
215
216 if( $redirection_url_error ){
217 ?>
218 <script>
219 setTimeout(function(){
220 window.location.replace("<?php echo esc_url( $redirection_url_error ); ?>");
221 }, 2000);
222 </script>
223 <?php
224 }
225
226 return $status;
227 }
228
229 if ( !empty( $_REQUEST[ 'better_payment_paypal_status' ] ) && $_REQUEST[ 'better_payment_paypal_status' ] == 'success' ) {
230 $status = self::paypal_payment_success( $settings );
231 } elseif ( !empty( $_REQUEST[ 'better_payment_stripe_status' ] ) && $_REQUEST[ 'better_payment_stripe_status' ] == 'success' ) {
232 $status = self::stripe_payment_success( $settings );
233 } elseif ( !empty( $_REQUEST[ 'better_payment_paystack_status' ] ) && $_REQUEST[ 'better_payment_paystack_status' ] == 'success' ) {
234 $status = self::paystack_payment_success( $settings );
235 }
236
237 if ( ! empty( $status['pending'] ) ) {
238 // Use home_url() instead of HTTP_HOST to avoid open-redirect via forged Host headers.
239 $return_url = home_url( $_SERVER['REQUEST_URI'] );
240 // PayPal uses rm=2 (POST redirect), so item_number arrives in $_POST and is absent from the URL.
241 // Add it explicitly so the poll-complete redirect (a plain GET) still carries item_number.
242 if ( ! empty( $status['order_id'] ) && strpos( $return_url, 'item_number=' ) === false ) {
243 $return_url = add_query_arg( 'item_number', $status['order_id'], $return_url );
244 }
245 ?>
246 <section class="bp-thank_page">
247 <div class="bp-thank_page-wrapper bp-paypal-pending"
248 data-order-id="<?php echo esc_attr( $status['order_id'] ); ?>"
249 data-return-url="<?php echo esc_url( $return_url ); ?>">
250
251 <div class="bp-thank_page-logo">
252 <span class="bp-paypal-pending__spinner-wrap">
253 <span class="bp-paypal-pending__ring"></span>
254 <img class="bp-paypal-pending__logo"
255 src="<?php echo esc_url( BETTER_PAYMENT_ASSETS . '/img/paypal-2.svg' ); ?>"
256 alt="PayPal">
257 </span>
258 </div>
259
260 <div class="bp-thank_page-text bp-paypal-pending__body">
261 <h2 class="bp-font_ibm bp-page_header bp-paypal-pending__heading">
262 <?php esc_html_e( 'Verifying your payment…', 'better-payment' ); ?>
263 </h2>
264 <p class="bp-font_ibm bp-payment_info bp-paypal-pending__sub">
265 <?php esc_html_e( 'Please do not close this page. This usually takes a few seconds.', 'better-payment' ); ?>
266 </p>
267 <div class="bp-paypal-pending__dots">
268 <span></span><span></span><span></span>
269 </div>
270 </div>
271
272 <div class="bp-paypal-pending__bar">
273 <div class="bp-paypal-pending__bar-inner"></div>
274 </div>
275
276 </div>
277 </section>
278 <?php
279 } elseif ( $status ) {
280 self::success_message_template( $settings, $status );
281 $redirection_url_success = ! empty( $settings['better_payment_form_success_page_url']['url'] ) ? esc_url( $settings['better_payment_form_success_page_url']['url'] ) : '';
282
283 if( $redirection_url_success ){
284 ?>
285 <script>
286 setTimeout(function(){
287 window.location.replace("<?php echo esc_url( $redirection_url_success ); ?>");
288 }, 2000);
289 </script>
290 <?php
291 }
292 }
293 self::remove_arg();
294
295 return $status;
296 }
297
298 /**
299 * Paypal payment success
300 *
301 * The DB write happens exclusively in handle_paypal_ipn() after PayPal verifies
302 * the IPN post-back. This method is read-only: it never writes to the DB.
303 *
304 * Display data priority:
305 * 1. DB row (IPN already fired and confirmed) — authoritative.
306 * 2. PayPal return-URL params ($_REQUEST) — for display only when IPN hasn't
307 * fired yet (local dev, race condition). Safe because no DB write occurs here.
308 *
309 * @since 0.0.1
310 */
311 public static function paypal_payment_success( $settings = [] ) {
312 $item_number = ! empty( $_REQUEST['item_number'] ) ? sanitize_text_field( $_REQUEST['item_number'] ) : '';
313
314 if ( empty( $item_number ) ) {
315 return false;
316 }
317
318 global $wpdb;
319 $table = "{$wpdb->prefix}better_payment";
320 $row = $wpdb->get_row(
321 $wpdb->prepare(
322 "SELECT transaction_id, status, email, amount, currency FROM $table WHERE order_id = %s LIMIT 1",
323 $item_number
324 )
325 );
326
327 // No matching order — reject (prevents fake success display with forged item_number).
328 if ( empty( $row ) ) {
329 return false;
330 }
331
332 // IPN confirmed — return verified DB data.
333 if ( $row->status === 'Completed' ) {
334 return [
335 'amount' => floatval( $row->amount ),
336 'email' => sanitize_email( $row->email ),
337 'transaction_id' => sanitize_text_field( $row->transaction_id ),
338 'currency' => sanitize_text_field( $row->currency ),
339 'method' => 'paypal',
340 ];
341 }
342
343 // Order exists but IPN hasn't arrived yet — signal pending so JS can poll.
344 return [ 'pending' => true, 'order_id' => $item_number ];
345 }
346
347 /**
348 * AJAX: check whether a PayPal order has been confirmed by IPN.
349 *
350 * Called by the frontend polling loop after the user lands on the return URL
351 * before the IPN fires. Returns {status:'pending'} or {status:'completed'}.
352 *
353 * Lives on Handler (not Actions) so it can be registered unconditionally —
354 * PayPal payments flow through the block/campaign path without Elementor.
355 */
356 public static function check_paypal_status() {
357 check_ajax_referer( 'better-payment', 'security' );
358
359 $order_id = ! empty( $_POST['order_id'] ) ? sanitize_text_field( $_POST['order_id'] ) : '';
360
361 if ( empty( $order_id ) ) {
362 wp_send_json_error( [ 'message' => 'Missing order_id' ] );
363 }
364
365 global $wpdb;
366 $table = "{$wpdb->prefix}better_payment";
367 $row = $wpdb->get_row(
368 $wpdb->prepare(
369 "SELECT status FROM $table WHERE order_id = %s LIMIT 1",
370 $order_id
371 )
372 );
373
374 if ( empty( $row ) ) {
375 wp_send_json_error( [ 'message' => 'Order not found' ] );
376 }
377
378 if ( $row->status === 'Completed' ) {
379 wp_send_json_success( [ 'status' => 'completed' ] );
380 }
381
382 wp_send_json_success( [ 'status' => 'pending' ] );
383 }
384
385 /**
386 * PayPal IPN listener.
387 *
388 * PayPal POSTs the raw IPN body here server-to-server. We echo it back with
389 * cmd=_notify-validate; only a VERIFIED response from PayPal's servers triggers
390 * the DB write. The browser return URL (paypal_payment_success) never writes.
391 *
392 * @since 2.1.3
393 */
394 public static function handle_paypal_ipn() {
395 $raw_post = file_get_contents( 'php://input' );
396
397 if ( empty( $raw_post ) ) {
398 status_header( 400 );
399 exit;
400 }
401
402 $data = [];
403 wp_parse_str( $raw_post, $data );
404
405 // PayPal includes test_ipn=1 for sandbox notifications.
406 $verify_url = ! empty( $data['test_ipn'] )
407 ? 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr'
408 : 'https://ipnpb.paypal.com/cgi-bin/webscr';
409
410 $response = wp_remote_post(
411 $verify_url,
412 [
413 'body' => 'cmd=_notify-validate&' . $raw_post,
414 'timeout' => 30,
415 'sslverify' => true,
416 'headers' => [ 'Content-Type' => 'application/x-www-form-urlencoded' ],
417 ]
418 );
419
420 // Always respond 200 to PayPal to prevent retries, but only act on VERIFIED.
421 if ( is_wp_error( $response ) || wp_remote_retrieve_body( $response ) !== 'VERIFIED' ) {
422 status_header( 200 );
423 exit;
424 }
425
426 $data = array_map( 'sanitize_text_field', $data );
427
428 if ( empty( $data['payment_status'] ) || empty( $data['item_number'] ) || $data['payment_status'] !== 'Completed' ) {
429 status_header( 200 );
430 exit;
431 }
432
433 global $wpdb;
434 $table = "{$wpdb->prefix}better_payment";
435 $results = $wpdb->get_row(
436 $wpdb->prepare(
437 "SELECT id, amount, currency, form_fields_info, referer FROM $table WHERE order_id = %s AND status IS NULL LIMIT 1",
438 $data['item_number']
439 )
440 );
441
442 if ( empty( $results->id ) ) {
443 status_header( 200 );
444 exit;
445 }
446
447 // Validate receiver_email matches the merchant's configured PayPal email.
448 $form_info = maybe_unserialize( $results->form_fields_info );
449 $expected_email = ! empty( $form_info['paypal_business_email'] ) ? strtolower( $form_info['paypal_business_email'] ) : '';
450 $receiver = ! empty( $data['receiver_email'] ) ? strtolower( $data['receiver_email'] ) : ( ! empty( $data['business'] ) ? strtolower( $data['business'] ) : '' );
451
452 if ( $expected_email && $receiver && $expected_email !== $receiver ) {
453 status_header( 200 );
454 exit;
455 }
456
457 // Validate the paid amount is not less than the expected order amount.
458 $paid_amount = ! empty( $data['mc_gross'] ) ? floatval( $data['mc_gross'] ) : 0;
459 if ( $paid_amount < floatval( $results->amount ) ) {
460 status_header( 200 );
461 exit;
462 }
463
464 // Validate currency matches.
465 $paid_currency = ! empty( $data['mc_currency'] ) ? strtoupper( $data['mc_currency'] ) : '';
466 if ( $paid_currency && $results->currency && strtoupper( $results->currency ) !== $paid_currency ) {
467 status_header( 200 );
468 exit;
469 }
470
471 $txn_id = ! empty( $data['txn_id'] ) ? $data['txn_id'] : '';
472
473 // Prevent txn_id replay: reject if this transaction ID was already recorded.
474 if ( $txn_id ) {
475 $existing = $wpdb->get_var(
476 $wpdb->prepare(
477 "SELECT id FROM $table WHERE transaction_id = %s LIMIT 1",
478 $txn_id
479 )
480 );
481 if ( $existing ) {
482 status_header( 200 );
483 exit;
484 }
485 }
486
487 $payer_email = ! empty( $data['payer_email'] ) ? sanitize_email( $data['payer_email'] ) : '';
488
489 $updated = $wpdb->update(
490 $table,
491 [
492 'transaction_id' => $txn_id,
493 'status' => $data['payment_status'],
494 'email' => $payer_email,
495 'customer_info' => maybe_serialize( $data ),
496 ],
497 [ 'ID' => $results->id ]
498 );
499
500 if ( false !== $updated ) {
501 $is_elementor_form = ! empty( $results->referer ) && $results->referer === 'elementor-form' ? 1 : 0;
502
503 // Rebuild the e-mail settings persisted at payment-create time. IPN runs
504 // server-to-server with no live widget context, so without this the body
505 // would collapse to the bare transaction id (every section flag defaults
506 // to 0). Legacy / pending rows created before this fix fall back to [].
507 $email_settings = ! empty( $form_info['email_settings'] ) && is_array( $form_info['email_settings'] )
508 ? $form_info['email_settings']
509 : [];
510
511 // Mirror the Stripe / Paystack gate: honour the form's "Send Email" toggle,
512 // and always e-mail for Elementor Pro Form submissions.
513 if (
514 ( isset( $email_settings['better_payment_form_email_enable'] ) && 'yes' === $email_settings['better_payment_form_email_enable'] )
515 || $is_elementor_form
516 ) {
517 self::better_email_notification( $txn_id, $payer_email, $email_settings, 'PayPal', $results->form_fields_info, $is_elementor_form );
518 }
519 }
520
521 status_header( 200 );
522 exit;
523 }
524
525 /**
526 * Stripe payment success
527 *
528 * Reads the transaction id from the request, EXCEPT when the caller
529 * supplies `better_payment_stripe_id` in $settings. A caller that has
530 * already authorized the id against server-side state (the WooCommerce
531 * return handler matches it to the order's own `_bp_payment_id`) must
532 * pass it explicitly: a caller-side guard that reads $_GET cannot bind
533 * this lookup, because PHP's default request_order=GP lets a POST body
534 * overwrite the query string and the two would then see different ids.
535 *
536 * @since 0.0.1
537 */
538 public static function stripe_payment_success( $settings = [] ) {
539
540 $data = $_REQUEST;
541
542 if ( ! empty( $settings[ 'better_payment_stripe_id' ] ) ) {
543 $data[ 'better_payment_stripe_id' ] = $settings[ 'better_payment_stripe_id' ];
544 }
545
546 if ( !empty( $data[ 'better_payment_stripe_id' ] ) ) {
547 global $wpdb;
548 $table = "{$wpdb->prefix}better_payment";
549 $results = $wpdb->get_row(
550 $wpdb->prepare( "SELECT id, obj_id, amount, transaction_id, currency, form_fields_info, referer FROM $table WHERE order_id=%s and status = 'unpaid' limit 1", sanitize_text_field( $data[ 'better_payment_stripe_id' ] ) )
551 );
552
553 if ( !empty( $results->obj_id ) && !empty( $settings[ 'better_payment_stripe_secret_key' ] ) ) {
554 $header = array(
555 'Authorization' => 'Basic ' . base64_encode( sanitize_text_field( $settings[ 'better_payment_stripe_secret_key' ] ) . ':' ),
556 'Stripe-Version' => '2019-05-16',
557 );
558
559 $request = [
560 'expand' => [
561 'subscription.latest_invoice.payment_intent',
562 'payment_intent'
563 ]
564 ];
565 $form_fields_info = maybe_unserialize($results->form_fields_info);
566 $is_payment_recurring = ! empty( $form_fields_info['mode'] ) && 'subscription' === $form_fields_info['mode'];
567 $is_payment_split_payment = ( $is_payment_recurring ) && ( ! empty( $form_fields_info['is_payment_split_payment'] ) && 1 === intval( $form_fields_info['is_payment_split_payment'] ) );
568
569 $action_data = [
570 'form_fields_info' => $form_fields_info,
571 'is_payment_recurring' => $is_payment_recurring,
572 'is_payment_split_payment' => $is_payment_split_payment,
573 'checkout_session_id' => $results->obj_id,
574 ];
575
576 $response = wp_safe_remote_post(
577 'https://api.stripe.com/v1/checkout/sessions/' . $results->obj_id,
578 array(
579 'method' => 'GET',
580 'headers' => $header,
581 'timeout' => 70,
582 'body' => $request
583 )
584 );
585
586 if ( is_wp_error( $response ) || empty( $response[ 'body' ] ) ) {
587 return new \WP_Error( 'stripe_error', __( 'There was a problem connecting to the Stripe API endpoint.', 'better-payment' ) );
588 }
589
590 $response = json_decode( $response[ 'body' ] );
591 $transaction_id = ! empty( $results->transaction_id ) ? sanitize_text_field( $results->transaction_id ) : '';
592
593 if ( $is_payment_recurring && ! empty( $response->subscription->id ) ) {
594 $transaction_id = sanitize_text_field( $response->subscription->id );
595 $subscription_obj = $response->subscription;
596 $form_fields_info['subscription_id'] = sanitize_text_field( $transaction_id );
597 $form_fields_info['subscription_customer_id'] = ! empty( $response->customer ) ? sanitize_text_field( $response->customer ) : '';
598 $form_fields_info['subscription_plan_id'] = ! empty( $subscription_obj->items->data[0]->plan->id ) ? sanitize_text_field( $subscription_obj->items->data[0]->plan->id ) : '';
599 $form_fields_info['subscription_interval'] = ! empty( $subscription_obj->items->data[0]->plan->interval_count ) ? intval( $subscription_obj->items->data[0]->plan->interval_count ) : '';
600 $form_fields_info['subscription_interval'] .= ! empty( $subscription_obj->items->data[0]->plan->interval ) ? ' ' . sanitize_text_field( $subscription_obj->items->data[0]->plan->interval ) : $form_fields_info['subscription_interval'];
601 $form_fields_info['subscription_current_period_start'] = ! empty( $subscription_obj->current_period_start ) ? intval( $subscription_obj->current_period_start ) : 0;
602 $form_fields_info['subscription_current_period_end'] = ! empty( $subscription_obj->current_period_end ) ? intval( $subscription_obj->current_period_end ) : 0;
603 $form_fields_info['subscription_status'] = sanitize_text_field( $response->status );
604 $form_fields_info['subscription_created_date'] = sanitize_text_field( $subscription_obj->created );
605 }
606
607 if ( isset( $response->error->message ) ) {
608 return new \WP_Error( 'stripe_error', __( 'There was a problem connecting to the Stripe API endpoint.', 'better-payment' ) );
609 }
610
611 $customer_email = $is_payment_recurring && ( ! empty( $response->customer_email ) ) ? sanitize_email( $response->customer_email ) : '';
612 $customer_email_optional = '';
613 if (
614 isset( $response->payment_intent ) &&
615 isset( $response->payment_intent->charges ) &&
616 isset( $response->payment_intent->charges->data ) &&
617 ! empty( $response->payment_intent->charges->data )
618 ) {
619 $charge = current( $response->payment_intent->charges->data );
620
621 if (
622 isset( $charge->billing_details ) &&
623 isset( $charge->billing_details->email ) &&
624 ! empty( $charge->billing_details->email )
625 ) {
626 $customer_email_optional = sanitize_email( $charge->billing_details->email );
627 }
628 }
629
630 $form_fields_info['is_coupon_applied'] = 0;
631 $exact_paid_amount = ! empty( $response->amount_total ) ? floatval( $response->amount_total ) : $results->amount * 100;
632 $exact_paid_amount = $exact_paid_amount / 100;
633 $form_fields_info['exact_paid_amount'] = $exact_paid_amount;
634 if( (int)$exact_paid_amount != (int)$results->amount ) {
635 $form_fields_info['paid_amount_diff'] = $results->amount - $exact_paid_amount;
636 $form_fields_info['is_coupon_applied'] = 1;
637 }
638
639 $updated = $wpdb->update(
640 $table,
641 array(
642 'amount' => $exact_paid_amount,
643 'status' => sanitize_text_field( $response->payment_status ),
644 'email' => $is_payment_recurring ? $customer_email : $customer_email_optional,
645 'customer_info' => maybe_serialize( $response ),
646 'form_fields_info' => maybe_serialize( $form_fields_info ),
647 'transaction_id' => $transaction_id,
648 ),
649 array( 'ID' => $results->id )
650 );
651
652 do_action('better_payment/stripe_payment/success', $action_data);
653 do_action( 'better_payment/payment_confirmed', (int) $results->id );
654
655 $frontend_data = [
656 'amount' => $exact_paid_amount,
657 'email' => $is_payment_recurring ? $customer_email : $customer_email_optional,
658 'transaction_id' => $transaction_id,
659 'currency' => ! empty( $results->currency ) ? $results->currency : __( 'USD', 'better-payment' ),
660 'method' => 'stripe',
661 'is_payment_recurring' => $is_payment_recurring,
662 'is_payment_split_payment' => $is_payment_split_payment,
663 'is_payment_mixed' => ! empty( $form_fields_info['is_payment_mixed'] ) ? intval( $form_fields_info['is_payment_mixed'] ) : 0,
664 'mixed_payment_selected_type' => ! empty( $form_fields_info['mixed_payment_selected_type'] ) ? sanitize_text_field( $form_fields_info['mixed_payment_selected_type'] ) : '',
665 ];
666
667 if ( $is_payment_split_payment ) {
668 $total_amount = ! empty( $form_fields_info['split_payment_total_amount'] )
669 ? sanitize_text_field( $form_fields_info['split_payment_total_amount'] )
670 : 0;
671 $total_installment = ! empty( $form_fields_info['split_payment_installment_iteration'] )
672 ? sanitize_text_field( $form_fields_info['split_payment_installment_iteration'] )
673 : 1;
674
675 // $frontend_data['amount'] = floatval( $total_amount / $total_installment );
676 $frontend_data['total_amount'] = $total_amount;
677 }
678
679 if ( false !== $updated ) {
680 //Send email notification
681 $better_customer_email = $is_payment_recurring ? $customer_email : $customer_email_optional;
682
683 if (
684 (isset($settings[ 'better_payment_form_email_enable' ]) && $settings[ 'better_payment_form_email_enable' ] == 'yes' )
685 || ( $results->referer === 'elementor-form' )
686 ) {
687 $is_elementor_form = ! empty( $results->referer ) && $results->referer === 'elementor-form' ? 1 : 0;
688 self::better_email_notification($transaction_id, $better_customer_email, $settings, 'Stripe', $results->form_fields_info, $is_elementor_form);
689 }
690
691 return $frontend_data;
692 }
693 }
694 }
695 return false;
696 }
697
698 /**
699 * Paystack payment success
700 *
701 * @since 0.0.1
702 */
703 public static function paystack_payment_success( $settings = [] ) {
704 $data = $_REQUEST;
705
706 if ( ! empty( $data[ 'better_payment_paystack_id' ] ) ) {
707 if ( empty( $data['reference'] ) || ! is_scalar( $data['reference'] ) ) {
708 return false;
709 }
710
711 global $wpdb;
712 $table = "{$wpdb->prefix}better_payment";
713 $results = $wpdb->get_row(
714 $wpdb->prepare( "SELECT id,obj_id,transaction_id,amount,currency,form_fields_info,referer FROM $table WHERE order_id=%s and status = 'unpaid' limit 1", sanitize_text_field( $data[ 'better_payment_paystack_id' ] ) )
715 );
716
717 if ( empty( $results->id ) ) {
718 return false;
719 }
720
721 $header_info = array(
722 'Authorization' => 'Bearer ' . sanitize_text_field( $settings[ 'better_payment_paystack_secret_key' ] ),
723 "Cache-Control: no-cache",
724 );
725
726 $response = wp_safe_remote_get(
727 'https://api.paystack.co/transaction/verify/'. sanitize_text_field( $data[ 'reference' ] ),
728 array(
729 'headers' => $header_info,
730 'timeout' => 70,
731 )
732 );
733
734 $response = json_decode(wp_remote_retrieve_body($response));
735
736 if ( ! empty( $response->status ) ) {
737 $verified = isset( $response->data ) ? $response->data : null;
738
739 if ( ! self::paystack_verification_matches( $results, $verified )
740 || self::paystack_reference_claimed_elsewhere( $verified->reference, (int) $results->id ) ) {
741 return false;
742 }
743
744 $updated = $wpdb->update(
745 $table,
746 array(
747 'status' => ! empty( $response->data->status ) ? sanitize_text_field( $response->data->status ) : '',
748 'amount' => ! empty( $response->data->amount ) ? floatval( ( $response->data->amount ) / 100 ) : 0,
749 'obj_id' => ! empty( $response->data->id ) ? sanitize_text_field( $response->data->id ) : '',
750 'transaction_id' => ! empty( $response->data->reference ) ? sanitize_text_field( $response->data->reference ) : '',
751 'customer_info' => ! empty( $response->data->customer ) ? maybe_serialize( $response->data->customer ) : '',
752 'email' => ! empty( $response->data->customer->email ) ? sanitize_email( $response->data->customer->email ) : '',
753 ),
754 array( 'ID' => $results->id )
755 );
756 do_action( 'better_payment/payment_confirmed', (int) $results->id );
757 }
758
759 $better_customer_email = ! empty( $response->data->customer->email ) ? maybe_serialize( $response->data->customer->email ) : '';
760 $transaction_id = ! empty( $response->data->reference ) ? sanitize_text_field( $response->data->reference ) : '';
761
762 $frontend_data = [
763 'amount' => ! empty( $response->data->amount ) ? floatval( ( $response->data->amount ) / 100 ) : 0,
764 'email' => ! empty( $better_customer_email ) ? $better_customer_email : '',
765 'transaction_id' => $transaction_id,
766 'currency' => ! empty( $response->data->currency ) ? sanitize_text_field( $response->data->currency ) : __( 'USD', 'better-payment' ),
767 'method' => 'paystack',
768 ];
769
770 if ( ! empty( $updated ) && $better_customer_email ) {
771 //Send email notification
772 if (
773 (isset($settings[ 'better_payment_form_email_enable' ]) && $settings[ 'better_payment_form_email_enable' ] == 'yes' )
774 || ( $results->referer === 'elementor-form' )
775 ) {
776 $is_elementor_form = ! empty( $results->referer ) && $results->referer === 'elementor-form' ? 1 : 0;
777 self::better_email_notification($transaction_id, $better_customer_email, $settings, 'Paystack', $results->form_fields_info, $is_elementor_form);
778 }
779
780 do_action( 'better_payment/payment/success', sanitize_text_field( $data['better_payment_paystack_id'] ?? '' ), (int) $results->id, 'paystack' );
781
782 return $frontend_data;
783 }
784 }
785 return false;
786 }
787
788 /**
789 * Whether a verified Paystack transaction really pays for the pending order.
790 *
791 * The verify call looks up whatever `reference` the return URL carries, and the return
792 * URL is the visitor's to edit. Without this, a cheap paid transaction could be replayed
793 * against an expensive pending order and mark it paid. Checked, in order:
794 * - the transaction itself succeeded. Paystack's top-level `status: true` only says the
795 * reference resolved — it is just as true for an abandoned or failed checkout, which is
796 * how a visitor who paid nothing used to reach the success page;
797 * - the reference is the one Paystack issued for this order at initialize (rows created
798 * before references were stored have none, and fall through to the amount check —
799 * paystack_reference_claimed_elsewhere() keeps that fallback from being replayable);
800 * - the amount paid covers the amount the order was created for — which
801 * PaymentRequestGuard has already held to what the form allows;
802 * - the currency is the order's.
803 *
804 * @param object $row Pending row: transaction_id, amount, currency.
805 * @param mixed $verified `data` object from Paystack's /transaction/verify response.
806 * @return bool
807 */
808 private static function paystack_verification_matches( $row, $verified ) {
809 if ( ! is_object( $verified ) ) {
810 return false;
811 }
812
813 if ( ! isset( $verified->status ) || 'success' !== $verified->status ) {
814 return false;
815 }
816
817 $expected_reference = isset( $row->transaction_id ) ? (string) $row->transaction_id : '';
818 $verified_reference = isset( $verified->reference ) ? (string) $verified->reference : '';
819
820 if ( '' !== $expected_reference && $verified_reference !== $expected_reference ) {
821 return false;
822 }
823
824 $paid = isset( $verified->amount ) && is_numeric( $verified->amount ) ? floatval( $verified->amount ) / 100 : 0.0;
825
826 if ( $paid + 0.005 < floatval( isset( $row->amount ) ? $row->amount : 0 ) ) {
827 return false;
828 }
829
830 $paid_currency = isset( $verified->currency ) ? strtoupper( (string) $verified->currency ) : '';
831 $order_currency = isset( $row->currency ) ? strtoupper( (string) $row->currency ) : '';
832
833 return '' === $paid_currency || '' === $order_currency || $paid_currency === $order_currency;
834 }
835
836 /**
837 * Whether a Paystack reference already belongs to a different order.
838 *
839 * Every order created since references were stored holds its own reference from
840 * initialize, so this only ever decides the legacy fallback — an order with no stored
841 * reference. Without it, one genuine payment could be replayed to mark every such
842 * order paid. A reference no order has claimed can still settle exactly one of them.
843 *
844 * @param mixed $reference Verified reference.
845 * @param int $row_id The order being verified.
846 * @return bool True when the reference must not settle this order.
847 */
848 private static function paystack_reference_claimed_elsewhere( $reference, $row_id ) {
849 global $wpdb;
850
851 $reference = is_scalar( $reference ) ? sanitize_text_field( (string) $reference ) : '';
852
853 if ( '' === $reference ) {
854 return true;
855 }
856
857 $table = "{$wpdb->prefix}better_payment";
858 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from $wpdb->prefix.
859 $other = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $table WHERE transaction_id = %s AND id <> %d LIMIT 1", $reference, (int) $row_id ) );
860
861 return ! empty( $other );
862 }
863
864 /**
865 * Success message template
866 *
867 * @since 0.0.1
868 */
869 public static function success_message_template( $settings = [], $tr_id = '' ) {
870 if ( empty( $tr_id ) || is_wp_error($tr_id)) {
871 return false;
872 }
873 wp_enqueue_script('better-payment-admin-script');
874 $image_url = BETTER_PAYMENT_ASSETS . '/img/success.svg';
875 $show_icon = 0;
876 $default_icon = 1;
877 $helper_obj = new Helper();
878
879 $store_name = ( isset( $settings['better_payment_form_payment_source'] ) && $settings['better_payment_form_payment_source'] === 'manual' && !empty( $settings['better_payment_form_title'] ) )
880 ? esc_html($settings['better_payment_form_title'])
881 : esc_html( get_bloginfo('name') );
882
883 $payment_desc_text = !empty( $settings['better_payment_form_success_message_heading'] )
884 ? esc_html($settings['better_payment_form_success_message_heading'])
885 : esc_html( __('You paid', 'better-payment') . ' [currency_symbol][amount] ' . __('to', 'better-payment') . ' [store_name]');
886
887 $bp__currency_symbol = esc_html( ! empty( $tr_id['currency'] ) ? $helper_obj->get_currency_symbol( $tr_id['currency'] ) : '' );
888
889 $payment_desc_text = str_replace( '[currency_symbol]', '<span class="bp-bold">' . $bp__currency_symbol . '</span>', $payment_desc_text );
890
891 $payment_desc_text = str_replace( '[amount]', '<span class="bp-bold">' . esc_html( ! empty( $tr_id['amount'] ) ? $tr_id['amount'] : 0 ) . '</span>', $payment_desc_text );
892
893 $payment_desc_text = str_replace( '[store_name]', '<span class="bp-bold">' . esc_html( $store_name ) . '</span>', $payment_desc_text );
894
895 $payment_mail_text = !empty( $settings['better_payment_form_success_message_sub_heading'] ) ? esc_html($settings['better_payment_form_success_message_sub_heading']) : esc_html( __('Payment Confirmation email will be sent to ', 'better-payment') . '[customer_email]');
896
897 $payment_mail_text = str_replace( '[customer_email]', '<span class="bp-bold">' . esc_html( ! empty( $tr_id['email'] ) ? $tr_id['email'] : '' ) . '</span>', $payment_mail_text );
898
899 if ( !empty( $settings[ 'better_payment_form_success_message_icon' ][ 'library' ] ) ) {
900 if ( $settings[ 'better_payment_form_success_message_icon' ][ 'library' ] == 'svg' ) {
901 $image_url = $settings[ 'better_payment_form_success_message_icon' ][ 'value' ][ 'url' ];
902 $default_icon = 0;
903 } else {
904 $show_icon = 1;
905 $image_url = $settings[ 'better_payment_form_success_message_icon' ][ 'value' ];
906 $default_icon = 0;
907 }
908 }
909
910
911 $allowed_payment_methods = [
912 'paypal' => BETTER_PAYMENT_ASSETS . '/img/paypal-2.svg',
913 'stripe' => BETTER_PAYMENT_ASSETS . '/img/stripe-2.svg',
914 'paystack' => BETTER_PAYMENT_ASSETS . '/img/paystack-2.svg',
915 ];
916 $payment_method_logo = isset( $tr_id['method'] ) && isset( $allowed_payment_methods[ $tr_id['method'] ] ) ? $allowed_payment_methods[ $tr_id['method'] ] : '';
917
918 $better_payment_form_success_message_thanks = !empty( $settings['better_payment_form_success_message_thanks'] )
919 ? esc_html( $settings['better_payment_form_success_message_thanks'] )
920 : esc_html__('Thank You!', 'better-payment');
921 $better_payment_form_success_message_transaction = !empty( $settings['better_payment_form_success_message_transaction'] )
922 ? esc_html__( $settings['better_payment_form_success_message_transaction'], 'better-payment' )
923 : esc_html__('Transaction ID', 'better-payment');
924 $better_payment_form_success_message_amount_text = !empty( $settings['better_payment_form_success_message_amount_text'] )
925 ? esc_html( $settings['better_payment_form_success_message_amount_text'] )
926 : esc_html__( 'Amount', 'better-payment' );
927 $better_payment_form_success_message_currency_text = !empty( $settings['better_payment_form_success_message_currency_text'] )
928 ? esc_html( $settings['better_payment_form_success_message_currency_text'] )
929 : esc_html__( 'Currency', 'better-payment' );
930 $better_payment_form_success_message_pay_method_text = !empty( $settings['better_payment_form_success_message_pay_method_text'] ) ? esc_html( $settings['better_payment_form_success_message_pay_method_text'] ) : esc_html__( 'Payment Method', 'better-payment' );
931 $better_payment_form_success_message_pay_type_text = !empty( $settings['better_payment_form_success_message_pay_type_text'] )
932 ? esc_html( $settings['better_payment_form_success_message_pay_type_text'] )
933 : esc_html__( 'Payment Type', 'better-payment' );
934 $better_payment_form_payment_type = !empty( $settings['better_payment_form_payment_type'] )
935 ? esc_html( $settings['better_payment_form_payment_type'] )
936 : '';
937 if ( ! empty( $better_payment_form_payment_type ) ) {
938 if ( 'one-time' === $better_payment_form_payment_type ) {
939 $better_payment_form_payment_type_value = esc_html__( 'One Time Payment', 'better-payment' );
940 } elseif ( 'recurring' === $better_payment_form_payment_type ) {
941 $better_payment_form_payment_type_value = esc_html__( 'Recurring Payment', 'better-payment' );
942 } elseif ( 'mixed' === $better_payment_form_payment_type ) {
943 $mixed_payment_selected_type = ! empty( $tr_id['mixed_payment_selected_type'] ) ? sanitize_text_field( $tr_id['mixed_payment_selected_type'] ) : '';
944 $is_mixed_recurring = 'recurring' === $mixed_payment_selected_type;
945
946 if ( empty( $mixed_payment_selected_type ) ) {
947 $is_mixed_recurring = ! empty( $tr_id['is_payment_recurring'] ) && empty( $tr_id['is_payment_split_payment'] );
948 }
949
950 $better_payment_form_payment_type_value = $is_mixed_recurring
951 ? esc_html__( 'Recurring Payment', 'better-payment' )
952 : esc_html__( 'One Time Payment', 'better-payment' );
953 } else {
954 $better_payment_form_payment_type_value = esc_html__( 'Split Payment', 'better-payment' );
955 }
956 }
957 $better_payment_form_success_message_merchant_details_text = !empty( $settings['better_payment_form_success_message_merchant_details_text'] )
958 ? esc_html( $settings['better_payment_form_success_message_merchant_details_text'] )
959 : esc_html__( 'Merchant Details', 'better-payment' );
960 $better_payment_form_success_message_merchant_details_value = isset( $settings['better_payment_form_payment_source'] ) && $settings['better_payment_form_payment_source'] === 'manual' && ! empty( $settings['better_payment_form_title'] )
961 ? esc_html($settings['better_payment_form_title'])
962 : esc_html( get_bloginfo( 'name' ) );
963 $better_payment_form_success_message_paid_amount_text = !empty( $settings['better_payment_form_success_message_paid_amount_text'] )
964 ? esc_html( $settings['better_payment_form_success_message_paid_amount_text'] )
965 : esc_html__( 'Paid Amount', 'better-payment' );
966 $better_payment_form_success_message_purchase_details_text = !empty( $settings['better_payment_form_success_message_purchase_details_text'] )
967 ? esc_html( $settings['better_payment_form_success_message_purchase_details_text'] )
968 : esc_html__( 'Purchase Details', 'better-payment' );
969 $better_payment_form_success_message_print_text = !empty( $settings['better_payment_form_success_message_print_text'] )
970 ? esc_html( $settings['better_payment_form_success_message_print_text'] )
971 : esc_html__( 'Print', 'better-payment' );
972 $better_payment_form_success_message_view_details_btn_text = !empty( $settings['better_payment_form_success_message_view_details_btn_text'] )
973 ? esc_html( $settings['better_payment_form_success_message_view_details_btn_text'] )
974 : esc_html__( 'View Details', 'better-payment' );
975 $better_payment_form_success_page_view_details_url = !empty( $settings['better_payment_form_success_page_view_details_url']['url'] ) ? esc_url( $settings['better_payment_form_success_page_view_details_url']['url'] ) : 'javascript:void(0)';
976 ?>
977 <section class="bp-thank_page">
978 <div class="bp-thank_page-wrapper">
979 <div class="bp-thank_page-logo">
980 <?php if( $default_icon ): ?>
981 <span class="bp-thank_page-logo_wrapper">
982 <img src="<?php echo esc_url( BETTER_PAYMENT_ASSETS . '/img/success-2.svg' ); ?>" alt="Better Payment logo">
983 </span>
984 <?php endif; ?>
985 <?php if( ! $default_icon ): ?>
986 <?php if( $show_icon ): ?>
987 <?php
988 // Add 'pf-block' class if this is a Gutenberg block (not Elementor)
989 $success_wrapper_class = 'bp-thank_page-custom-logo_wrapper-success';
990 if ( ! empty( $settings['better_payment_form_source'] ) && 'block' === $settings['better_payment_form_source'] ) {
991 $success_wrapper_class .= ' pf-block';
992 }
993 ?>
994 <div class="<?php echo esc_attr( $success_wrapper_class ); ?>">
995 <?php
996 // Render icon safely (handles FontAwesome, Dashicons, SVG with or without Elementor)
997 if (!empty($settings['better_payment_form_success_message_icon'])) {
998 self::render_icon($settings['better_payment_form_success_message_icon'], ['aria-hidden' => 'true']);
999 } else {
1000 // Fallback to default icons
1001 echo '<i class="' . esc_attr($image_url) . '"></i>';
1002 }
1003 ?>
1004 </div>
1005 <?php endif; ?>
1006 <?php if( ! $show_icon ): ?>
1007 <span class="bp-thank_page-logo_wrapper">
1008 <img src="<?php echo esc_url($image_url); ?>" alt="Success logo">
1009 </span>
1010 <?php endif; ?>
1011 <?php endif; ?>
1012 </div>
1013 <div class="bp-thank_page-text">
1014 <h2 class="bp-font_ibm bp-page_header">
1015 <?php echo wp_kses_post( $better_payment_form_success_message_thanks ); ?>
1016 <span> &#127881;</span>
1017 </h2>
1018 <p class="bp-font_ibm bp-reason_for-text">
1019 <?php echo wp_kses_post( $payment_desc_text ); ?>
1020 </p>
1021 <?php if (isset( $settings['better_payment_form_email_enable'] ) && !empty( $tr_id['email'] )): ?>
1022 <p class="bp-font_ibm bp-payment_info">
1023 <?php echo wp_kses_post( $payment_mail_text ); ?>
1024 </p>
1025 <?php endif; ?>
1026
1027 <div class="bp-flex bp-transaction_info-wrapper">
1028 <p class="bp-flex bp-font_ibm bp-transaction_info"> <span class="line-height-0">
1029 <img src="<?php echo esc_url( BETTER_PAYMENT_ASSETS . '/img/wallet.svg' ); ?>" alt="Better Payment wallet">
1030 </span>
1031 <span>
1032 <?php echo wp_kses_post( $better_payment_form_success_message_transaction ); ?>:
1033 </span>
1034 <span class="bp-bold bp-transaction_id"><?php echo esc_html( ! empty( $tr_id['transaction_id']) ? $tr_id['transaction_id'] : '' ); ?></span>
1035 </p>
1036
1037 <button class="bp-transaction_data-copy_btn ">
1038 <svg width="20" height="20" viewBox="0 0 20 20"
1039 fill="none" xmlns="http://www.w3.org/2000/svg">
1040 <path
1041 d="M15.0002 15.601C16.3257 15.601 17.4002 14.5265 17.4002 13.201V8.40098C17.4002 5.38399 17.4002 3.87549 16.463 2.93823C15.5257 2.00098 14.0172 2.00098 11.0002 2.00098H7.80024C6.47476 2.00098 5.40024 3.07549 5.40024 4.40098M10.2002 18.001H7.80024C5.5375 18.001 4.40613 18.001 3.70319 17.298C3.00024 16.5951 3.00024 15.4637 3.00024 13.201V9.20098C3.00024 6.93823 3.00024 5.80686 3.70319 5.10392C4.40613 4.40098 5.5375 4.40098 7.80024 4.40098H10.2002C12.463 4.40098 13.5944 4.40098 14.2973 5.10392C15.0002 5.80686 15.0002 6.93824 15.0002 9.20098V13.201C15.0002 15.4637 15.0002 16.5951 14.2973 17.298C13.5944 18.001 12.463 18.001 10.2002 18.001Z"
1042 stroke-width="1.152" />
1043 </svg>
1044 </button>
1045 </div>
1046 </div>
1047
1048 <div class="bp-thank_page-info">
1049 <ul class="bp-page_info-list">
1050 <li class="bp-flex bp-font_ibm bp-page_info-list_item">
1051 <span class="bp-info_content">
1052 <?php
1053 echo wp_kses_post( $better_payment_form_success_message_amount_text );
1054 ?>
1055 </span>
1056 <span class="bp-info_content bp-bold"><?php echo $bp__currency_symbol . esc_html( ! empty( $tr_id['amount'] ) ? $tr_id['amount'] : 0 ); ?></span>
1057 </li>
1058 <li class="bp-flex bp-font_ibm bp-page_info-list_item">
1059 <span class=" bp-info_content">
1060 <?php
1061 echo wp_kses_post( $better_payment_form_success_message_currency_text );
1062 ?>
1063 </span>
1064 <span class="bp-info_content bp-bold"><?php echo esc_html( ! empty( $tr_id['currency'] ) ? $tr_id['currency'] : '' ); ?></span>
1065 </li>
1066 <li class="bp-flex bp-font_ibm bp-page_info-list_item">
1067 <span class="bp-info_content">
1068 <?php
1069 echo wp_kses_post( $better_payment_form_success_message_pay_method_text );
1070 ?>
1071 </span>
1072 <div class="bp-payment_method-logo-wrapper">
1073 <?php if ( !empty( $payment_method_logo ) ) : ?>
1074 <img src="<?php echo esc_url( $payment_method_logo ); ?>" alt="<?php echo esc_attr( $tr_id['method'] ); ?>" class="bp-payment_method-logo">
1075 <?php endif; ?>
1076 </div>
1077 </li>
1078 <?php if( !empty( $better_payment_form_payment_type ) ): ?>
1079 <li class="bp-flex bp-font_ibm bp-page_info-list_item">
1080 <span class="bp-info_content">
1081 <?php
1082 echo wp_kses_post( $better_payment_form_success_message_pay_type_text );
1083 ?>
1084 </span>
1085 <span class="bp-info_content bp-bold"><?php echo wp_kses_post( $better_payment_form_payment_type_value ); ?></span>
1086 </li>
1087 <?php endif; ?>
1088 <li class="bp-flex bp-font_ibm bp-page_info-list_item">
1089 <span class="bp-info_content">
1090 <?php
1091 echo wp_kses_post( $better_payment_form_success_message_merchant_details_text );
1092 ?>
1093 </span>
1094 <span class="bp-info_content bp-bold">
1095 <?php echo wp_kses_post( $better_payment_form_success_message_merchant_details_value ); ?>
1096 </span>
1097 </li>
1098 <?php if( isset( $tr_id['is_payment_split_payment'] ) && $tr_id['is_payment_split_payment'] ): ?>
1099 <li class="bp-flex bp-font_ibm bp-page_info-list_item">
1100 <span class="bp-info_content">
1101 <?php
1102 echo wp_kses_post( $better_payment_form_success_message_paid_amount_text );
1103 ?>
1104 </span>
1105 <span class="bp-info_content bp-bold">
1106 <?php echo $bp__currency_symbol . esc_html($tr_id['amount']); ?> out of <?php echo $bp__currency_symbol . esc_html($tr_id['total_amount']); ?>
1107 </span>
1108 </li>
1109 <?php endif; ?>
1110 <?php
1111 $has_woo_products = !empty($settings['better_payment_form_woocommerce_product_id']) || !empty($settings['better_payment_form_woocommerce_product_ids']);
1112 $has_fluentcart_products = !empty($settings['better_payment_form_fluentcart_product_id']) || !empty($settings['better_payment_form_fluentcart_product_ids']);
1113
1114 if( $has_woo_products || $has_fluentcart_products ):
1115 ?>
1116 <li class="bp-flex bp-font_ibm bp-page_info-list_item">
1117 <span class="bp-info_content">
1118 <?php
1119 echo wp_kses_post( $better_payment_form_success_message_purchase_details_text );
1120 ?>
1121 </span>
1122 <?php
1123 $product_names = [];
1124
1125 if( !empty( $settings['better_payment_form_woocommerce_product_id'] ) ):
1126 $product_names[] = get_the_title( $settings['better_payment_form_woocommerce_product_id'] );
1127 endif;
1128
1129 if( !empty( $settings['better_payment_form_woocommerce_product_ids'] ) ):
1130 $product_ids = $settings['better_payment_form_woocommerce_product_ids'];
1131 foreach( $product_ids as $product_id ):
1132 $product_names[] = get_the_title( $product_id );
1133 endforeach;
1134 endif;
1135
1136 if( !empty( $settings['better_payment_form_fluentcart_product_id'] ) && function_exists('fluentCart') && class_exists( '\FluentCart\App\Models\Product' ) ):
1137 try {
1138 $fluentcart_product = \FluentCart\App\Models\Product::query()->find($settings['better_payment_form_fluentcart_product_id']);
1139 if ($fluentcart_product) {
1140 $product_names[] = sanitize_text_field( $fluentcart_product->post_title );
1141 }
1142 } catch ( \Exception $e ) {
1143 //
1144 }
1145 endif;
1146
1147 if( !empty( $settings['better_payment_form_fluentcart_product_ids'] ) && function_exists('fluentCart') && class_exists( '\FluentCart\App\Models\Product' ) ):
1148 $product_ids = $settings['better_payment_form_fluentcart_product_ids'];
1149 foreach( $product_ids as $product_id ):
1150 try {
1151 $fluentcart_product = \FluentCart\App\Models\Product::query()->find($product_id);
1152 if ($fluentcart_product) {
1153 $product_names[] = sanitize_text_field( $fluentcart_product->post_title );
1154 }
1155 } catch ( \Exception $e ) {
1156 //
1157 }
1158 endforeach;
1159 endif;
1160
1161 if( !empty($product_names) ):
1162 echo '<span class="bp-info_content bp-bold">' . implode( ', ', $product_names ) . '</span>';
1163 endif;
1164 ?>
1165 </li>
1166 <?php endif; ?>
1167 </ul>
1168 </div>
1169
1170 <div class="bp-flex bp-button_group">
1171 <button class="bp-flex bp-font_ibm bp-print_btn">
1172 <span style="line-height: 0;">
1173 <img src="<?php echo esc_url( BETTER_PAYMENT_ASSETS . '/img/print.svg' ); ?>" alt="Better Payment print">
1174 </span>
1175 <span class="bp-font_ibm">
1176 <?php echo wp_kses_post( $better_payment_form_success_message_print_text ); ?>
1177 </span>
1178 </button>
1179
1180 <div class="bp-flex bp-btn_wrapper">
1181 <a href="<?php echo $better_payment_form_success_page_view_details_url; ?>" target="_blank" class="bp-font_ibm bp-details_btn">
1182 <?php echo wp_kses_post( $better_payment_form_success_message_view_details_btn_text ); ?>
1183 </a>
1184 </div>
1185 </div>
1186 </div>
1187 </section>
1188 <?php
1189 }
1190
1191 /**
1192 * Error message template
1193 *
1194 * @since 0.0.1
1195 */
1196 public static function failed_message_template( $settings = [], $transaction_data = false ) {
1197
1198 $image_url = BETTER_PAYMENT_ASSETS . '/img/fail.svg';
1199 $show_icon = 0;
1200 $default_icon = 1;
1201 $failed_heading = !empty( $settings['better_payment_form_error_message_heading'] ) ? esc_html( $settings['better_payment_form_error_message_heading'] ) : esc_html__( 'Payment Failed!', 'better-payment' );
1202 $failed_sub_heading = !empty( $settings['better_payment_form_error_message_sub_heading'] ) ? esc_html( $settings['better_payment_form_error_message_sub_heading'] ) : esc_html__( 'Your payment has failed. Please check your payment details', 'better-payment' );
1203 $transaction_id_text = !empty( $settings['better_payment_form_error_message_transaction_id_text'] ) ? esc_html( $settings['better_payment_form_error_message_transaction_id_text'] ) : esc_html__( 'Transaction ID', 'better-payment' );
1204 $show_details_button = !empty( $settings['better_payment_form_error_details_button_switch'] ) && $settings['better_payment_form_error_details_button_switch'] == 'yes' ? true : false;
1205 $details_button_text = !empty( $settings['better_payment_form_error_details_button_text'] ) ? esc_html( $settings['better_payment_form_error_details_button_text'] ) : esc_html__( 'View Details', 'better-payment' );
1206 $better_payment_form_error_details_page_url = !empty( $settings['better_payment_form_error_details_page_url']['url'] ) ? esc_url( $settings['better_payment_form_error_details_page_url']['url'] ) : 'javascript:void(0)';
1207 $store_name = ( isset( $settings['better_payment_form_payment_source'] ) && $settings['better_payment_form_payment_source'] === 'manual' && !empty( $settings['better_payment_form_title'] ) )
1208 ? esc_html($settings['better_payment_form_title'])
1209 : esc_html( get_bloginfo('name') );
1210
1211 // Get transaction details
1212 $transaction_amount = '';
1213 $transaction_id = '';
1214 $currency_symbol = '';
1215
1216 if ( !empty( $transaction_data ) ) {
1217 $transaction_amount = !empty( $transaction_data['amount'] ) ? floatval( $transaction_data['amount'] ) : '';
1218 $currency_symbol = !empty( $transaction_data['currency_symbol'] ) ? esc_html( $transaction_data['currency_symbol'] ) : '';
1219 $transaction_id = !empty( $transaction_data['transaction_id'] ) ? esc_html( $transaction_data['transaction_id'] ) : '';
1220 }
1221
1222 if ( !empty( $settings[ 'better_payment_form_error_message_icon' ][ 'library' ] ) ) {
1223 if ( $settings[ 'better_payment_form_error_message_icon' ][ 'library' ] == 'svg' ) {
1224 $image_url = $settings[ 'better_payment_form_error_message_icon' ][ 'value' ][ 'url' ];
1225 $default_icon = 0;
1226 } else {
1227 $show_icon = 1;
1228 $image_url = $settings[ 'better_payment_form_error_message_icon' ][ 'value' ];
1229 $default_icon = 0;
1230 }
1231 }
1232 ?>
1233 <section class="payment-failed-screen-section">
1234 <div class="bp-thank_page-wrapper">
1235 <div class="bp-thank_page-logo">
1236 <?php if( $default_icon ): ?>
1237 <span class="bp-thank_page-logo_wrapper">
1238 <svg width="58" height="58" viewBox="0 0 58 58" fill="none" xmlns="http://www.w3.org/2000/svg">
1239 <path d="M40.804 10.7998H16.8039C13.9409 10.8041 11.1964 11.9434 9.17192 13.9678C7.14746 15.9923 6.00822 18.7368 6.00391 21.5998H51.604C51.5997 18.7368 50.4604 15.9923 48.436 13.9678C46.4115 11.9434 43.667 10.8041 40.804 10.7998ZM6.00391 26.3998V35.9998C6.00822 38.8628 7.14746 41.6073 9.17192 43.6318C11.1964 45.6562 13.9409 46.7955 16.8039 46.7998H40.804C43.667 46.7955 46.4115 45.6562 48.436 43.6318C50.4604 41.6073 51.5997 38.8628 51.604 35.9998V26.3998H6.00391ZM24.0039 38.3998H16.8039C16.1674 38.3998 15.557 38.1469 15.1069 37.6968C14.6568 37.2468 14.4039 36.6363 14.4039 35.9998C14.4039 35.3633 14.6568 34.7529 15.1069 34.3028C15.557 33.8527 16.1674 33.5998 16.8039 33.5998H24.0039C24.6404 33.5998 25.2509 33.8527 25.7009 34.3028C26.151 34.7529 26.4039 35.3633 26.4039 35.9998C26.4039 36.6363 26.151 37.2468 25.7009 37.6968C25.2509 38.1469 24.6404 38.3998 24.0039 38.3998Z" fill="#F44336"></path>
1240 <circle cx="45.5" cy="41.5" r="6.5" fill="white"></circle>
1241 <foreignObject x="21.4686" y="17.3701" width="47.2066" height="47.3399">
1242 <div xmlns="http://www.w3.org/1999/xhtml" style="backdrop-filter:blur(6.36px);clip-path:url(#bgblur_0_3595_8284_clip_path);height:100%;width:100%">
1243 </div>
1244 </foreignObject>
1245 <g filter="url(#filter0_d_3595_8284)" data-figma-bg-blur-radius="12.71">
1246 <path d="M55.765 38.782C55.3067 36.6386 54.2191 34.6825 52.6429 33.1664C50.6276 31.1788 47.9133 30.0699 45.0893 30.0804C42.1969 30.0848 39.4243 31.2411 37.3791 33.2958C35.334 35.3505 34.183 38.136 34.1786 41.0417V41.8849C34.2614 43.0459 34.5336 44.1853 34.9843 45.2576C36.0299 47.7766 37.9733 49.8131 40.4344 50.9688C42.8955 52.1245 45.6972 52.3162 48.2915 51.5064C50.8859 50.6967 53.0862 48.9437 54.4622 46.5904C55.8381 44.2371 56.2907 41.4528 55.7314 38.782H55.765ZM46.4154 44.7686L45.0893 43.4364L43.7632 44.7686C43.6072 44.9266 43.4215 45.0521 43.217 45.1377C43.0124 45.2233 42.793 45.2674 42.5714 45.2674C42.3498 45.2674 42.1304 45.2233 41.9259 45.1377C41.7213 45.0521 41.5357 44.9266 41.3796 44.7686C41.2223 44.6118 41.0974 44.4253 41.0122 44.2198C40.927 44.0143 40.8831 43.7939 40.8831 43.5713C40.8831 43.3486 40.927 43.1282 41.0122 42.9227C41.0974 42.7172 41.2223 42.5307 41.3796 42.374L41.8664 41.8849L42.7057 41.0417L41.3796 39.7095C41.0636 39.3919 40.886 38.9613 40.886 38.5122C40.886 38.0631 41.0636 37.6324 41.3796 37.3149C41.6957 36.9973 42.1244 36.8189 42.5714 36.8189C43.0184 36.8189 43.4471 36.9973 43.7632 37.3149L45.0893 38.6471L46.4154 37.3149C46.7314 36.9973 47.1601 36.8189 47.6071 36.8189C48.0541 36.8189 48.4828 36.9973 48.7989 37.3149C49.115 37.6324 49.2926 38.0631 49.2926 38.5122C49.2926 38.9613 49.115 39.3919 48.7989 39.7095L47.4729 41.0417L48.2618 41.8343L48.7989 42.374C48.9563 42.5307 49.0811 42.7172 49.1664 42.9227C49.2516 43.1282 49.2954 43.3486 49.2954 43.5713C49.2954 43.7939 49.2516 44.0143 49.1664 44.2198C49.0811 44.4253 48.9563 44.6118 48.7989 44.7686C48.6429 44.9266 48.4572 45.0521 48.2527 45.1377C48.0481 45.2233 47.8287 45.2674 47.6071 45.2674C47.3855 45.2674 47.1661 45.2233 46.9616 45.1377C46.7571 45.0521 46.5714 44.9266 46.4154 44.7686Z" fill="#F44336" fill-opacity="0.5" shape-rendering="crispEdges"></path>
1247 </g>
1248 <defs>
1249 <filter id="filter0_d_3595_8284" x="21.4686" y="17.3701" width="47.2066" height="47.3399" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
1250 <feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood>
1251 <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix>
1252 <feOffset dx="-4"></feOffset>
1253 <feGaussianBlur stdDeviation="2"></feGaussianBlur>
1254 <feComposite in2="hardAlpha" operator="out"></feComposite>
1255 <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"></feColorMatrix>
1256 <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_3595_8284"></feBlend>
1257 <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_3595_8284" result="shape"></feBlend>
1258 </filter>
1259 <clipPath id="bgblur_0_3595_8284_clip_path" transform="translate(-21.4686 -17.3701)">
1260 <path d="M55.765 38.782C55.3067 36.6386 54.2191 34.6825 52.6429 33.1664C50.6276 31.1788 47.9133 30.0699 45.0893 30.0804C42.1969 30.0848 39.4243 31.2411 37.3791 33.2958C35.334 35.3505 34.183 38.136 34.1786 41.0417V41.8849C34.2614 43.0459 34.5336 44.1853 34.9843 45.2576C36.0299 47.7766 37.9733 49.8131 40.4344 50.9688C42.8955 52.1245 45.6972 52.3162 48.2915 51.5064C50.8859 50.6967 53.0862 48.9437 54.4622 46.5904C55.8381 44.2371 56.2907 41.4528 55.7314 38.782H55.765ZM46.4154 44.7686L45.0893 43.4364L43.7632 44.7686C43.6072 44.9266 43.4215 45.0521 43.217 45.1377C43.0124 45.2233 42.793 45.2674 42.5714 45.2674C42.3498 45.2674 42.1304 45.2233 41.9259 45.1377C41.7213 45.0521 41.5357 44.9266 41.3796 44.7686C41.2223 44.6118 41.0974 44.4253 41.0122 44.2198C40.927 44.0143 40.8831 43.7939 40.8831 43.5713C40.8831 43.3486 40.927 43.1282 41.0122 42.9227C41.0974 42.7172 41.2223 42.5307 41.3796 42.374L41.8664 41.8849L42.7057 41.0417L41.3796 39.7095C41.0636 39.3919 40.886 38.9613 40.886 38.5122C40.886 38.0631 41.0636 37.6324 41.3796 37.3149C41.6957 36.9973 42.1244 36.8189 42.5714 36.8189C43.0184 36.8189 43.4471 36.9973 43.7632 37.3149L45.0893 38.6471L46.4154 37.3149C46.7314 36.9973 47.1601 36.8189 47.6071 36.8189C48.0541 36.8189 48.4828 36.9973 48.7989 37.3149C49.115 37.6324 49.2926 38.0631 49.2926 38.5122C49.2926 38.9613 49.115 39.3919 48.7989 39.7095L47.4729 41.0417L48.2618 41.8343L48.7989 42.374C48.9563 42.5307 49.0811 42.7172 49.1664 42.9227C49.2516 43.1282 49.2954 43.3486 49.2954 43.5713C49.2954 43.7939 49.2516 44.0143 49.1664 44.2198C49.0811 44.4253 48.9563 44.6118 48.7989 44.7686C48.6429 44.9266 48.4572 45.0521 48.2527 45.1377C48.0481 45.2233 47.8287 45.2674 47.6071 45.2674C47.3855 45.2674 47.1661 45.2233 46.9616 45.1377C46.7571 45.0521 46.5714 44.9266 46.4154 44.7686Z"></path>
1261 </clipPath>
1262 </defs>
1263 </svg>
1264 </span>
1265 <?php else: ?>
1266 <?php if( $show_icon ): ?>
1267 <?php
1268 // Add 'pf-block' class if this is a Gutenberg block (not Elementor)
1269 $error_wrapper_class = 'bp-thank_page-custom-logo_wrapper-error';
1270 if ( ! empty( $settings['better_payment_form_source'] ) && 'block' === $settings['better_payment_form_source'] ) {
1271 $error_wrapper_class .= ' pf-block';
1272 }
1273 ?>
1274 <div class="<?php echo esc_attr( $error_wrapper_class ); ?>">
1275 <?php
1276 // Render icon safely (handles FontAwesome, Dashicons, SVG with or without Elementor)
1277 if (!empty($settings['better_payment_form_error_message_icon'])) {
1278 self::render_icon($settings['better_payment_form_error_message_icon'], ['aria-hidden' => 'true']);
1279 } else {
1280 // Fallback to default icons
1281 echo '<i class="' . esc_attr($image_url) . '"></i>';
1282 }
1283 ?>
1284 </div>
1285 <?php else: ?>
1286 <span class="bp-thank_page-logo_wrapper">
1287 <img src="<?php echo esc_url($image_url); ?>" alt="Error logo">
1288 </span>
1289 <?php endif; ?>
1290 <?php endif; ?>
1291 </div>
1292 <div class="bp-thank_page-text">
1293 <h2 class="bp-font_ibm bp-page_header"><?php echo wp_kses_post( $failed_heading ); ?></h2>
1294 <p class="bp-font_ibm bp-reason_for-text">Payment of <?php echo $currency_symbol . $transaction_amount; ?> to <?php echo $store_name; ?> failed.</p>
1295 <p class="bp-font_ibm bp-payment_info"><?php echo wp_kses_post( $failed_sub_heading ); ?></p>
1296
1297 <div class="bp-flex bp-transaction_info-wrapper">
1298 <p class="bp-flex bp-font_ibm bp-transaction_info"> <span>
1299 <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
1300 <g clip-path="url(#clip0_2914_6421)">
1301 <path d="M12.2002 12.825C11.8821 12.825 11.6242 13.0829 11.6242 13.401C11.6242 13.7191 11.8821 13.977 12.2002 13.977V12.825ZM17.0002 13.401L17.4075 13.8083C17.5156 13.7002 17.5762 13.5537 17.5762 13.401C17.5762 13.2482 17.5156 13.1017 17.4075 12.9937L17.0002 13.401ZM14.993 14.5937C14.768 14.8186 14.768 15.1833 14.993 15.4083C15.2179 15.6332 15.5826 15.6332 15.8075 15.4083L14.993 14.5937ZM15.8075 11.3937C15.5826 11.1687 15.2179 11.1687 14.993 11.3937C14.768 11.6186 14.768 11.9833 14.993 12.2083L15.8075 11.3937ZM10.6002 16.377C10.9184 16.377 11.1762 16.1191 11.1762 15.801C11.1762 15.4829 10.9184 15.225 10.6002 15.225V16.377ZM16.4242 9.40098C16.4242 9.71909 16.6821 9.97698 17.0002 9.97698C17.3184 9.97698 17.5762 9.71909 17.5762 9.40098H16.4242ZM7.40024 13.177C7.71836 13.177 7.97624 12.9191 7.97624 12.601C7.97624 12.2829 7.71836 12.025 7.40024 12.025V13.177ZM4.20024 12.025C3.88213 12.025 3.62424 12.2829 3.62424 12.601C3.62424 12.9191 3.88213 13.177 4.20024 13.177V12.025ZM9.80024 13.177C10.1184 13.177 10.3762 12.9191 10.3762 12.601C10.3762 12.2829 10.1184 12.025 9.80024 12.025V13.177ZM9.40024 12.025C9.08213 12.025 8.82424 12.2829 8.82424 12.601C8.82424 12.9191 9.08213 13.177 9.40024 13.177V12.025ZM1.00024 7.22498C0.682128 7.22498 0.424244 7.48286 0.424244 7.80098C0.424244 8.11909 0.682128 8.37698 1.00024 8.37698V7.22498ZM17.0002 8.37698C17.3184 8.37698 17.5762 8.11909 17.5762 7.80098C17.5762 7.48286 17.3184 7.22498 17.0002 7.22498V8.37698ZM12.2002 13.977H17.0002V12.825H12.2002V13.977ZM16.593 12.9937L14.993 14.5937L15.8075 15.4083L17.4075 13.8083L16.593 12.9937ZM17.4075 12.9937L15.8075 11.3937L14.993 12.2083L16.593 13.8083L17.4075 12.9937ZM7.40024 3.57698H10.6002V2.42498H7.40024V3.57698ZM10.6002 15.225H7.40024V16.377H10.6002V15.225ZM7.40024 15.225C5.87547 15.225 4.78983 15.2238 3.96572 15.113C3.15819 15.0044 2.68857 14.8002 2.3448 14.4564L1.53021 15.271C2.1237 15.8645 2.87695 16.1289 3.81222 16.2547C4.73093 16.3782 5.90803 16.377 7.40024 16.377V15.225ZM0.424244 9.40098C0.424244 10.8932 0.423021 12.0703 0.546538 12.989C0.672282 13.9243 0.936721 14.6775 1.53021 15.271L2.3448 14.4564C2.00103 14.1127 1.79684 13.643 1.68827 12.8355C1.57747 12.0114 1.57624 10.9258 1.57624 9.40098H0.424244ZM10.6002 3.57698C12.125 3.57698 13.2107 3.5782 14.0348 3.689C14.8423 3.79757 15.3119 4.00176 15.6557 4.34553L16.4703 3.53094C15.8768 2.93745 15.1235 2.67301 14.1883 2.54727C13.2696 2.42375 12.0925 2.42498 10.6002 2.42498V3.57698ZM17.5762 9.40098C17.5762 7.90876 17.5775 6.73166 17.4539 5.81296C17.3282 4.87768 17.0638 4.12443 16.4703 3.53094L15.6557 4.34553C15.9995 4.6893 16.2037 5.15892 16.3122 5.96646C16.423 6.79056 16.4242 7.8762 16.4242 9.40098H17.5762ZM7.40024 2.42498C5.90803 2.42498 4.73093 2.42375 3.81222 2.54727C2.87695 2.67301 2.1237 2.93745 1.53021 3.53094L2.3448 4.34553C2.68857 4.00176 3.15819 3.79757 3.96572 3.689C4.78983 3.5782 5.87547 3.57698 7.40024 3.57698V2.42498ZM1.57624 9.40098C1.57624 7.8762 1.57747 6.79056 1.68827 5.96646C1.79684 5.15892 2.00103 4.6893 2.3448 4.34553L1.53021 3.53094C0.936721 4.12443 0.672282 4.87768 0.546538 5.81296C0.423021 6.73166 0.424244 7.90876 0.424244 9.40098H1.57624ZM7.40024 12.025H4.20024V13.177H7.40024V12.025ZM9.80024 12.025H9.40024V13.177H9.80024V12.025ZM1.00024 8.37698H17.0002V7.22498H1.00024V8.37698Z" fill="#8F9AB0"></path>
1302 </g>
1303 <defs>
1304 <clipPath id="clip0_2914_6421">
1305 <rect width="18" height="18" fill="white"></rect>
1306 </clipPath>
1307 </defs>
1308 </svg>
1309 </span>
1310 <span><?php echo wp_kses_post( $transaction_id_text ); ?>:</span>
1311 <span class="bp-bolt bp-transaction_id"><?php echo $transaction_id; ?></span>
1312 </p>
1313
1314 <button class="bp-transaction_data-copy_btn ">
1315 <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
1316 <path d="M15.0002 15.601C16.3257 15.601 17.4002 14.5265 17.4002 13.201V8.40098C17.4002 5.38399 17.4002 3.87549 16.463 2.93823C15.5257 2.00098 14.0172 2.00098 11.0002 2.00098H7.80024C6.47476 2.00098 5.40024 3.07549 5.40024 4.40098M10.2002 18.001H7.80024C5.5375 18.001 4.40613 18.001 3.70319 17.298C3.00024 16.5951 3.00024 15.4637 3.00024 13.201V9.20098C3.00024 6.93823 3.00024 5.80686 3.70319 5.10392C4.40613 4.40098 5.5375 4.40098 7.80024 4.40098H10.2002C12.463 4.40098 13.5944 4.40098 14.2973 5.10392C15.0002 5.80686 15.0002 6.93824 15.0002 9.20098V13.201C15.0002 15.4637 15.0002 16.5951 14.2973 17.298C13.5944 18.001 12.463 18.001 10.2002 18.001Z" stroke-width="1.152"></path>
1317 </svg>
1318 </button>
1319 </div>
1320 </div>
1321 <?php if( $show_details_button ): ?>
1322 <div class="bp-btn_wrapper">
1323 <a href="<?php echo $better_payment_form_error_details_page_url; ?>" target="_blank" class="bp-font_ibm bp-details_btn"><?php echo $details_button_text; ?></a>
1324 </div>
1325 <?php endif; ?>
1326 </div>
1327 </section>
1328 <?php
1329 }
1330
1331 /**
1332 * Remove args from url
1333 *
1334 * @since 0.0.1
1335 */
1336 public static function remove_arg() {
1337 ?>
1338 <script>
1339
1340 if(typeof params === 'undefined'){
1341 let params = '';
1342 }
1343
1344 params = new URLSearchParams(location.search);
1345
1346 if (params.has('better_payment_paypal_status') || params.has('better_payment_stripe_status') || params.has('better_payment_paystack_status')) {
1347 params.delete('better_payment_paypal_status');
1348 params.delete('better_payment_stripe_status');
1349 params.delete('better_payment_paystack_status');
1350 params.delete('better_payment_widget_id');
1351 params.delete('better_payment_stripe_id');
1352 params.delete('better_payment_paystack_id');
1353 params.delete('item_number');
1354 window.history.replaceState({}, '', `${location.pathname}?${params}`);
1355 }
1356 </script>
1357 <?php
1358 }
1359
1360 /**
1361 * Get failed transaction data
1362 *
1363 * @since 1.0.0
1364 */
1365 public static function get_failed_transaction_data( $order_id = '' ) {
1366 if ( empty( $order_id ) ) {
1367 return false;
1368 }
1369
1370 global $wpdb;
1371 $table = "{$wpdb->prefix}better_payment";
1372
1373 $result = $wpdb->get_row(
1374 $wpdb->prepare(
1375 "SELECT amount, currency, transaction_id, order_id, source FROM $table WHERE order_id=%s LIMIT 1",
1376 $order_id
1377 )
1378 );
1379
1380 if ( !empty( $result ) ) {
1381 $helper_obj = new Helper();
1382 return [
1383 'amount' => floatval( $result->amount ),
1384 'currency' => sanitize_text_field( $result->currency ),
1385 'currency_symbol' => $helper_obj->get_currency_symbol( $result->currency ),
1386 'transaction_id' => !empty( $result->transaction_id ) ? sanitize_text_field( $result->transaction_id ) : 'N/A',
1387 'method' => sanitize_text_field( $result->source ),
1388 ];
1389 }
1390
1391 return false;
1392 }
1393
1394 /**
1395 * Whitelist the e-mail related settings that better_email_notification() reads.
1396 *
1397 * PayPal confirms payments asynchronously through the IPN listener
1398 * (handle_paypal_ipn), where the live Elementor widget / block settings are NOT
1399 * available. Stripe and Paystack build their e-mail synchronously on the return
1400 * page, so they still have the full settings array in hand. To reach parity we
1401 * persist this subset into the transaction's form_fields_info at payment-create
1402 * time, and the IPN handler rebuilds the same $settings to produce an identical
1403 * e-mail body (heading, From / To / Transaction Summary / footer sections,
1404 * subjects, headers) and to honour the "Send Email" on/off toggle.
1405 *
1406 * @param mixed $settings Full widget / block / form settings (guarded: non-array input returns []).
1407 *
1408 * @return array Whitelisted e-mail settings (safe to serialize into form_fields_info).
1409 * @since 2.2.2
1410 */
1411 public static function extract_email_settings( $settings = [] ) {
1412 $email_settings = [];
1413
1414 if ( ! is_array( $settings ) ) {
1415 return $email_settings;
1416 }
1417
1418 foreach ( $settings as $key => $value ) {
1419 if (
1420 0 === strpos( $key, 'better_payment_email_' )
1421 || 0 === strpos( $key, 'better_payment_form_email_' )
1422 || 'better_payment_form_title' === $key
1423 ) {
1424 $email_settings[ $key ] = $value;
1425 }
1426 }
1427
1428 return $email_settings;
1429 }
1430
1431 /**
1432 * Email template
1433 *
1434 * @since 0.0.1
1435 */
1436 public static function better_email_notification($transaction_id, $customer_email, $settings, $referrer='Stripe', $form_fields_info='', $is_elementor_form = 0){
1437 //Send Email: customer and admin
1438 $better_txn_id = sanitize_text_field( $transaction_id );
1439 $better_cus_email = sanitize_email( $customer_email );
1440 $default_subject = sprintf(__('Better Payment transaction on %s', 'better-payment'), esc_html(get_option('blogname')));
1441 $better_payment_global_settings = DB::get_settings();
1442
1443 $better_payment_email_to = !empty($better_payment_global_settings['better_payment_settings_general_email_to']) ? $better_payment_global_settings['better_payment_settings_general_email_to'] : get_option( 'admin_email' );
1444
1445 $better_payment_email_subject = !empty($better_payment_global_settings['better_payment_settings_general_email_subject']) ? $better_payment_global_settings['better_payment_settings_general_email_subject'] : $default_subject;
1446 $better_payment_email_content = !empty($better_payment_global_settings['better_payment_settings_general_email_message_admin']) ? $better_payment_global_settings['better_payment_settings_general_email_message_admin'] : 'Email Content';
1447 $better_payment_email_from = !empty($better_payment_global_settings['better_payment_settings_general_email_from_email']) ? $better_payment_global_settings['better_payment_settings_general_email_from_email'] : '';
1448 $better_payment_email_from_name = !empty($better_payment_global_settings['better_payment_settings_general_email_from_name']) ? $better_payment_global_settings['better_payment_settings_general_email_from_name'] : '';
1449 $better_payment_email_reply_to = !empty($better_payment_global_settings['better_payment_settings_general_email_reply_to']) ? $better_payment_global_settings['better_payment_settings_general_email_reply_to'] : '';
1450 $better_payment_email_cc = !empty($better_payment_global_settings['better_payment_settings_general_email_cc']) ? $better_payment_global_settings['better_payment_settings_general_email_cc'] : '';
1451 $better_payment_email_bcc = !empty($better_payment_global_settings['better_payment_settings_general_email_bcc']) ? $better_payment_global_settings['better_payment_settings_general_email_bcc'] : '';
1452 $better_payment_email_content_type = !empty($better_payment_global_settings['better_payment_settings_general_email_send_as']) ? $better_payment_global_settings['better_payment_settings_general_email_send_as'] : 'html';
1453
1454 $better_payment_email_subject_customer = !empty($better_payment_global_settings['better_payment_settings_general_email_subject_customer']) ? $better_payment_global_settings['better_payment_settings_general_email_subject_customer'] : $default_subject;
1455 $better_payment_email_content_customer = !empty($better_payment_global_settings['better_payment_settings_general_email_message_customer']) ? $better_payment_global_settings['better_payment_settings_general_email_message_customer'] : 'Email Content';
1456 $better_payment_email_from_customer = !empty($better_payment_global_settings['better_payment_settings_general_email_from_email_customer']) ? $better_payment_global_settings['better_payment_settings_general_email_from_email_customer'] : '';
1457 $better_payment_email_from_name_customer = !empty($better_payment_global_settings['better_payment_settings_general_email_from_name_customer']) ? $better_payment_global_settings['better_payment_settings_general_email_from_name_customer'] : '';
1458 $better_payment_email_reply_to_customer = !empty($better_payment_global_settings['better_payment_settings_general_email_reply_to_customer']) ? $better_payment_global_settings['better_payment_settings_general_email_reply_to_customer'] : '';
1459 $better_payment_email_cc_customer = !empty($better_payment_global_settings['better_payment_settings_general_email_cc_customer']) ? $better_payment_global_settings['better_payment_settings_general_email_cc_customer'] : '';
1460 $better_payment_email_bcc_customer = !empty($better_payment_global_settings['better_payment_settings_general_email_bcc_customer']) ? $better_payment_global_settings['better_payment_settings_general_email_bcc_customer'] : '';
1461 $better_payment_email_content_type_customer = !empty($better_payment_global_settings['better_payment_settings_general_email_send_as_customer']) ? $better_payment_global_settings['better_payment_settings_general_email_send_as_customer'] : 'html';
1462 $args = [];
1463
1464 $args['email_form_name'] = ! empty( $settings['better_payment_form_title'] ) ? sanitize_text_field( $settings['better_payment_form_title'] ) : 'N/A';
1465 $args['email_content_heading_show'] = ! empty( $settings['better_payment_email_content_heading'] ) && 'yes' === $settings['better_payment_email_content_heading'] ? 1 : 0;
1466 $args['email_content_from_section_show'] = ! empty( $settings['better_payment_email_content_from_section'] ) && 'yes' === $settings['better_payment_email_content_from_section'] ? 1 : 0;
1467 $args['email_content_to_section_show'] = ! empty( $settings['better_payment_email_content_to_section'] ) && 'yes' === $settings['better_payment_email_content_to_section'] ? 1 : 0;
1468 $args['email_content_transaction_summary_show'] = ! empty( $settings['better_payment_email_content_transaction_summary'] ) && 'yes' === $settings['better_payment_email_content_transaction_summary'] ? 1 : 0;
1469 $args['email_content_footer_text_show'] = ! empty( $settings['better_payment_email_content_footer_text'] ) && 'yes' === $settings['better_payment_email_content_footer_text'] ? 1 : 0;
1470 $args['email_content_customer'] = ! empty( $settings['better_payment_email_content_customer'] ) ? $settings['better_payment_email_content_customer'] : '';
1471 $args['email_content_admin'] = ! empty( $settings['better_payment_email_content'] ) ? $settings['better_payment_email_content'] : '';
1472 $args['is_elementor_form'] = $is_elementor_form;
1473
1474 if ( ! empty( $settings['better_payment_email_content_greeting'] ) ) {
1475 $args['email_content_greeting'] = $settings['better_payment_email_content_greeting'];
1476 }
1477
1478 if ( ! empty( $settings['better_payment_form_email_logo']['url'] ) ) {
1479 $args['email_logo_url'] = $settings['better_payment_form_email_logo']['url'];
1480 }
1481
1482 if ( ! empty( $settings['better_payment_form_email_attachment']['id'] ) ) {
1483 $args['email_attachment_id'] = intval( $settings['better_payment_form_email_attachment']['id'] );
1484 $args['email_attachment_path'] = get_attached_file( $args['email_attachment_id'] );
1485 }
1486
1487 if ( ! empty( $settings['better_payment_form_email_attachment_pdf_show'] ) && ! empty( $settings['better_payment_form_email_attachment_pdf']['id'] ) ) {
1488 $args['email_attachment_id'] = intval( $settings['better_payment_form_email_attachment_pdf']['id'] );
1489 $args['email_attachment_path'] = get_attached_file( $args['email_attachment_id'] );
1490 }
1491
1492 if(isset($settings['better_payment_email_to'])){
1493 $better_payment_email_to = sanitize_email($settings['better_payment_email_to']);
1494 }
1495
1496 if(isset($settings['better_payment_email_subject'])){
1497 $better_payment_email_subject = sanitize_text_field($settings['better_payment_email_subject']);
1498 }
1499
1500 if(isset($settings['better_payment_email_subject_customer'])){
1501 $better_payment_email_subject_customer = sanitize_text_field($settings['better_payment_email_subject_customer']);
1502 }
1503
1504 if(isset($settings['better_payment_email_content_type'])){
1505 $better_payment_email_content_type = sanitize_text_field($settings['better_payment_email_content_type']);
1506 }
1507
1508 if(isset($settings['better_payment_email_content_type_customer'])){
1509 $better_payment_email_content_type_customer = sanitize_text_field($settings['better_payment_email_content_type_customer']);
1510 }
1511
1512 $args['email_content_type'] = $better_payment_email_content_type;
1513 $args['email_content_type_customer'] = $better_payment_email_content_type_customer;
1514
1515 $line_break = $better_payment_email_content_type == 'html' ? '<br>' : "\n";
1516 $line_break_customer = $better_payment_email_content_type_customer == 'html' ? '<br>' : "\n";
1517
1518 global $wpdb;
1519 $table = "{$wpdb->prefix}better_payment";
1520 $transaction_obj = $wpdb->get_row(
1521 $wpdb->prepare( "SELECT * FROM $table WHERE transaction_id=%s limit 1", sanitize_text_field( $transaction_id ) )
1522 );
1523
1524 if(isset($settings['better_payment_email_content'])){
1525 $better_payment_email_content = sanitize_text_field($settings['better_payment_email_content']);
1526 $better_payment_email_content = self::better_render_email_body( $better_payment_email_content, $form_fields_info, $line_break, 'admin', $transaction_obj, $args );
1527 } else if ( !empty($better_payment_email_content) ){
1528 $better_payment_email_content = self::better_render_email_body( $better_payment_email_content, $form_fields_info, $line_break, 'admin', $transaction_obj, $args );
1529 }
1530
1531 if(isset($settings['better_payment_email_content_customer'])){
1532 $better_payment_email_content_customer = sanitize_text_field($settings['better_payment_email_content_customer']);
1533 $better_payment_email_content_customer = self::better_render_email_body( $better_payment_email_content_customer, $form_fields_info, $line_break_customer, 'customer', $transaction_obj, $args );
1534 } else if ( !empty($better_payment_email_content_customer) ){
1535 $better_payment_email_content_customer = self::better_render_email_body( $better_payment_email_content_customer, $form_fields_info, $line_break, 'customer', $transaction_obj, $args ); //$line_break_customer not used : need to check type on parent method and add headers accordingly.
1536 }
1537
1538 if(isset($settings['better_payment_email_from'])){
1539 $better_payment_email_from = sanitize_email($settings['better_payment_email_from']);
1540 }
1541
1542 if(isset($settings['better_payment_email_from_name'])){
1543 $better_payment_email_from_name = sanitize_text_field($settings['better_payment_email_from_name']);
1544 }
1545
1546 if(isset($settings['better_payment_email_reply_to'])){
1547 $better_payment_email_reply_to = sanitize_email($settings['better_payment_email_reply_to']);
1548 }
1549
1550 if(isset($settings['better_payment_email_cc'])){
1551 $better_payment_email_cc = sanitize_email($settings['better_payment_email_cc']);
1552 }
1553
1554 if(isset($settings['better_payment_email_bcc'])){
1555 $better_payment_email_bcc = sanitize_email($settings['better_payment_email_bcc']);
1556 }
1557
1558 //Customer Email
1559 if(isset($settings['better_payment_email_from_customer'])){
1560 $better_payment_email_from_customer = sanitize_email($settings['better_payment_email_from_customer']);
1561 }
1562
1563 if(isset($settings['better_payment_email_from_name_customer'])){
1564 $better_payment_email_from_name_customer = sanitize_text_field($settings['better_payment_email_from_name_customer']);
1565 }
1566
1567 if(isset($settings['better_payment_email_reply_to_customer'])){
1568 $better_payment_email_reply_to_customer = sanitize_email($settings['better_payment_email_reply_to_customer']);
1569 }
1570
1571 if(isset($settings['better_payment_email_cc_customer'])){
1572 $better_payment_email_cc_customer = sanitize_email($settings['better_payment_email_cc_customer']);
1573 }
1574
1575 if(isset($settings['better_payment_email_bcc_customer'])){
1576 $better_payment_email_bcc_customer = sanitize_email($settings['better_payment_email_bcc_customer']);
1577 }
1578
1579 if($better_payment_email_content == ''){
1580 $better_payment_email_content = __('New better payment transaction! ', 'better-payment');
1581 }
1582
1583 if($better_payment_email_content_customer == ''){
1584 $better_payment_email_content_customer = __('New better payment transaction! ', 'better-payment');
1585 }
1586
1587 $better_payment_email_headers = [];
1588 $better_payment_email_headers_customer = [];
1589
1590 if($better_payment_email_content_type == 'html'){
1591 $better_payment_email_headers[] = 'Content-Type: text/html; charset=UTF-8';
1592 }
1593
1594 if($better_payment_email_content_type_customer == 'html'){
1595 $better_payment_email_headers_customer[] = 'Content-Type: text/html; charset=UTF-8';
1596 }
1597
1598 if($better_payment_email_from != ''){
1599 $better_payment_email_headers[] = "From: $better_payment_email_from_name <$better_payment_email_from>";
1600 }
1601
1602 if($better_payment_email_cc != ''){
1603 $better_payment_email_headers[] = "Cc: <$better_payment_email_cc>";
1604 }
1605
1606 if($better_payment_email_bcc != ''){
1607 $better_payment_email_headers[] = "BCc: $better_payment_email_bcc";
1608 }
1609
1610 if($better_payment_email_reply_to != ''){
1611 $better_payment_email_headers[] = "Reply-To: $better_payment_email_reply_to";
1612 }
1613
1614 //Customer Email
1615 if($better_payment_email_from_customer != ''){
1616 $better_payment_email_headers_customer[] = "From: $better_payment_email_from_name_customer <$better_payment_email_from_customer>";
1617 }
1618
1619 $form_fields_info_array = [];
1620
1621 if( ! empty( $form_fields_info ) ){
1622 $form_fields_info_array = maybe_unserialize( $form_fields_info );
1623 }
1624
1625 $form_fields_info_cus_email = ! empty( $form_fields_info_array['primary_email'] ) ? sanitize_email( $form_fields_info_array['primary_email'] ) : '';
1626
1627 if($better_payment_email_cc_customer != '' || ! empty( $form_fields_info_cus_email ) ){
1628 $better_payment_email_cc_customer_multiple = implode(',', [$form_fields_info_cus_email, $better_payment_email_cc_customer]);
1629 $better_payment_email_headers_customer[] = "Cc: $better_payment_email_cc_customer_multiple";
1630 }
1631
1632 if($better_payment_email_bcc_customer != ''){
1633 $better_payment_email_headers_customer[] = "BCc: $better_payment_email_bcc_customer";
1634 }
1635
1636 if($better_payment_email_reply_to_customer != ''){
1637 $better_payment_email_headers_customer[] = "Reply-To: $better_payment_email_reply_to_customer";
1638 }
1639
1640 $email_attachments = ! empty( $args['email_attachment_path'] ) ? [ $args['email_attachment_path'] ] : [];
1641 $allowed_extensions = array('jpg', 'jpeg', 'png', 'pdf');
1642
1643 $file_info = count( $email_attachments ) ? pathinfo($email_attachments[0]) : [];
1644 $file_extension = count( $file_info ) ? strtolower($file_info['extension']) : '';
1645
1646 if ( ! in_array( $file_extension, $allowed_extensions ) ){
1647 $email_attachments = [];
1648 }
1649
1650 self::send_better_email($better_cus_email, $better_payment_email_subject_customer, $better_payment_email_content_customer, $better_payment_email_headers_customer, $email_attachments); //customer mail
1651 self::send_better_email($better_payment_email_to, $better_payment_email_subject, $better_payment_email_content, $better_payment_email_headers); //admin mail
1652 }
1653
1654 /**
1655 * Sends an HTML email.
1656 *
1657 * @since 0.0.2
1658 *
1659 * @param string $to
1660 * @param string $subject
1661 * @param array $message
1662 *
1663 * @return bool false indicates mail sending failed while true doesn't gurantee that mail sent.
1664 * true just indicate script processed successfully without any errors.
1665 * Ref: https://developer.wordpress.org/reference/functions/wp_mail/
1666 */
1667 public static function send_better_email( $to, $subject, $message, $headers=[], $attachments = [] ) {
1668 $response = false;
1669
1670 $mailValidated = filter_var($to, FILTER_VALIDATE_EMAIL);
1671
1672 if ($mailValidated) {
1673 $response = wp_mail( $to, $subject, $message, $headers, $attachments );
1674 }
1675
1676 return $response;
1677 }
1678
1679 /**
1680 * Email body
1681 *
1682 *
1683 * @return string
1684 * @since 0.0.1
1685 */
1686 public static function better_render_email_body( $email_body, $form_fields_info, $line_break, $type = 'admin', $transaction_obj = null, $args = [] ) {
1687 $transaction_id =
1688 $currency =
1689 $payment_date =
1690 $customer_name =
1691 $customer_first_name =
1692 $customer_last_name =
1693 $amount = '';
1694 $helper_obj = new Helper();
1695
1696 if(null !== $transaction_obj && is_object($transaction_obj)){
1697 $transaction_id = $transaction_obj->transaction_id;
1698 $amount = $transaction_obj->amount;
1699 $status = $transaction_obj->status;
1700 $source = $transaction_obj->source;
1701 $currency = $transaction_obj->currency;
1702 $currency_symbol = $helper_obj->get_currency_symbol( esc_html($currency) );
1703 $payment_date = wp_date(get_option('date_format').' '.get_option('time_format'), strtotime($transaction_obj->payment_date));
1704 }
1705
1706 $is_empty_email_body = empty($email_body);
1707
1708 $email_body = do_shortcode( $email_body );
1709 $bp_form_fields_html_content = $email_body;
1710
1711 $bp_all_fields_shortcode = 'bp-all-fields';
1712 $referer_content_page_link = '#';
1713
1714 $field_text = __('Field', 'better-payment');
1715 $value_text = __('Entry', 'better-payment');
1716
1717 $form_fields_info_arr = maybe_unserialize($form_fields_info);
1718
1719 $email_logo_url = ! empty( $args['email_logo_url'] ) ? $args['email_logo_url'] : '';
1720 $email_content_greeting = ! empty( $args['email_content_greeting'] ) ? 1 : 0;
1721 $email_form_name = ! empty( $args['email_form_name'] ) ? $args['email_form_name'] : 'N/A';
1722
1723 $bp_form_fields_html_header = '<table style="margin-bottom: 20px; font-size: 14px; border-collapse: collapse; width: 100%;">';
1724
1725 $bp_form_fields_html_footer = "</table>";
1726
1727 $content = ''. $line_break;
1728
1729 foreach ( $form_fields_info_arr as $key => $field ) {
1730 if ( $key === 'is_payment_split_payment' ) {
1731 $is_payment_split_payment = 1;
1732 }
1733 //Hide few fields
1734 if(
1735 $key === 'referer_page_id' || $key === 'referer_widget_id' || $key === 'source' || $key === 'el_form_fields'
1736 || $key === 'is_woo_layout'
1737 || $key === 'is_payment_split_payment'
1738 || $key === 'split_payment_total_amount'
1739 || $key === 'split_payment_total_amount_price_id'
1740 || $key === 'split_payment_installment_price_id'
1741 ) {
1742 if($key === 'referer_page_id'){
1743 $referer_content_page_link = !empty($field) ? get_permalink( $field ) : $referer_content_page_link;
1744 }
1745
1746 continue;
1747 }
1748
1749 if($key === 'primary_first_name'){
1750 $key = 'first_name';
1751 $customer_first_name = $field;
1752 }
1753 if($key === 'primary_last_name'){
1754 $key = 'last_name';
1755 $customer_last_name = $field;
1756 }
1757
1758 if($key === 'primary_email'){
1759 $key = 'email';
1760 }
1761
1762 if($key === 'primary_payment_amount'){
1763 $key = 'payment_amount';
1764 }
1765
1766 $key_formatted = self::better_title_case($key);
1767 if($key_formatted === 'Amount'){
1768 $key_formatted = __('Paid', 'better-payment');
1769
1770 if ( ! empty( $is_payment_split_payment ) ) {
1771 $key_formatted = __('Product Price: ', 'better-payment');
1772 }
1773 }
1774
1775 $content .= "<tr>
1776 <td style='padding: 7px; border: 1px solid #666;'> <strong>$key_formatted</strong> </td>
1777 <td style='padding: 7px; border: 1px solid #666;'> $field </td>
1778 </tr>";
1779 }
1780
1781 if($customer_first_name) {
1782 $customer_name = $customer_first_name . ' ';
1783 }
1784 if($customer_last_name) {
1785 $customer_name .= $customer_last_name . ' ';
1786 }
1787
1788 $allowed_sources = ['paypal', 'stripe', 'paystack'];
1789 $source_image_url = BETTER_PAYMENT_ASSETS . '/img/stripe.png';
1790 $source_image_alt = 'Stripe';
1791
1792 if( in_array( strtolower( $transaction_obj->source ), $allowed_sources ) ){
1793 $source_image_url = strtolower( $transaction_obj->source ) == 'paypal' ? BETTER_PAYMENT_ASSETS . '/img/paypal.png' : BETTER_PAYMENT_ASSETS . "/img/{$transaction_obj->source}.png";
1794 $source_image_alt = strtolower( $transaction_obj->source ) == 'paypal' ? 'PayPal' : ucfirst( strtolower( $transaction_obj->source ) );
1795 }
1796
1797 $amount_quantity = ! empty( $form_fields_info_arr['amount_quantity'] ) ? intval( $form_fields_info_arr['amount_quantity'] ) : 1;
1798 // #ToDo Product id or ids with comma
1799 $woo_product_id = ! empty( $form_fields_info_arr['woo_product_id'] ) ? intval( $form_fields_info_arr['woo_product_id'] ) : 0;
1800 $fluentcart_product_id = ! empty( $form_fields_info_arr['fluentcart_product_id'] ) ? intval( $form_fields_info_arr['fluentcart_product_id'] ) : 0;
1801 $woo_product_ids = ! empty( $form_fields_info_arr['woo_product_ids'] ) ? maybe_unserialize( $form_fields_info_arr['woo_product_ids'] ) : [0];
1802 $fluentcart_product_ids = ! empty( $form_fields_info_arr['fluentcart_product_ids'] ) ? maybe_unserialize( $form_fields_info_arr['fluentcart_product_ids'] ) : [0];
1803 $product_name = '';
1804 $product_permalink = '';
1805 $product_image_src = '';
1806
1807 // Handle WooCommerce products
1808 if (function_exists('wc_get_product') && $woo_product_id ) {
1809 $bp_woocommerce_product = wc_get_product($woo_product_id);
1810 $product = $bp_woocommerce_product;
1811
1812 if ($product) {
1813 $product_name = $product->get_name();
1814 $product_permalink = get_permalink($product->get_id());
1815 $product_price = $product->get_price();
1816 $product_image_src_array = wp_get_attachment_image_src( get_post_thumbnail_id( $product->get_id() ), 'single-post-thumbnail' );
1817 $product_image_src = is_array( $product_image_src_array ) && count( $product_image_src_array ) ? $product_image_src_array[0] : '';
1818 }
1819 }
1820
1821 if (function_exists('fluentCart') && $fluentcart_product_id ) {
1822 try {
1823 $fluentcart_product = \FluentCart\App\Models\Product::query()
1824 ->with(['detail', 'variants'])
1825 ->find($fluentcart_product_id);
1826
1827 if ($fluentcart_product) {
1828 $product_name = $fluentcart_product->post_title;
1829 $product_permalink = get_permalink($fluentcart_product->ID);
1830
1831 // Get price from the first variant or detail
1832 $product_price = 0;
1833 if (!empty($fluentcart_product->variants) && count($fluentcart_product->variants) > 0) {
1834 $product_price = $fluentcart_product->variants[0]->item_price;
1835 } elseif (!empty($fluentcart_product->detail)) {
1836 $product_price = $fluentcart_product->detail->min_price;
1837 }
1838
1839 $product_image_src_array = wp_get_attachment_image_src( get_post_thumbnail_id( $fluentcart_product->ID ), 'single-post-thumbnail' );
1840 $product_image_src = is_array( $product_image_src_array ) && count( $product_image_src_array ) ? $product_image_src_array[0] : '';
1841 }
1842 } catch ( \Exception $e ) {
1843 // Handle error silently
1844 }
1845 }
1846
1847 $detailed_product_info = isset( $form_fields_info_arr['detailed_product_info'] ) ? maybe_unserialize( $form_fields_info_arr['detailed_product_info'] ) : [];
1848
1849 $all_data = [
1850 'amount' => $amount,
1851 'amount_quantity' => $amount_quantity,
1852 'woo_product_id' => $woo_product_id,
1853 'woo_product_ids' => $woo_product_ids,
1854 'fluentcart_product_id' => $fluentcart_product_id,
1855 'fluentcart_product_ids' => $fluentcart_product_ids,
1856 'product_name' => $product_name,
1857 'product_permalink' => $product_permalink,
1858 'product_image_src' => $product_image_src,
1859 'detailed_product_info' => $detailed_product_info,
1860 'amount_single' => $amount_quantity > 0 ? floatval( $amount / $amount_quantity ) : $amount,
1861 'currency' => ! empty( $currency ) ? $currency : '',
1862 'currency_symbol' => ! empty( $currency_symbol ) ? $currency_symbol : '',
1863 'payment_date_time' => ! empty( $payment_date ) ? $payment_date : '',
1864 'transaction_id' => ! empty( $transaction_id ) ? $transaction_id : '',
1865 'payment_date_only' => wp_date(get_option('date_format'), strtotime($transaction_obj->payment_date)),
1866 'status' => ucfirst( $status ),
1867 'form_fields_info_arr' => $form_fields_info_arr,
1868 // 'admin_name' => $admin_name,
1869 'customer_name' => $customer_name,
1870 'site_title' => get_bloginfo( 'name' ),
1871 'site_admin_email' => get_bloginfo( 'admin_email' ),
1872 'form_name' => $email_form_name,
1873 'payment_method' => ucfirst( $source ),
1874 'email_logo_url' => $email_logo_url,
1875 'source_image_url' => $source_image_url,
1876 'source_image_alt' => $source_image_alt,
1877 'view_transaction_link' => admin_url("admin.php?page=better-payment-transactions&action=view&id={$transaction_obj->id}"),
1878 'email_content_heading_show' => intval( $args['email_content_heading_show'] ),
1879 'email_content_from_section_show' => intval( $args['email_content_from_section_show'] ),
1880 'email_content_to_section_show' => intval( $args['email_content_to_section_show'] ),
1881 'email_content_transaction_summary_show' => intval( $args['email_content_transaction_summary_show'] ),
1882 'email_content_footer_text_show' => intval( $args['email_content_footer_text_show'] ),
1883 'email_content_customer' => wp_kses_post( $args['email_content_customer'] ),
1884 'email_content_admin' => wp_kses_post( $args['email_content_admin'] ),
1885 'is_elementor_form' => intval( $args['is_elementor_form'] ),
1886 'email_content_type' => $args['email_content_type'] ?? 'html',
1887 'email_content_type_customer' => $args['email_content_type_customer'] ?? 'html',
1888 ];
1889
1890 $bp_form_fields_html_body = $content;
1891 $bp_form_fields_html_content = $bp_form_fields_html_header . $bp_form_fields_html_body . $bp_form_fields_html_footer ;
1892
1893 //Replace shortcode with form fields info
1894 $email_body = str_replace( "[$bp_all_fields_shortcode]", $bp_form_fields_html_content, $email_body );
1895
1896 // Email V2
1897 // General content
1898 $form_name =
1899
1900 ob_start();
1901
1902 include BETTER_PAYMENT_ADMIN_VIEWS_PATH . "/template-email-notification.php";
1903
1904 $email_body = ob_get_contents();
1905 ob_end_clean();
1906
1907 return $email_body;
1908 }
1909
1910 /**
1911 * String helper method
1912 * helps to convert string to title case
1913 *
1914 * @return string
1915 * @since 0.0.1
1916 */
1917 public static function better_title_case($string){
1918 $string = str_replace('_',' ', $string);
1919 $string = ucwords($string);
1920 return $string;
1921 }
1922
1923 /**
1924 * Safe icon rendering for payment form messages
1925 *
1926 * Handles multiple icon types: FontAwesome, Dashicons, SVG from media library
1927 * Works with or without Elementor being active
1928 *
1929 * @param array $icon_settings Icon settings from block/widget controls
1930 * @param array $html_attributes Optional HTML attributes for the icon element
1931 *
1932 * @return void (outputs HTML directly)
1933 * @since 2.0.4
1934 */
1935 public static function render_icon( $icon_settings = [], $html_attributes = [] ) {
1936 // Bail if no settings provided
1937 if ( empty( $icon_settings ) || ! is_array( $icon_settings ) ) {
1938 return;
1939 }
1940
1941 // If value is empty, nothing to render
1942 if ( empty( $icon_settings['value'] ) ) {
1943 return;
1944 }
1945
1946 $library = ! empty( $icon_settings['library'] ) ? $icon_settings['library'] : '';
1947 $value = $icon_settings['value'];
1948
1949 // Try to use Elementor's Icons_Manager if it's available and loaded
1950 if ( did_action( 'elementor/loaded' ) && class_exists( '\Elementor\Icons_Manager' ) ) {
1951 try {
1952 \Elementor\Icons_Manager::render_icon( $icon_settings, $html_attributes );
1953 return;
1954 } catch ( \Exception $e ) {
1955 // If Elementor rendering fails, fall back to manual rendering
1956 }
1957 }
1958
1959 // Handle SVG (uploaded via media library)
1960 if ( $library === 'svg' ) {
1961 if ( is_array( $value ) && ! empty( $value['url'] ) ) {
1962 $svg_url = esc_url( $value['url'] );
1963 echo '<img src="' . $svg_url . '" alt="Icon" class="bp-icon-svg" />';
1964 return;
1965 }
1966 }
1967
1968 // Handle FontAwesome icons (all variants: fa, fas, far, fab, fal, fad, etc.)
1969 if ( in_array( $library, [ 'fa', 'fas', 'far', 'fab', 'fal', 'fad', 'fontawesome', 'font-awesome' ], true ) ) {
1970 if ( is_string( $value ) && ! empty( $value ) ) {
1971 // Build complete FontAwesome class
1972 $icon_class = 'fa';
1973
1974 // Add variant if specified (fas, far, fab, fal)
1975 if ( ! empty( $library ) && $library !== 'fa' && $library !== 'fontawesome' && $library !== 'font-awesome' ) {
1976 $icon_class = esc_attr( $library );
1977 } else {
1978 // Default to 'fas' if no variant specified
1979 $icon_class = 'fas';
1980 }
1981
1982 // Add the specific icon class
1983 $icon_class .= ' ' . esc_attr( $value );
1984
1985 $html_class = ! empty( $html_attributes['class'] ) ? ' ' . $html_attributes['class'] : '';
1986 $aria_hidden = isset( $html_attributes['aria-hidden'] ) ? ' aria-hidden="' . esc_attr( $html_attributes['aria-hidden'] ) . '"' : ' aria-hidden="true"';
1987
1988 echo '<i class="' . $icon_class . $html_class . '"' . $aria_hidden . '></i>';
1989 return;
1990 }
1991 }
1992
1993 // Handle Dashicons
1994 if ( in_array( $library, [ 'dashicons' ], true ) ) {
1995 if ( is_string( $value ) && ! empty( $value ) ) {
1996 $icon_class = 'dashicons dashicons-' . esc_attr( $value );
1997
1998 $html_class = ! empty( $html_attributes['class'] ) ? ' ' . $html_attributes['class'] : '';
1999 $aria_hidden = isset( $html_attributes['aria-hidden'] ) ? ' aria-hidden="' . esc_attr( $html_attributes['aria-hidden'] ) . '"' : ' aria-hidden="true"';
2000
2001 echo '<span class="' . $icon_class . $html_class . '"' . $aria_hidden . '></span>';
2002 return;
2003 }
2004 }
2005
2006 // Handle custom CSS classes (like 'bp-icon bp-check-circle')
2007 // These are custom icon fonts or CSS-based icons defined by the theme/plugin
2008 if ( 'custom' === $library ) {
2009 if ( is_string( $value ) && ! empty( $value ) ) {
2010 $html_class = ! empty( $html_attributes['class'] ) ? ' ' . $html_attributes['class'] : '';
2011 $aria_hidden = isset( $html_attributes['aria-hidden'] ) ? ' aria-hidden="' . esc_attr( $html_attributes['aria-hidden'] ) . '"' : ' aria-hidden="true"';
2012
2013 echo '<i class="' . esc_attr( $value ) . $html_class . '"' . $aria_hidden . '></i>';
2014 return;
2015 }
2016 }
2017
2018 // If value is a plain string (might be a FontAwesome class without library specified)
2019 if ( is_string( $value ) && ! empty( $value ) && empty( $library ) ) {
2020 // Check if it looks like a FontAwesome class
2021 if ( strpos( $value, 'fa' ) !== false || strpos( $value, 'fa-' ) !== false ) {
2022 $icon_class = 'fas ' . esc_attr( $value );
2023
2024 $html_class = ! empty( $html_attributes['class'] ) ? ' ' . $html_attributes['class'] : '';
2025 $aria_hidden = isset( $html_attributes['aria-hidden'] ) ? ' aria-hidden="' . esc_attr( $html_attributes['aria-hidden'] ) . '"' : ' aria-hidden="true"';
2026
2027 echo '<i class="' . $icon_class . $html_class . '"' . $aria_hidden . '></i>';
2028 return;
2029 }
2030
2031 // Check if it looks like a dashicon
2032 if ( strpos( $value, 'dashicon' ) !== false || strpos( $value, 'dashicons-' ) !== false ) {
2033 $icon_class = 'dashicons ' . esc_attr( $value );
2034
2035 $html_class = ! empty( $html_attributes['class'] ) ? ' ' . $html_attributes['class'] : '';
2036 $aria_hidden = isset( $html_attributes['aria-hidden'] ) ? ' aria-hidden="' . esc_attr( $html_attributes['aria-hidden'] ) . '"' : ' aria-hidden="true"';
2037
2038 echo '<span class="' . $icon_class . $html_class . '"' . $aria_hidden . '></span>';
2039 return;
2040 }
2041 }
2042 }
2043 }
2044