PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.4.1
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.4.1
2.4.1 2.4.0 2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.2 2.2.1 2.2.0 2.1.2 2.1.1 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 All 68 releases
← All changes | includes/Classes/Actions.php +34 -414 2.3.4 → 2.4.1 View file →
@@ -79,11 +79,8 @@
79 79 $woo_product_id = !empty($el_settings["better_payment_form_woocommerce_product_id"]) ? intval($el_settings["better_payment_form_woocommerce_product_id"]) : 0;
80 80 $woo_product_ids = !empty($el_settings["better_payment_form_woocommerce_product_ids"]) ? $el_settings["better_payment_form_woocommerce_product_ids"] : [0];
81 81 $fluentcart_product_id = !empty($el_settings["better_payment_form_fluentcart_product_id"]) ? intval($el_settings["better_payment_form_fluentcart_product_id"]) : 0;
82 82 $fluentcart_product_ids = !empty($el_settings["better_payment_form_fluentcart_product_ids"]) ? $el_settings["better_payment_form_fluentcart_product_ids"] : [0];
83 - $is_layout_6 = ! empty( $el_settings[ 'better_payment_form_layout' ] ) && 'layout-6-pro' === $el_settings[ 'better_payment_form_layout' ];
84 - $is_fluentcart_layout = $is_layout_6 && ! empty( $el_settings[ 'better_payment_form_layout_6_ecommerce_platform' ] ) && 'fluentcart' === $el_settings[ 'better_payment_form_layout_6_ecommerce_platform' ];
85 - $is_woo_layout = $is_layout_6 && ( empty( $el_settings[ 'better_payment_form_layout_6_ecommerce_platform' ] ) || 'woocommerce' === $el_settings[ 'better_payment_form_layout_6_ecommerce_platform' ] );
86 83
87 84 // Amount, quantity, currency and campaign come from the stored settings, never
88 85 // from the request — see PaymentRequestGuard.
89 86 $guarded = ( new PaymentRequestGuard() )->resolve_form_payment(
@@ -137,16 +134,19 @@
137 134 'fluentcart_product_id' => $fluentcart_product_id,
138 135 'fluentcart_product_ids' => maybe_serialize( $fluentcart_product_ids ),
139 136 'source' => 'paypal',
140 137 'amount_quantity' => ! empty( $primary_payment_amount_quantity ) ? intval( $primary_payment_amount_quantity ) : '',
141 - 'is_woo_layout' => $is_woo_layout,
142 - 'is_fluentcart_layout' => $is_fluentcart_layout,
143 138 'detailed_product_info' => maybe_serialize( $detailed_product_info ),
144 139 'paypal_business_email' => sanitize_email( $el_settings['better_payment_paypal_business_email'] ),
140 + // The PayPal environment the order was sent to; the IPN must come from the same one.
141 + 'paypal_mode' => 'yes' === $el_settings['better_payment_paypal_live_mode'] ? 'live' : 'sandbox',
145 142 // Persisted so the async PayPal IPN can rebuild the e-mail body + on/off toggle.
146 143 'email_settings' => Handler::extract_email_settings( $el_settings ),
147 144 ];
148 145
146 + // Fields only Pro's forms store (layout 6's platform flags) — see PaymentEntitlement.
147 + $better_form_fields = PaymentEntitlement::transaction_fields( $better_form_fields, $el_settings );
148 +
149 149 $better_form_fields = array_merge( $better_form_fields, $this->fetch_better_form_fields($el_settings, $_POST) );
150 150
151 151 if ( !empty( $better_form_fields[ 'primary_first_name' ] ) ) {
152 152 $request_data[ 'primary_first_name' ] = sanitize_text_field( $better_form_fields[ 'primary_first_name' ] );
@@ -216,19 +216,13 @@
216 216 if ( empty( $el_settings ) ) {
217 217 wp_send_json_error( esc_html(__( 'Setting Data is missing', 'better-payment' )) );
218 218 }
219 219
220 - $is_layout_6 = ! empty( $el_settings[ 'better_payment_form_layout' ] ) && 'layout-6-pro' === $el_settings[ 'better_payment_form_layout' ];
221 - $is_fluentcart_layout = $is_layout_6 && ! empty( $el_settings[ 'better_payment_form_layout_6_ecommerce_platform' ] ) && 'fluentcart' === $el_settings[ 'better_payment_form_layout_6_ecommerce_platform' ];
222 - $is_woo_layout = $is_layout_6 && ( empty( $el_settings[ 'better_payment_form_layout_6_ecommerce_platform' ] ) || 'woocommerce' === $el_settings[ 'better_payment_form_layout_6_ecommerce_platform' ] );
223 -
224 220 $better_payment_keys = [
225 221 'public_key' => 'yes' === sanitize_text_field( $el_settings[ 'better_payment_stripe_live_mode' ] ) ? sanitize_text_field( $el_settings[ 'better_payment_stripe_public_key_live' ] ) : sanitize_text_field( $el_settings[ 'better_payment_stripe_public_key' ] ),
226 222 'secret_key' => 'yes' === sanitize_text_field( $el_settings[ 'better_payment_stripe_live_mode' ] ) ? sanitize_text_field( $el_settings[ 'better_payment_stripe_secret_key_live' ] ) : sanitize_text_field( $el_settings[ 'better_payment_stripe_secret_key' ] ),
227 223 ];
228 224
229 - $is_payment_split_payment = ! empty( $el_settings["better_payment_form_payment_type"] ) && 'split-payment' === $el_settings["better_payment_form_payment_type"];
230 -
231 225 // Amount, quantity, currency, price ids, coupon and campaign come from the stored
232 226 // settings, never from the request — see PaymentRequestGuard.
233 227 $guard = new PaymentRequestGuard();
234 228 $guarded = $guard->resolve_form_payment(
@@ -318,39 +312,8 @@
318 312 if ( ! empty( $discount ) ) {
319 313 $request['discounts'] = [ $discount ];
320 314 }
321 315
322 - $is_payment_recurring = $guarded['is_recurring'];
323 - $recurring_price_id = $guarded['recurring_price_id'];
324 - $mixed_payment_selection_data = $this->get_mixed_payment_selection_data( $el_settings, $_POST['fields'] );
325 -
326 - if ( is_wp_error( $mixed_payment_selection_data ) ) {
327 - wp_send_json_error( $mixed_payment_selection_data->get_error_message() );
328 - }
329 -
330 - $is_payment_mixed_recurring = ! empty( $mixed_payment_selection_data['is_mixed_recurring'] );
331 - $mixed_dynamic_recurring_price = [];
332 -
333 - if ( $is_payment_mixed_recurring ) {
334 - $mixed_dynamic_recurring_price = $this->create_mixed_recurring_stripe_price(
335 - $header_info,
336 - $amount,
337 - $el_settings_currency,
338 - $order_id,
339 - $widget_id,
340 - $mixed_payment_selection_data,
341 - $el_settings
342 - );
343 -
344 - if ( is_wp_error( $mixed_dynamic_recurring_price ) ) {
345 - wp_send_json_error( $mixed_dynamic_recurring_price->get_error_message() );
346 - }
347 -
348 - // Reuse existing recurring checkout flow with a runtime-generated recurring Stripe Price.
349 - $is_payment_recurring = true;
350 - $recurring_price_id = ! empty( $mixed_dynamic_recurring_price['price_id'] ) ? sanitize_text_field( $mixed_dynamic_recurring_price['price_id'] ) : '';
351 - }
352 -
353 316 $product_ids = [
354 317 'woo_product_ids' => $woo_product_ids,
355 318 'fluentcart_product_ids' => $fluentcart_product_ids,
356 319 ];
@@ -367,29 +330,17 @@
367 330 'fluentcart_product_id' => $fluentcart_product_id,
368 331 'fluentcart_product_ids' => maybe_serialize( $fluentcart_product_ids ),
369 332 'source' => 'stripe',
370 333 'amount_quantity' => ! empty( $amount_quantity ) ? $amount_quantity : '',
371 - 'is_woo_layout' => $is_woo_layout,
372 - 'is_fluentcart_layout' => $is_fluentcart_layout,
373 334 'stripe_coupon_code' => $coupon_code,
374 335 'detailed_product_info' => maybe_serialize( $detailed_product_info ),
375 336 ];
376 337
338 + // Fields only Pro's forms store (layout 6's platform flags) — see PaymentEntitlement.
339 + $better_form_fields = PaymentEntitlement::transaction_fields( $better_form_fields, $el_settings );
340 +
377 341 $better_form_fields = array_merge( $better_form_fields, $this->fetch_better_form_fields($el_settings, $_POST['fields']) );
378 342
379 - if ( ! empty( $mixed_payment_selection_data['is_payment_mixed'] ) ) {
380 - $better_form_fields['is_payment_mixed'] = 1;
381 - $better_form_fields['mixed_payment_selected_type'] = ! empty( $mixed_payment_selection_data['selected_type'] ) ? sanitize_text_field( $mixed_payment_selection_data['selected_type'] ) : 'one-time';
382 - }
383 -
384 - if ( $is_payment_mixed_recurring ) {
385 - $better_form_fields['mixed_payment_selected_type'] = 'recurring';
386 - $better_form_fields['mixed_payment_recurring_interval_count'] = ! empty( $mixed_payment_selection_data['interval_count'] ) ? absint( $mixed_payment_selection_data['interval_count'] ) : 1;
387 - $better_form_fields['mixed_payment_recurring_interval'] = ! empty( $mixed_payment_selection_data['interval'] ) ? sanitize_text_field( $mixed_payment_selection_data['interval'] ) : 'month';
388 - $better_form_fields['mixed_payment_dynamic_price_id'] = ! empty( $mixed_dynamic_recurring_price['price_id'] ) ? sanitize_text_field( $mixed_dynamic_recurring_price['price_id'] ) : '';
389 - $better_form_fields['mixed_payment_dynamic_product_id'] = ! empty( $mixed_dynamic_recurring_price['product_id'] ) ? sanitize_text_field( $mixed_dynamic_recurring_price['product_id'] ) : '';
390 - }
391 -
392 343 if ( !empty( $better_form_fields['primary_first_name'] ) ) {
393 344 $request[ 'customer_name' ] = $better_form_fields['primary_first_name'];
394 345 }
395 346
@@ -409,41 +360,34 @@
409 360 $request[ 'payment_intent_data' ][ 'metadata' ][ 'customer_email' ] = $request[ 'customer_email' ];
410 361
411 362 }
412 363
413 - if ( $is_payment_split_payment ) {
414 - $installment_price_id = $guarded['installment_price_id'];
415 - $split_payment_installments_data = ! empty( $el_settings['better_payment_split_installment_price_ids'] ) ? $el_settings['better_payment_split_installment_price_ids'] : [];
364 + // Recurring, split-payment and mixed checkouts are Pro's: refused without Pro, built by
365 + // Pro (or, for an older Pro, by Lite's transitional copy) — see PaymentEntitlement.
366 + if ( PaymentEntitlement::is_pro_form( $el_settings ) ) {
367 + $checkout = PaymentEntitlement::stripe_checkout(
368 + [
369 + 'request' => $request,
370 + 'form_fields' => $better_form_fields,
371 + ],
372 + [
373 + 'settings' => $el_settings,
374 + 'fields' => isset( $_POST['fields'] ) && is_array( $_POST['fields'] ) ? wp_unslash( $_POST['fields'] ) : [],
375 + 'guarded' => $guarded,
376 + 'order_id' => $order_id,
377 + 'widget_id' => $widget_id,
378 + 'header_info' => $header_info,
379 + ]
380 + );
416 381
417 - if ( is_array( $split_payment_installments_data ) && count( $split_payment_installments_data ) ){
418 - foreach( $split_payment_installments_data as $split_payment_installment_data ){
419 - $item_price_id = ! empty( $split_payment_installment_data[ 'better_payment_split_installment_price_id' ] ) ? sanitize_text_field( $split_payment_installment_data[ 'better_payment_split_installment_price_id' ] ) : '';
420 -
421 - if ( $installment_price_id === $item_price_id ) {
422 - $installment_price_iteration = ! empty( $split_payment_installment_data[ 'better_payment_split_installment_iteration' ] ) ? sanitize_text_field( $split_payment_installment_data[ 'better_payment_split_installment_iteration' ] ) : '';
423 - break;
424 - }
425 - }
382 + if ( is_wp_error( $checkout ) ) {
383 + wp_send_json_error( esc_html( $checkout->get_error_message() ) );
426 384 }
427 385
428 - $better_form_fields['is_payment_split_payment'] = 1;
429 - $better_form_fields['split_payment_installment_price_id'] = $installment_price_id;
430 - $better_form_fields['split_payment_total_amount_price_id'] = $recurring_price_id;
431 - $better_form_fields['split_payment_installment_iteration'] = ( !empty( $installment_price_id ) && !empty( $installment_price_iteration ) ) ? $installment_price_iteration : 1;
432 - $better_form_fields['split_payment_total_amount'] = $amount * $better_form_fields['split_payment_installment_iteration'];
386 + $request = $checkout['request'];
387 + $better_form_fields = $checkout['form_fields'];
433 388 }
434 -
435 - if ( $is_payment_recurring || $is_payment_split_payment ) {
436 - $request['mode'] = 'subscription';
437 - unset($request['line_items'][0]);
438 - unset($request['payment_intent_data']);
439 - $request['line_items'][0]['price'] = $is_payment_split_payment && ! empty( $installment_price_id ) ? $installment_price_id : $recurring_price_id;
440 - $request['line_items'][0]['quantity'] = 1;
441 389
442 - $better_form_fields['mode'] = $request['mode'];
443 - $better_form_fields['recurring_price_id'] = $request['line_items'][0]['price'];
444 - }
445 -
446 390 #ToDo: Need to prefill name field on stripe checkout
447 391 $response = wp_safe_remote_post(
448 392 'https://api.stripe.com/v1/checkout/sessions',
449 393 array(
@@ -492,314 +436,8 @@
492 436 }
493 437 }
494 438
495 439 /**
496 - * Parse mixed payment selection payload.
497 - *
498 - * @param array $el_settings
499 - * @param array $fields
500 - *
501 - * @return array|\WP_Error
502 - */
503 - private function get_mixed_payment_selection_data( $el_settings = [], $fields = [] ) {
504 - $selection_data = [
505 - 'is_payment_mixed' => 0,
506 - 'selected_type' => 'one-time',
507 - 'is_mixed_recurring' => 0,
508 - 'interval_count' => 1,
509 - 'interval' => 'month',
510 - ];
511 -
512 - $is_payment_mixed = ! empty( $el_settings['better_payment_form_payment_type'] ) && 'mixed' === sanitize_text_field( $el_settings['better_payment_form_payment_type'] );
513 - if ( ! $is_payment_mixed ) {
514 - return $selection_data;
515 - }
516 -
517 - $selection_data['is_payment_mixed'] = 1;
518 - $selected_type = ! empty( $fields['better_payment_mixed_selected_type'] ) ? sanitize_text_field( $fields['better_payment_mixed_selected_type'] ) : '';
519 - if ( ! empty( $fields['payment_type'] ) && in_array( $fields['payment_type'], [ 'one-time', 'recurring' ], true ) ) {
520 - $selected_type = sanitize_text_field( $fields['payment_type'] );
521 - }
522 -
523 - $selected_type = in_array( $selected_type, [ 'one-time', 'recurring' ], true ) ? $selected_type : 'one-time';
524 - $selection_data['selected_type'] = $selected_type;
525 - $selection_data['is_mixed_recurring'] = 'recurring' === $selected_type ? 1 : 0;
526 -
527 - if ( ! $selection_data['is_mixed_recurring'] ) {
528 - return $selection_data;
529 - }
530 -
531 - $allowed_intervals = [ 'day', 'week', 'month', 'year' ];
532 - $mixed_interval_value = ! empty( $fields['better_payment_mixed_interval'] ) ? sanitize_text_field( $fields['better_payment_mixed_interval'] ) : '';
533 -
534 - if ( empty( $mixed_interval_value ) ) {
535 - $interval_type = ! empty( $fields['better_payment_mixed_interval_type'] ) ? sanitize_key( $fields['better_payment_mixed_interval_type'] ) : '';
536 -
537 - if ( empty( $interval_type ) ) {
538 - if ( ! empty( $fields['better_payment_mixed_interval_day'] ) ) {
539 - $interval_type = 'day';
540 - } elseif ( ! empty( $fields['better_payment_mixed_interval_week'] ) ) {
541 - $interval_type = 'week';
542 - } elseif ( ! empty( $fields['better_payment_mixed_interval_month'] ) ) {
543 - $interval_type = 'month';
544 - } elseif ( ! empty( $fields['better_payment_mixed_interval_year'] ) ) {
545 - $interval_type = 'year';
546 - }
547 - }
548 -
549 - $interval_type = in_array( $interval_type, $allowed_intervals, true ) ? $interval_type : '';
550 - $interval_count = 0;
551 -
552 - switch ( $interval_type ) {
553 - case 'day':
554 - $interval_count = ! empty( $fields['better_payment_mixed_interval_day'] ) ? absint( $fields['better_payment_mixed_interval_day'] ) : 0;
555 - break;
556 - case 'week':
557 - $interval_count = ! empty( $fields['better_payment_mixed_interval_week'] ) ? absint( $fields['better_payment_mixed_interval_week'] ) : 0;
558 - break;
559 - case 'year':
560 - $interval_count = ! empty( $fields['better_payment_mixed_interval_year'] ) ? absint( $fields['better_payment_mixed_interval_year'] ) : 0;
561 - break;
562 - case 'month':
563 - default:
564 - $interval_count = ! empty( $fields['better_payment_mixed_interval_month'] ) ? absint( $fields['better_payment_mixed_interval_month'] ) : 0;
565 - break;
566 - }
567 -
568 - if ( ! empty( $interval_count ) && ! empty( $interval_type ) ) {
569 - $mixed_interval_value = "{$interval_count}|{$interval_type}";
570 - }
571 - }
572 -
573 - if ( empty( $mixed_interval_value ) || false === strpos( $mixed_interval_value, '|' ) ) {
574 - return new \WP_Error( 'mixed_interval_missing', __( 'Recurring time period is required for mixed recurring payment.', 'better-payment' ) );
575 - }
576 -
577 - list( $interval_count_raw, $interval_raw ) = array_pad( explode( '|', $mixed_interval_value, 2 ), 2, '' );
578 - $interval_count = absint( $interval_count_raw );
579 - $interval = sanitize_key( $interval_raw );
580 -
581 - if ( empty( $interval_count ) || ! in_array( $interval, $allowed_intervals, true ) ) {
582 - return new \WP_Error( 'mixed_interval_invalid', __( 'Invalid recurring time period selected for mixed payment.', 'better-payment' ) );
583 - }
584 -
585 - // The period decides how often the amount is charged, so — like the amount — it must
586 - // be one the form offers: a "$10 monthly" plan must not become "$10 yearly".
587 - if ( ! in_array( "{$interval_count}|{$interval}", self::mixed_interval_options( $el_settings ), true ) ) {
588 - return new \WP_Error( 'mixed_interval_invalid', __( 'Invalid recurring time period selected for mixed payment.', 'better-payment' ) );
589 - }
590 -
591 - $selection_data['interval_count'] = $interval_count;
592 - $selection_data['interval'] = $interval;
593 -
594 - return $selection_data;
595 - }
596 -
597 - /**
598 - * Create a runtime Stripe recurring Price (and Product) for mixed recurring payments.
599 - *
600 - * @param array $header_info
601 - * @param float $amount
602 - * @param string $currency
603 - * @param string $order_id
604 - * @param string $widget_id
605 - * @param array $selection_data
606 - * @param array $el_settings
607 - *
608 - * @return array|\WP_Error
609 - */
610 - private function create_mixed_recurring_stripe_price( $header_info = [], $amount = 0, $currency = '', $order_id = '', $widget_id = '', $selection_data = [], $el_settings = [] ) {
611 - $unit_amount = (int) round( floatval( $amount ) * 100 );
612 - if ( $unit_amount < 1 ) {
613 - return new \WP_Error( 'mixed_amount_invalid', __( 'Payment Amount field is required!', 'better-payment' ) );
614 - }
615 -
616 - $product_id = ! empty( $el_settings['better_payment_one_time_recurring_product_id'] )
617 - ? sanitize_text_field( $el_settings['better_payment_one_time_recurring_product_id'] )
618 - : '';
619 -
620 - if ( empty( $product_id ) ) {
621 - return new \WP_Error(
622 - 'mixed_product_id_missing',
623 - __( 'Stripe Product ID is required for mixed recurring payments. Please provide it in the widget settings.', 'better-payment' )
624 - );
625 - }
626 -
627 - $interval = ! empty( $selection_data['interval'] ) ? sanitize_key( $selection_data['interval'] ) : 'month';
628 - $interval_count = ! empty( $selection_data['interval_count'] ) ? absint( $selection_data['interval_count'] ) : 1;
629 - $currency = ! empty( $currency ) ? strtolower( sanitize_text_field( $currency ) ) : 'usd';
630 -
631 - $form_name = ! empty( $el_settings['better_payment_form_title'] ) ? sanitize_text_field( $el_settings['better_payment_form_title'] ) : __( 'Better Payment', 'better-payment' );
632 -
633 - $interval_label = $interval;
634 - if ( $interval_count > 1 ) {
635 - $interval_label = $interval_count . ' ' . $interval . 's';
636 - }
637 -
638 - // One Price per plan, not one per request. This runs for anonymous visitors, and a
639 - // Price created on every checkout attempt piled objects into the merchant's Stripe
640 - // account without limit. A plan is reused through a lookup key derived from exactly
641 - // what it charges, so the Price carries no per-order data (the order id lives on the
642 - // Checkout Session instead).
643 - $lookup_key = self::mixed_price_lookup_key( $product_id, $currency, $unit_amount, $interval, $interval_count );
644 - $existing = $this->find_mixed_recurring_stripe_price( $header_info, $lookup_key, $product_id, $currency, $unit_amount, $interval, $interval_count );
645 -
646 - if ( '' !== $existing ) {
647 - return [
648 - 'price_id' => $existing,
649 - 'product_id' => $product_id,
650 - ];
651 - }
652 -
653 - $price_request = [
654 - 'currency' => $currency,
655 - 'unit_amount' => $unit_amount,
656 - 'recurring' => [
657 - 'interval' => $interval,
658 - 'interval_count' => $interval_count,
659 - ],
660 - 'product' => $product_id,
661 - 'lookup_key' => $lookup_key,
662 - 'nickname' => sprintf( '%s - %s', $form_name, $interval_label ),
663 - 'metadata' => [
664 - 'is_mixed_payment' => '1',
665 - 'mixed_interval' => $interval,
666 - 'mixed_interval_count' => strval( $interval_count ),
667 - ],
668 - ];
669 -
670 - $response = wp_safe_remote_post(
671 - 'https://api.stripe.com/v1/prices',
672 - [
673 - 'method' => 'POST',
674 - 'headers' => $header_info,
675 - 'body' => $price_request,
676 - 'timeout' => 70,
677 - ]
678 - );
679 -
680 - if ( is_wp_error( $response ) ) {
681 - return new \WP_Error( 'mixed_price_request_failed', $response->get_error_message() );
682 - }
683 -
684 - $response_body = wp_remote_retrieve_body( $response );
685 - $response_ar = ! empty( $response_body ) ? json_decode( $response_body ) : null;
686 - $error_message = ! empty( $response_ar->error->message ) ? sanitize_text_field( $response_ar->error->message ) : __( 'Unable to create recurring Stripe price for mixed payment.', 'better-payment' );
687 -
688 - if ( empty( $response_ar ) || empty( $response_ar->id ) ) {
689 - // A concurrent request may have claimed the lookup key a moment earlier.
690 - $existing = $this->find_mixed_recurring_stripe_price( $header_info, $lookup_key, $product_id, $currency, $unit_amount, $interval, $interval_count );
691 -
692 - if ( '' !== $existing ) {
693 - return [
694 - 'price_id' => $existing,
695 - 'product_id' => $product_id,
696 - ];
697 - }
698 -
699 - return new \WP_Error( 'mixed_price_create_failed', $error_message );
700 - }
701 -
702 - return [
703 - 'price_id' => sanitize_text_field( $response_ar->id ),
704 - 'product_id' => $product_id,
705 - ];
706 - }
707 -
708 - /**
709 - * The periods a mixed-payment form offers, as `{count}|{interval}` — the exact values
710 - * layouts 4/5 (Pro) render into the "Recurring time period" select.
711 - *
712 - * @param array $el_settings Form settings.
713 - * @return string[]
714 - */
715 - private static function mixed_interval_options( $el_settings = [] ) {
716 - $items = ! empty( $el_settings['better_payment_mixed_installment_options'] ) && is_array( $el_settings['better_payment_mixed_installment_options'] )
717 - ? $el_settings['better_payment_mixed_installment_options']
718 - : [];
719 - $options = [];
720 -
721 - foreach ( $items as $option ) {
722 - if ( ! is_array( $option ) ) {
723 - continue;
724 - }
725 -
726 - $type = ! empty( $option['better_payment_mixed_interval_type'] ) ? sanitize_key( $option['better_payment_mixed_interval_type'] ) : 'month';
727 - $type = in_array( $type, [ 'day', 'week', 'month', 'year' ], true ) ? $type : 'month';
728 -
729 - $count = ! empty( $option[ "better_payment_mixed_interval_{$type}" ] ) ? absint( $option[ "better_payment_mixed_interval_{$type}" ] ) : 0;
730 -
731 - if ( empty( $count ) && ! empty( $option['better_payment_mixed_interval'] ) ) {
732 - $count = absint( $option['better_payment_mixed_interval'] );
733 - }
734 -
735 - $options[] = ( $count ? $count : 1 ) . '|' . $type;
736 - }
737 -
738 - return $options;
739 - }
740 -
741 - /**
742 - * Stripe lookup key for a mixed recurring plan.
743 - *
744 - * @param string $product_id Stripe product id.
745 - * @param string $currency Lower-case currency.
746 - * @param int $unit_amount Minor units.
747 - * @param string $interval day|week|month|year.
748 - * @param int $interval_count Interval count.
749 - * @return string
750 - */
751 - private static function mixed_price_lookup_key( $product_id, $currency, $unit_amount, $interval, $interval_count ) {
752 - return 'bp_mixed_' . md5( implode( '|', [ $product_id, $currency, (int) $unit_amount, $interval, (int) $interval_count ] ) );
753 - }
754 -
755 - /**
756 - * An active Stripe Price already created for this exact plan, or ''.
757 - *
758 - * The lookup key is ours, but a Price is only reused while it still charges exactly this
759 - * plan — anything edited or mismatched in Stripe is ignored and a fresh one is created.
760 - *
761 - * @return string Price id, or '' when there is none to reuse.
762 - */
763 - private function find_mixed_recurring_stripe_price( $header_info, $lookup_key, $product_id, $currency, $unit_amount, $interval, $interval_count ) {
764 - $response = wp_safe_remote_get(
765 - 'https://api.stripe.com/v1/prices?' . http_build_query(
766 - [
767 - 'lookup_keys' => [ $lookup_key ],
768 - 'active' => 'true',
769 - 'limit' => 1,
770 - ]
771 - ),
772 - [
773 - 'headers' => $header_info,
774 - 'timeout' => 70,
775 - ]
776 - );
777 -
778 - if ( is_wp_error( $response ) ) {
779 - return '';
780 - }
781 -
782 - $body = json_decode( wp_remote_retrieve_body( $response ) );
783 - $price = isset( $body->data ) && is_array( $body->data ) && ! empty( $body->data[0] ) ? $body->data[0] : null;
784 -
785 - if ( ! is_object( $price ) || empty( $price->id ) ) {
786 - return '';
787 - }
788 -
789 - $price_product = isset( $price->product ) && is_object( $price->product ) ? ( isset( $price->product->id ) ? $price->product->id : '' ) : ( isset( $price->product ) ? $price->product : '' );
790 -
791 - $same_plan = isset( $price->unit_amount, $price->currency, $price->recurring->interval, $price->recurring->interval_count )
792 - && (int) $price->unit_amount === (int) $unit_amount
793 - && strtolower( (string) $price->currency ) === $currency
794 - && (string) $price_product === (string) $product_id
795 - && (string) $price->recurring->interval === $interval
796 - && (int) $price->recurring->interval_count === (int) $interval_count;
797 -
798 - return $same_plan ? sanitize_text_field( $price->id ) : '';
799 - }
800 -
801 - /**
802 440 * Get the paystack token
803 441 *
804 442 * @since 0.0.1
805 443 */
@@ -824,11 +462,8 @@
824 462
825 463 $page_id = intval( $_POST[ 'setting_data' ]['page_id'] );
826 464 $widget_id = sanitize_text_field( $_POST[ 'setting_data' ]['widget_id'] );
827 465 $el_settings = $this->better_payment_widget_settings( $page_id, $widget_id );
828 - $is_layout_6 = ! empty( $el_settings[ 'better_payment_form_layout' ] ) && 'layout-6-pro' === $el_settings[ 'better_payment_form_layout' ];
829 - $is_fluentcart_layout = $is_layout_6 && ! empty( $el_settings[ 'better_payment_form_layout_6_ecommerce_platform' ] ) && 'fluentcart' === $el_settings[ 'better_payment_form_layout_6_ecommerce_platform' ];
830 - $is_woo_layout = $is_layout_6 && ( empty( $el_settings[ 'better_payment_form_layout_6_ecommerce_platform' ] ) || 'woocommerce' === $el_settings[ 'better_payment_form_layout_6_ecommerce_platform' ] );
831 466
832 467 if ( empty( $el_settings ) ) {
833 468 wp_send_json_error( esc_html(__( 'Setting Data is missing', 'better-payment' )) );
834 469 }
@@ -910,13 +545,14 @@
910 545 'fluentcart_product_id' => $fluentcart_product_id,
911 546 'fluentcart_product_ids' => maybe_serialize( $fluentcart_product_ids ),
912 547 'source' => 'paystack',
913 548 'amount_quantity' => ! empty( $amount_quantity ) ? $amount_quantity : '',
914 - 'is_woo_layout' => $is_woo_layout,
915 - 'is_fluentcart_layout' => $is_fluentcart_layout,
916 549 'detailed_product_info' => maybe_serialize( $detailed_product_info ),
917 550 ];
918 551
552 + // Fields only Pro's forms store (layout 6's platform flags) — see PaymentEntitlement.
553 + $better_form_fields = PaymentEntitlement::transaction_fields( $better_form_fields, $el_settings );
554 +
919 555 $better_form_fields = array_merge( $better_form_fields, $this->fetch_better_form_fields($el_settings, $_POST['fields']) );
920 556
921 557 if ( !empty( $better_form_fields['primary_email'] ) ) {
922 558 $request[ 'email' ] = $better_form_fields['primary_email'];
@@ -1109,26 +745,10 @@
1109 745 $post_data_primary_email = '';
1110 746
1111 747 $post_fields = $post_data_form_fields;
1112 748
1113 - $layout = ! empty( $el_settings['better_payment_form_layout'] ) ? sanitize_text_field( $el_settings['better_payment_form_layout'] ) : 'layout-1';
1114 -
1115 - switch( $layout ) {
1116 - case 'layout-4-pro':
1117 - $el_settings['better_payment_form_fields'] = $el_settings['better_payment_form_fields_layout_4_5_6'];
1118 - break;
1119 -
1120 - case 'layout-5-pro':
1121 - $el_settings['better_payment_form_fields'] = $el_settings['better_payment_form_fields_layout_4_5_6_desc'];
1122 - break;
1123 -
1124 - case 'layout-6-pro':
1125 - $el_settings['better_payment_form_fields'] = $el_settings['better_payment_form_fields_layout_4_5_6_woo'];
1126 - break;
1127 -
1128 - default:
1129 - break;
1130 - }
749 + // The fields repeater a Pro form renders is Pro's to name — see PaymentEntitlement.
750 + $el_settings['better_payment_form_fields'] = PaymentEntitlement::form_fields( is_array( $el_settings ) ? $el_settings : [] );
1131 751
1132 752 //in case of duplicate post name using the first one
1133 753 if(isset($el_settings['better_payment_form_fields']) && count($el_settings['better_payment_form_fields'])){
1134 754 foreach ($el_settings['better_payment_form_fields'] as $item) {