| @@ -32,8 +32,16 @@ | ||
| 32 | 32 | * Every payment entry point must call it before talking to a gateway: the widget handlers |
| 33 | 33 | * (`Classes\Actions`), the block handlers (`Blocks\BlockActions`) and the Elementor Pro |
| 34 | 34 | * form actions. `tests/Unit/Classes/PaymentRequestGuardWiringTest.php` fails if one stops. |
| 35 | 35 | * |
| 36 | + * Better Payment Pro's payment forms — Payment Form layouts 4/5/6 and the recurring, | |
| 37 | + * split-payment and mixed payment types — are not resolved by the rules in this class. | |
| 38 | + * PaymentEntitlement refuses them outright without Pro and otherwise asks Pro (or, for | |
| 39 | + * an older Pro, Lite's transitional copy of Pro's code) to resolve them. Those resolvers use | |
| 40 | + * the public checks below (resolve_amount(), coupon_code(), stripe_price(), | |
| 41 | + * product_price(), …), so a Pro form is held to exactly the same amount, price and coupon | |
| 42 | + * rules as a free one. | |
| 43 | + * | |
| 36 | 44 | * @since 2.3.4 |
| 37 | 45 | */ |
| 38 | 46 | class PaymentRequestGuard { |
| 39 | 47 | |
| @@ -67,10 +75,17 @@ | ||
| 67 | 75 | return $this->helper; |
| 68 | 76 | } |
| 69 | 77 | |
| 70 | 78 | /** |
| 71 | - * Resolve a widget or block submission (Pro layouts 4/5/6 included). | |
| 79 | + * Resolve a widget or block submission. | |
| 72 | 80 | * |
| 81 | + * A Pro payment form (a Pro layout, PaymentEntitlement::is_pro_form()) is refused on a free | |
| 82 | + * install before anything else is looked up, and otherwise resolved through | |
| 83 | + * PaymentEntitlement::resolve_form_payment() — which fails closed when nothing resolves it. | |
| 84 | + * A request posting a field only a Pro form renders is refused on a free install too, and | |
| 85 | + * on every install when its form is not a Pro layout. A free layout is a one-time payment, | |
| 86 | + * whatever payment type its saved settings still hold. | |
| 87 | + * | |
| 73 | 88 | * @param array $settings Stored widget/block settings — never request data. |
| 74 | 89 | * @param array $fields Submitted fields: the AJAX `fields` array, or `$_POST` for PayPal. Unslashed. |
| 75 | 90 | * @param array $context `page_id` (int) of the form, `gateway` ('stripe'|'paypal'|'paystack'). |
| 76 | 91 | * |
| @@ -77,24 +92,36 @@ | ||
| 77 | 92 | * @return array|WP_Error { |
| 78 | 93 | * @type float $amount Total to charge (unit amount × quantity). |
| 79 | 94 | * @type float $unit_amount Validated amount per unit. |
| 80 | 95 | * @type int|string $quantity Submitted quantity, or '' when none was sent. |
| 81 | - * @type int[] $product_quantities Layout 6 per-product quantities, keyed like the product id setting. | |
| 96 | + * @type int[] $product_quantities Per-product quantities of a product-list form, keyed like its product id setting. | |
| 82 | 97 | * @type string $currency Currency to charge in. |
| 83 | 98 | * @type string $campaign_id Verified campaign id, or '' (never an unverified request value). |
| 84 | 99 | * @type string $coupon_code Coupon, only when the form has a visible coupon field. |
| 85 | - * @type bool $is_recurring Whether this is a Stripe subscription checkout. | |
| 86 | - * @type string $recurring_price_id Allowed recurring price id. | |
| 87 | - * @type string $installment_price_id Allowed split-payment installment price id. | |
| 100 | + * @type bool $is_recurring Whether this is a Stripe subscription checkout (Pro forms only). | |
| 101 | + * @type string $recurring_price_id Allowed recurring price id (Pro forms only). | |
| 102 | + * @type string $installment_price_id Allowed split-payment installment price id (Pro forms only). | |
| 88 | 103 | * } |
| 89 | 104 | */ |
| 90 | 105 | public function resolve_form_payment( array $settings, array $fields, array $context ) { |
| 91 | - $page_id = isset( $context['page_id'] ) ? absint( $context['page_id'] ) : 0; | |
| 92 | - $gateway = isset( $context['gateway'] ) ? sanitize_key( $context['gateway'] ) : ''; | |
| 93 | - $layout = self::setting( $settings, 'better_payment_form_layout', 'layout-1' ); | |
| 94 | - $payment_type = self::setting( $settings, 'better_payment_form_payment_type', 'one-time' ); | |
| 95 | - $campaign = $this->resolve_campaign( self::field( $fields, 'campaign_id' ), $page_id ); | |
| 106 | + $page_id = isset( $context['page_id'] ) ? absint( $context['page_id'] ) : 0; | |
| 107 | + $gateway = isset( $context['gateway'] ) ? sanitize_key( $context['gateway'] ) : ''; | |
| 108 | + $is_pro_form = PaymentEntitlement::is_pro_form( $settings ); | |
| 109 | + $posts_pro_field = PaymentEntitlement::posts_pro_fields( $fields ); | |
| 96 | 110 | |
| 111 | + // Before anything else is read: a free install runs none of a Pro payment. | |
| 112 | + if ( ( $is_pro_form || $posts_pro_field ) && ! ProCompatibility::pro_enabled() ) { | |
| 113 | + return PaymentEntitlement::refusal(); | |
| 114 | + } | |
| 115 | + | |
| 116 | + // A free layout never renders a plan, an installment or a mixed choice, whatever payment | |
| 117 | + // type its saved settings still hold. Such a request is forged, and never reaches Pro. | |
| 118 | + if ( ! $is_pro_form && $posts_pro_field ) { | |
| 119 | + return self::invalid_price(); | |
| 120 | + } | |
| 121 | + | |
| 122 | + $campaign = $this->resolve_campaign( self::field( $fields, 'campaign_id' ), $page_id ); | |
| 123 | + | |
| 97 | 124 | $resolved = [ |
| 98 | 125 | 'amount' => 0.0, |
| 99 | 126 | 'unit_amount' => 0.0, |
| 100 | 127 | 'quantity' => '', |
| @@ -100,26 +127,38 @@ | ||
| 100 | 127 | 'quantity' => '', |
| 101 | 128 | 'product_quantities' => [], |
| 102 | 129 | 'currency' => null !== $campaign && '' !== $campaign['currency'] ? $campaign['currency'] : self::form_currency( $settings ), |
| 103 | 130 | 'campaign_id' => null !== $campaign ? $campaign['id'] : '', |
| 104 | - 'coupon_code' => self::allowed_coupon_code( $settings, $layout, $fields ), | |
| 131 | + 'coupon_code' => '', | |
| 105 | 132 | 'is_recurring' => false, |
| 106 | 133 | 'recurring_price_id' => '', |
| 107 | 134 | 'installment_price_id' => '', |
| 108 | 135 | ]; |
| 109 | 136 | |
| 110 | - if ( in_array( $payment_type, [ 'recurring', 'split-payment' ], true ) ) { | |
| 111 | - return $this->resolve_subscription( $resolved, $settings, $fields, $payment_type, $gateway ); | |
| 137 | + if ( $is_pro_form ) { | |
| 138 | + return PaymentEntitlement::resolve_form_payment( | |
| 139 | + $this, | |
| 140 | + $resolved, | |
| 141 | + $settings, | |
| 142 | + $fields, | |
| 143 | + [ | |
| 144 | + 'page_id' => $page_id, | |
| 145 | + 'gateway' => $gateway, | |
| 146 | + 'campaign' => $campaign, | |
| 147 | + ] | |
| 148 | + ); | |
| 112 | 149 | } |
| 113 | 150 | |
| 114 | - // A subscription checkout is only ever rendered for a recurring/split form. | |
| 115 | - if ( 'subscription' === self::field( $fields, 'better_payment_recurring_mode' ) ) { | |
| 116 | - return self::invalid_price(); | |
| 117 | - } | |
| 151 | + $form_fields = self::repeater( $settings, 'better_payment_form_fields' ); | |
| 152 | + $resolved['coupon_code'] = self::coupon_code( $form_fields, $fields ); | |
| 118 | 153 | |
| 119 | - $amount = 'layout-6-pro' === $layout | |
| 120 | - ? $this->resolve_product_list_amount( $settings, $fields ) | |
| 121 | - : $this->resolve_single_amount( $settings, $fields, $layout, $campaign ); | |
| 154 | + $amount = $this->resolve_amount( | |
| 155 | + $settings, | |
| 156 | + $fields, | |
| 157 | + $form_fields, | |
| 158 | + self::preset_amounts( $settings, 'better_payment_show_amount_list', 'better_payment_amount' ), | |
| 159 | + $campaign | |
| 160 | + ); | |
| 122 | 161 | |
| 123 | 162 | if ( is_wp_error( $amount ) ) { |
| 124 | 163 | return $amount; |
| 125 | 164 | } |
| @@ -246,17 +285,21 @@ | ||
| 246 | 285 | |
| 247 | 286 | // ------------------------------------------------------------------ one-time amounts |
| 248 | 287 | |
| 249 | 288 | /** |
| 250 | - * Layouts 1–5: a product-priced form, a verified campaign donation, or the amount field's rules. | |
| 289 | + * A one-time amount: a product-priced form, a verified campaign donation, or the amount field's rules. | |
| 251 | 290 | * |
| 252 | - * @param array $settings Form settings. | |
| 253 | - * @param array $fields Submitted fields. | |
| 254 | - * @param string $layout Form layout. | |
| 255 | - * @param array|null $campaign Verified campaign. | |
| 256 | - * @return array|WP_Error | |
| 291 | + * Every one-time form goes through this, Pro's included — Pro's resolver passes the | |
| 292 | + * fields repeater and preset amounts its layout renders. | |
| 293 | + * | |
| 294 | + * @param array $settings Form settings. | |
| 295 | + * @param array $fields Submitted fields. | |
| 296 | + * @param array $form_fields The fields repeater the form renders. | |
| 297 | + * @param float[] $presets Preset amounts the form renders as radios (preset_amounts()). | |
| 298 | + * @param array|null $campaign Verified campaign (resolve_campaign()), or null. | |
| 299 | + * @return array|WP_Error `amount`, `unit_amount`, `quantity`, `product_quantities`. | |
| 257 | 300 | */ |
| 258 | - private function resolve_single_amount( array $settings, array $fields, $layout, $campaign ) { | |
| 301 | + public function resolve_amount( array $settings, array $fields, array $form_fields, array $presets, $campaign = null ) { | |
| 259 | 302 | $posted = self::posted_amount( $fields, 'primary_payment_amount' ); |
| 260 | 303 | $quantity = self::posted_quantity( $fields ); |
| 261 | 304 | |
| 262 | 305 | if ( is_wp_error( $quantity ) ) { |
| @@ -283,9 +326,9 @@ | ||
| 283 | 326 | } elseif ( null !== $campaign ) { |
| 284 | 327 | if ( ! self::campaign_allows( $campaign, $posted ) ) { |
| 285 | 328 | return self::invalid_amount(); |
| 286 | 329 | } |
| 287 | - } elseif ( ! self::amount_field_allows( $settings, $layout, $posted ) ) { | |
| 330 | + } elseif ( ! self::amount_field_allows( $form_fields, $presets, $posted ) ) { | |
| 288 | 331 | return self::invalid_amount(); |
| 289 | 332 | } |
| 290 | 333 | |
| 291 | 334 | if ( $posted <= 0 ) { |
| @@ -300,74 +343,8 @@ | ||
| 300 | 343 | ]; |
| 301 | 344 | } |
| 302 | 345 | |
| 303 | 346 | /** |
| 304 | - * Layout 6: the total of the configured WooCommerce/FluentCart products at their real prices. | |
| 305 | - * | |
| 306 | - * Quantities are the visitor's to choose, prices are not. Stripe and Paystack post one | |
| 307 | - * quantity per product; PayPal's plain form POST keeps only the last `payment_amount_quantity` | |
| 308 | - * input, so with several products and a single quantity the total can only be held to its | |
| 309 | - * floor — every product at least once — which is still never less than the listed price. | |
| 310 | - * | |
| 311 | - * @param array $settings Form settings. | |
| 312 | - * @param array $fields Submitted fields. | |
| 313 | - * @return array|WP_Error | |
| 314 | - */ | |
| 315 | - private function resolve_product_list_amount( array $settings, array $fields ) { | |
| 316 | - $platform = 'fluentcart' === self::setting( $settings, 'better_payment_form_layout_6_ecommerce_platform', '' ) ? 'fluentcart' : 'woocommerce'; | |
| 317 | - $product_ids = isset( $settings[ "better_payment_form_{$platform}_product_ids" ] ) ? (array) $settings[ "better_payment_form_{$platform}_product_ids" ] : []; | |
| 318 | - $product_ids = array_filter( array_map( 'absint', $product_ids ) ); | |
| 319 | - | |
| 320 | - if ( empty( $product_ids ) ) { | |
| 321 | - return self::price_unavailable(); | |
| 322 | - } | |
| 323 | - | |
| 324 | - $posted = self::posted_amount( $fields, 'primary_payment_amount' ); | |
| 325 | - $raw_quantity = isset( $fields['payment_amount_quantity'] ) ? $fields['payment_amount_quantity'] : null; | |
| 326 | - $expected = 0.0; | |
| 327 | - $exact = true; | |
| 328 | - $quantities = []; | |
| 329 | - | |
| 330 | - foreach ( $product_ids as $key => $product_id ) { | |
| 331 | - if ( is_array( $raw_quantity ) ) { | |
| 332 | - $quantity = isset( $raw_quantity[ $key ] ) ? intval( $raw_quantity[ $key ] ) : 1; | |
| 333 | - } elseif ( null !== $raw_quantity && '' !== $raw_quantity && 1 === count( $product_ids ) ) { | |
| 334 | - $quantity = intval( $raw_quantity ); | |
| 335 | - } else { | |
| 336 | - $quantity = 1; | |
| 337 | - $exact = 1 === count( $product_ids ); | |
| 338 | - } | |
| 339 | - | |
| 340 | - if ( $quantity < 1 ) { | |
| 341 | - return self::invalid_quantity(); | |
| 342 | - } | |
| 343 | - | |
| 344 | - $price = $this->product_price( $platform, $product_id ); | |
| 345 | - if ( null === $price ) { | |
| 346 | - return self::price_unavailable(); | |
| 347 | - } | |
| 348 | - | |
| 349 | - $expected += $price * $quantity; | |
| 350 | - $quantities[ $key ] = $quantity; | |
| 351 | - } | |
| 352 | - | |
| 353 | - $allowed = $exact ? self::same_amount( $posted, $expected ) : $posted + self::TOLERANCE >= $expected; | |
| 354 | - | |
| 355 | - if ( $expected <= 0 || ! $allowed ) { | |
| 356 | - return self::invalid_amount(); | |
| 357 | - } | |
| 358 | - | |
| 359 | - $amount = $exact ? $expected : $posted; | |
| 360 | - | |
| 361 | - return [ | |
| 362 | - 'amount' => $amount, | |
| 363 | - 'unit_amount' => $amount, | |
| 364 | - 'quantity' => 1, | |
| 365 | - 'product_quantities' => $quantities, | |
| 366 | - ]; | |
| 367 | - } | |
| 368 | - | |
| 369 | - /** | |
| 370 | 347 | * Whether an amount is one the form's amount field could have submitted. |
| 371 | 348 | * |
| 372 | 349 | * Mirrors partials/layout-repeater-vars.php and the layouts that include it: |
| 373 | 350 | * - a preset amount (radio) is always allowed — clicking one writes it into the input, |
| @@ -375,20 +352,20 @@ | ||
| 375 | 352 | * - a dynamic amount comes from the URL, so only min/max apply; |
| 376 | 353 | * - a fixed or hidden field can only submit its configured default; |
| 377 | 354 | * - otherwise the visitor types it, within min (default 1, as rendered) and max. |
| 378 | 355 | * |
| 379 | - * @param array $settings Form settings. | |
| 380 | - * @param string $layout Form layout. | |
| 381 | - * @param float $amount Submitted amount. | |
| 356 | + * @param array $form_fields The fields repeater the form renders. | |
| 357 | + * @param float[] $presets Preset amounts the form renders. | |
| 358 | + * @param float $amount Submitted amount. | |
| 382 | 359 | * @return bool |
| 383 | 360 | */ |
| 384 | - private static function amount_field_allows( array $settings, $layout, $amount ) { | |
| 385 | - if ( self::amount_in( $amount, self::preset_amounts( $settings, $layout ) ) ) { | |
| 361 | + private static function amount_field_allows( array $form_fields, array $presets, $amount ) { | |
| 362 | + if ( self::amount_in( $amount, $presets ) ) { | |
| 386 | 363 | return true; |
| 387 | 364 | } |
| 388 | 365 | |
| 389 | 366 | $field = null; |
| 390 | - foreach ( self::form_fields( $settings, $layout ) as $item ) { | |
| 367 | + foreach ( $form_fields as $item ) { | |
| 391 | 368 | if ( is_array( $item ) && 'primary_payment_amount' === ( isset( $item['better_payment_primary_field_type'] ) ? $item['better_payment_primary_field_type'] : '' ) ) { |
| 392 | 369 | $field = $item; |
| 393 | 370 | break; |
| 394 | 371 | } |
| @@ -486,71 +463,8 @@ | ||
| 486 | 463 | |
| 487 | 464 | return false; |
| 488 | 465 | } |
| 489 | 466 | |
| 490 | - // ------------------------------------------------------------------ subscriptions | |
| 491 | - | |
| 492 | - /** | |
| 493 | - * Recurring and split-payment forms: the price ids must be ones the form offers. | |
| 494 | - * | |
| 495 | - * The price decides what Stripe charges, so a request-supplied price id is as dangerous | |
| 496 | - * as a request-supplied amount — any price in the merchant's Stripe account would do. | |
| 497 | - * | |
| 498 | - * @param array $resolved Base resolution. | |
| 499 | - * @param array $settings Form settings. | |
| 500 | - * @param array $fields Submitted fields. | |
| 501 | - * @param string $payment_type 'recurring' | 'split-payment'. | |
| 502 | - * @param string $gateway Gateway handling the request. | |
| 503 | - * @return array|WP_Error | |
| 504 | - */ | |
| 505 | - private function resolve_subscription( array $resolved, array $settings, array $fields, $payment_type, $gateway ) { | |
| 506 | - // Recurring/split layouts render Stripe only; any other gateway would charge a | |
| 507 | - // one-time amount of the visitor's choosing instead of the plan. | |
| 508 | - if ( 'stripe' !== $gateway ) { | |
| 509 | - return new WP_Error( 'bp_subscription_gateway', __( 'This payment plan can only be paid with Stripe.', 'better-payment' ) ); | |
| 510 | - } | |
| 511 | - | |
| 512 | - $is_split = 'split-payment' === $payment_type; | |
| 513 | - $main_price = self::setting( $settings, 'better_payment_form_recurring_price_id', '' ); | |
| 514 | - $list_key = $is_split ? 'better_payment_split_installment_price_ids' : 'better_payment_recurring_price_ids'; | |
| 515 | - $item_key = $is_split ? 'better_payment_split_installment_price_id' : 'better_payment_recurring_price_id'; | |
| 516 | - $allowed = [ $main_price ]; | |
| 517 | - | |
| 518 | - foreach ( (array) ( isset( $settings[ $list_key ] ) ? $settings[ $list_key ] : [] ) as $item ) { | |
| 519 | - if ( is_array( $item ) && ! empty( $item[ $item_key ] ) ) { | |
| 520 | - $allowed[] = sanitize_text_field( $item[ $item_key ] ); | |
| 521 | - } | |
| 522 | - } | |
| 523 | - | |
| 524 | - $allowed = array_values( array_filter( $allowed ) ); | |
| 525 | - $price_id = self::field( $fields, 'better_payment_recurring_price_id' ); | |
| 526 | - $price_id = '' === $price_id ? $main_price : $price_id; | |
| 527 | - | |
| 528 | - if ( '' === $price_id || ! in_array( $price_id, $allowed, true ) ) { | |
| 529 | - return self::invalid_price(); | |
| 530 | - } | |
| 531 | - | |
| 532 | - $installment_id = $is_split ? self::field( $fields, 'split_payment_installment' ) : ''; | |
| 533 | - if ( '' !== $installment_id && ! in_array( $installment_id, $allowed, true ) ) { | |
| 534 | - return self::invalid_price(); | |
| 535 | - } | |
| 536 | - | |
| 537 | - $charged_price = '' !== $installment_id ? $installment_id : $price_id; | |
| 538 | - $unit_amount = $this->stripe_price_amount( $charged_price, self::stripe_secret_key( $settings ) ); | |
| 539 | - | |
| 540 | - if ( null === $unit_amount ) { | |
| 541 | - return self::price_unavailable(); | |
| 542 | - } | |
| 543 | - | |
| 544 | - $resolved['amount'] = $unit_amount; | |
| 545 | - $resolved['unit_amount'] = $unit_amount; | |
| 546 | - $resolved['is_recurring'] = true; | |
| 547 | - $resolved['recurring_price_id'] = $price_id; | |
| 548 | - $resolved['installment_price_id'] = $installment_id; | |
| 549 | - | |
| 550 | - return $resolved; | |
| 551 | - } | |
| 552 | - | |
| 553 | 467 | // ------------------------------------------------------------------ campaigns |
| 554 | 468 | |
| 555 | 469 | /** |
| 556 | 470 | * A Campaign Builder campaign (`bp_campaign` post). |
| @@ -735,8 +649,22 @@ | ||
| 735 | 649 | return $product_id ? $this->product_price( $source, $product_id ) : null; |
| 736 | 650 | } |
| 737 | 651 | |
| 738 | 652 | /** |
| 653 | + * Unit amount (major units) of a Stripe price, read with the form's own Stripe secret key. | |
| 654 | + * | |
| 655 | + * Looked up once per request. Null when it cannot be read — refuse the payment then; it | |
| 656 | + * is never charged at the visitor's amount. | |
| 657 | + * | |
| 658 | + * @param mixed $price_id Stripe price id; anything but a scalar reads as none. | |
| 659 | + * @param array $settings Form settings (they select the live or test secret key). | |
| 660 | + * @return float|null | |
| 661 | + */ | |
| 662 | + public function stripe_price( $price_id, array $settings ) { | |
| 663 | + return $this->stripe_price_amount( is_scalar( $price_id ) ? (string) $price_id : '', self::stripe_secret_key( $settings ) ); | |
| 664 | + } | |
| 665 | + | |
| 666 | + /** | |
| 739 | 667 | * Current price of a WooCommerce or FluentCart product, read the way the form renders it. |
| 740 | 668 | * |
| 741 | 669 | * @param string $platform 'woocommerce' | 'fluentcart'. |
| 742 | 670 | * @param int $product_id Product id. |
| @@ -741,9 +669,9 @@ | ||
| 741 | 669 | * @param string $platform 'woocommerce' | 'fluentcart'. |
| 742 | 670 | * @param int $product_id Product id. |
| 743 | 671 | * @return float|null |
| 744 | 672 | */ |
| 745 | - private function product_price( $platform, $product_id ) { | |
| 673 | + public function product_price( $platform, $product_id ) { | |
| 746 | 674 | $price = null; |
| 747 | 675 | |
| 748 | 676 | if ( 'woocommerce' === $platform && function_exists( 'wc_get_product' ) ) { |
| 749 | 677 | $product = wc_get_product( $product_id ); |
| @@ -820,43 +748,33 @@ | ||
| 820 | 748 | |
| 821 | 749 | // ------------------------------------------------------------------ form configuration |
| 822 | 750 | |
| 823 | 751 | /** |
| 824 | - * The fields repeater a layout renders — same mapping as Actions::fetch_better_form_fields(). | |
| 752 | + * A repeater setting (a list of items), or [] when it is unset or not a list. | |
| 825 | 753 | * |
| 826 | 754 | * @param array $settings Form settings. |
| 827 | - * @param string $layout Form layout. | |
| 755 | + * @param string $key Setting key, e.g. `better_payment_form_fields`. | |
| 828 | 756 | * @return array |
| 829 | 757 | */ |
| 830 | - private static function form_fields( array $settings, $layout ) { | |
| 831 | - $keys = [ | |
| 832 | - 'layout-4-pro' => 'better_payment_form_fields_layout_4_5_6', | |
| 833 | - 'layout-5-pro' => 'better_payment_form_fields_layout_4_5_6_desc', | |
| 834 | - 'layout-6-pro' => 'better_payment_form_fields_layout_4_5_6_woo', | |
| 835 | - ]; | |
| 836 | - $key = isset( $keys[ $layout ] ) ? $keys[ $layout ] : 'better_payment_form_fields'; | |
| 837 | - | |
| 758 | + public static function repeater( array $settings, $key ) { | |
| 838 | 759 | return isset( $settings[ $key ] ) && is_array( $settings[ $key ] ) ? $settings[ $key ] : []; |
| 839 | 760 | } |
| 840 | 761 | |
| 841 | 762 | /** |
| 842 | - * Preset amounts the layout renders as radios. | |
| 763 | + * Preset amounts a form renders as radios: the items of `$items_key`, while `$toggle_key` is on. | |
| 843 | 764 | * |
| 844 | - * @param array $settings Form settings. | |
| 845 | - * @param string $layout Form layout. | |
| 765 | + * @param array $settings Form settings. | |
| 766 | + * @param string $toggle_key Show-amount-list switch, e.g. `better_payment_show_amount_list`. | |
| 767 | + * @param string $items_key Amount list repeater, e.g. `better_payment_amount`. | |
| 846 | 768 | * @return float[] |
| 847 | 769 | */ |
| 848 | - private static function preset_amounts( array $settings, $layout ) { | |
| 849 | - $is_pro_layout = in_array( $layout, [ 'layout-4-pro', 'layout-5-pro', 'layout-6-pro' ], true ); | |
| 850 | - $toggle = $is_pro_layout ? 'better_payment_show_amount_list_layout_4_5_6' : 'better_payment_show_amount_list'; | |
| 851 | - $items = $is_pro_layout ? 'better_payment_amount_layout_4_5_6' : 'better_payment_amount'; | |
| 852 | - | |
| 853 | - if ( 'yes' !== self::setting( $settings, $toggle, '' ) || empty( $settings[ $items ] ) || ! is_array( $settings[ $items ] ) ) { | |
| 770 | + public static function preset_amounts( array $settings, $toggle_key, $items_key ) { | |
| 771 | + if ( 'yes' !== self::setting( $settings, $toggle_key, '' ) || empty( $settings[ $items_key ] ) || ! is_array( $settings[ $items_key ] ) ) { | |
| 854 | 772 | return []; |
| 855 | 773 | } |
| 856 | 774 | |
| 857 | 775 | $amounts = []; |
| 858 | - foreach ( $settings[ $items ] as $item ) { | |
| 776 | + foreach ( $settings[ $items_key ] as $item ) { | |
| 859 | 777 | if ( is_array( $item ) && isset( $item['better_payment_amount_val'] ) ) { |
| 860 | 778 | $amounts[] = floatval( $item['better_payment_amount_val'] ); |
| 861 | 779 | } |
| 862 | 780 | } |
| @@ -866,14 +784,13 @@ | ||
| 866 | 784 | |
| 867 | 785 | /** |
| 868 | 786 | * A coupon code, only when the form actually offers a visible coupon field. |
| 869 | 787 | * |
| 870 | - * @param array $settings Form settings. | |
| 871 | - * @param string $layout Form layout. | |
| 872 | - * @param array $fields Submitted fields. | |
| 788 | + * @param array $form_fields The fields repeater the form renders. | |
| 789 | + * @param array $fields Submitted fields. | |
| 873 | 790 | * @return string |
| 874 | 791 | */ |
| 875 | - private static function allowed_coupon_code( array $settings, $layout, array $fields ) { | |
| 792 | + public static function coupon_code( array $form_fields, array $fields ) { | |
| 876 | 793 | $code = self::field( $fields, 'primary_coupon_code' ); |
| 877 | 794 | |
| 878 | 795 | if ( '' === $code ) { |
| 879 | 796 | return ''; |
| @@ -878,9 +795,9 @@ | ||
| 878 | 795 | if ( '' === $code ) { |
| 879 | 796 | return ''; |
| 880 | 797 | } |
| 881 | 798 | |
| 882 | - foreach ( self::form_fields( $settings, $layout ) as $item ) { | |
| 799 | + foreach ( $form_fields as $item ) { | |
| 883 | 800 | if ( is_array( $item ) |
| 884 | 801 | && 'primary_coupon_code' === ( isset( $item['better_payment_primary_field_type'] ) ? $item['better_payment_primary_field_type'] : '' ) |
| 885 | 802 | && self::is_visible( $item ) ) { |
| 886 | 803 | return $code; |
| @@ -902,9 +819,9 @@ | ||
| 902 | 819 | * metadata `better_payment_public = yes`. |
| 903 | 820 | * Anything else refuses the checkout rather than silently charging full price for a code |
| 904 | 821 | * the donor believes applied. |
| 905 | 822 | * |
| 906 | - * Call this only with a code allowed_coupon_code() has let through. | |
| 823 | + * Call this only with a code coupon_code() has let through. | |
| 907 | 824 | * |
| 908 | 825 | * @since 2.3.4 |
| 909 | 826 | * |
| 910 | 827 | * @param string $code Coupon code from resolve_form_payment(). |
| @@ -1014,9 +931,9 @@ | ||
| 1014 | 931 | * @param array $fields Submitted fields. |
| 1015 | 932 | * @param string $key Amount input name. |
| 1016 | 933 | * @return float |
| 1017 | 934 | */ |
| 1018 | - private static function posted_amount( array $fields, $key ) { | |
| 935 | + public static function posted_amount( array $fields, $key ) { | |
| 1019 | 936 | $amount = self::field( $fields, $key ); |
| 1020 | 937 | |
| 1021 | 938 | if ( '' === $amount || 0.0 === floatval( $amount ) ) { |
| 1022 | 939 | $amount = self::field( $fields, 'primary_payment_amount_radio' ); |
| @@ -1051,9 +968,9 @@ | ||
| 1051 | 968 | * @param array $fields Submitted fields. |
| 1052 | 969 | * @param string $key Field name. |
| 1053 | 970 | * @return string |
| 1054 | 971 | */ |
| 1055 | - private static function field( array $fields, $key ) { | |
| 972 | + public static function field( array $fields, $key ) { | |
| 1056 | 973 | if ( ! isset( $fields[ $key ] ) ) { |
| 1057 | 974 | return ''; |
| 1058 | 975 | } |
| 1059 | 976 | |
| @@ -1072,9 +989,9 @@ | ||
| 1072 | 989 | * @param string $key Key. |
| 1073 | 990 | * @param string $fallback Value when unset or empty. |
| 1074 | 991 | * @return string |
| 1075 | 992 | */ |
| 1076 | - private static function setting( array $settings, $key, $fallback ) { | |
| 993 | + public static function setting( array $settings, $key, $fallback ) { | |
| 1077 | 994 | return isset( $settings[ $key ] ) && is_scalar( $settings[ $key ] ) && '' !== (string) $settings[ $key ] |
| 1078 | 995 | ? sanitize_text_field( (string) $settings[ $key ] ) |
| 1079 | 996 | : $fallback; |
| 1080 | 997 | } |
| @@ -1136,9 +1053,9 @@ | ||
| 1136 | 1053 | * @param float $a Amount. |
| 1137 | 1054 | * @param float $b Amount. |
| 1138 | 1055 | * @return bool |
| 1139 | 1056 | */ |
| 1140 | - private static function same_amount( $a, $b ) { | |
| 1057 | + public static function same_amount( $a, $b ) { | |
| 1141 | 1058 | return abs( (float) $a - (float) $b ) < self::TOLERANCE; |
| 1142 | 1059 | } |
| 1143 | 1060 | |
| 1144 | 1061 | /** |
| @@ -1143,9 +1060,9 @@ | ||
| 1143 | 1060 | |
| 1144 | 1061 | /** |
| 1145 | 1062 | * @return WP_Error |
| 1146 | 1063 | */ |
| 1147 | - private static function invalid_amount() { | |
| 1064 | + public static function invalid_amount() { | |
| 1148 | 1065 | return new WP_Error( 'bp_invalid_amount', __( 'The payment amount is not allowed for this form.', 'better-payment' ) ); |
| 1149 | 1066 | } |
| 1150 | 1067 | |
| 1151 | 1068 | /** |
| @@ -1150,9 +1067,9 @@ | ||
| 1150 | 1067 | |
| 1151 | 1068 | /** |
| 1152 | 1069 | * @return WP_Error |
| 1153 | 1070 | */ |
| 1154 | - private static function invalid_quantity() { | |
| 1071 | + public static function invalid_quantity() { | |
| 1155 | 1072 | return new WP_Error( 'bp_invalid_quantity', __( 'The quantity must be a whole number of at least 1.', 'better-payment' ) ); |
| 1156 | 1073 | } |
| 1157 | 1074 | |
| 1158 | 1075 | /** |
| @@ -1157,9 +1074,9 @@ | ||
| 1157 | 1074 | |
| 1158 | 1075 | /** |
| 1159 | 1076 | * @return WP_Error |
| 1160 | 1077 | */ |
| 1161 | - private static function invalid_price() { | |
| 1078 | + public static function invalid_price() { | |
| 1162 | 1079 | return new WP_Error( 'bp_invalid_price', __( 'The selected payment plan is not available for this form.', 'better-payment' ) ); |
| 1163 | 1080 | } |
| 1164 | 1081 | |
| 1165 | 1082 | /** |
| @@ -1178,8 +1095,8 @@ | ||
| 1178 | 1095 | |
| 1179 | 1096 | /** |
| 1180 | 1097 | * @return WP_Error |
| 1181 | 1098 | */ |
| 1182 | - private static function price_unavailable() { | |
| 1099 | + public static function price_unavailable() { | |
| 1183 | 1100 | return new WP_Error( 'bp_price_unavailable', __( 'The price for this payment could not be verified. Please try again later.', 'better-payment' ) ); |
| 1184 | 1101 | } |
| 1185 | 1102 | } |