Skip to content

PayPal Commerce Lite#3093

Merged
Crabcyborg merged 400 commits into
masterfrom
forms_actions_refresh_plus_paypal
May 25, 2026
Merged

PayPal Commerce Lite#3093
Crabcyborg merged 400 commits into
masterfrom
forms_actions_refresh_plus_paypal

Conversation

@Crabcyborg

@Crabcyborg Crabcyborg commented May 7, 2026

Copy link
Copy Markdown
Contributor

Closes https://github.com/Strategy11/formidable-pro/issues/6124

Requires https://github.com/Strategy11/formidable-pro/pull/6381/changes to work properly with multi-page forms.

Includes PayPal (originally #2678)
Include Form Actions refresh (originally #2999)

The PayPal update adds:

  • New PayPal global payment settings (with buttons to onboard like our Stripe and Square gateways).
  • New PayPal Commerce payment action.
  • Payment actions are now split into "Stripe", "Square", and "PayPal" actions. Payment action icons no longer exist unless the Authorize.Net add-on is active, since there is no icon for this gateway (and other possible custom gateways).
  • Many new payment settings specific to PayPal (Product Name, PayPal Button Settings, Customer syncing behaviour, Shipping address data).
  • Various UI changes for payment actions (the gateways radio buttons have been replaced by tab buttons and moved to the top of the action, the one time/recurring payment setting has been moved to the top of the action).

Summary by CodeRabbit

  • New Features

    • PayPal “Lite”: frontend checkout (Card, Buttons, Google/Apple Pay), one-time & recurring flows, connect/disconnect, admin settings, webhooks, and seller/status UI.
  • Improvements

    • Unified gateway handling across Stripe, Square, PayPal; clearer gateway availability, action limits, and payment/status/error messaging.
    • Optional syncing of PayPal order data into entries and refined “My Actions” editor grouping.
  • Chores

    • PayPal assets included in distribution, misc. styling and config tweaks (typo list updated).

Review Change Stack

…all buttons, add default colour option for buttons
…, add ideal to list of payment sources/buttons to render
…ort error description based on the issue key
…s if card processing is enabled, add bn codes
}

// Remove limit for Stripe action when Stripe add-on is active
if ( 'stripe' === $action_control->id_base && class_exists( 'FrmStrpAppHelper' ) ) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $action_control might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

?>
<ul class="frm_actions_list frm-list-grid-layout frm-m-0">
<?php
foreach ( $action_controls as $action_control ) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $action_controls might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

continue;
}

FrmFormActionsController::show_action_icon_link( $action_control, $allowed );

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $allowed might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

// Build array of installed action IDs to skip in second loop
$installed_action_ids = array();

foreach ( $action_controls as $action_control ) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $action_controls might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

// Check if this action is already installed
$action_is_installed = false;

foreach ( $action_controls as $action_control ) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable $action_controls might not be defined


A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.

Comment thread classes/views/frm-form-actions/settings.php
Comment thread classes/views/frm-form-actions/settings.php

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
classes/controllers/FrmFormActionsController.php (1)

139-143: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix nullable iteration to unblock Psalm.

self::$registered_actions is nullable by type, so iterating self::$registered_actions->actions at Line 139 triggers the current pipeline failure (PossiblyNullIterator).

Proposed fix
-		foreach ( self::$registered_actions->actions as $action ) {
+		$registered_actions = self::$registered_actions ? self::$registered_actions->actions : array();
+		foreach ( $registered_actions as $action ) {
 			if ( $action->action_options['description'] === '' && isset( $descriptions[ $action->id_base ] ) ) {
 				$action->action_options['description'] = $descriptions[ $action->id_base ];
 			}
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@classes/controllers/FrmFormActionsController.php` around lines 139 - 143,
FrmFormActionsController currently iterates self::$registered_actions->actions
while self::$registered_actions is nullable, causing a PossiblyNullIterator
error; guard the loop by ensuring self::$registered_actions is not null and that
the actions collection exists (e.g., check self::$registered_actions !== null
and isset(self::$registered_actions->actions) or use the null-coalescing pattern
to iterate an empty array when actions is absent) before running the foreach
over $action so that $action->action_options['description'] assignment and
checks against $descriptions[$action->id_base] are only executed when a valid
actions iterable is present.
classes/helpers/FrmAppHelper.php (1)

4047-4066: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Include PayPal in connected-gateway modal copy.

When only PayPal is connected, implode() receives an empty list and Line 4057 produces broken text; the fallback copy on Line 4065 also still excludes PayPal.

Proposed fix
 		if ( $gateway_connected ) {
 			$gateway_texts = array();
@@
 			if ( $square_connected ) {
 				$gateway_texts['square'] = esc_html__( 'Square', 'formidable' );
 			}
+			if ( $paypal_connected ) {
+				$gateway_texts['paypal'] = esc_html__( 'PayPal', 'formidable' );
+			}
 
 			$admin_script_strings['pricingFieldsModal']['msg'] = sprintf(
-				// translators: %s: Stripe or Square.
+				// translators: %s: Connected gateway names.
 				esc_html__( 'You already have %s connected, so these have already been unlocked.', 'formidable' ),
 				esc_html( implode( ' ' . esc_html__( 'and', 'formidable' ) . ' ', $gateway_texts ) )
 			);
@@
-			$admin_script_strings['pricingFieldsModal']['msg'] = __( 'We\'ve unlocked Product, Quantity, and Total fields for Lite users! You can now transform your forms into checkout pages. To start collecting revenue, simply connect your preferred payment gateway (Stripe, or Square) in your settings.', 'formidable' );
+			$admin_script_strings['pricingFieldsModal']['msg'] = __( 'We\'ve unlocked Product, Quantity, and Total fields for Lite users! You can now transform your forms into checkout pages. To start collecting revenue, simply connect your preferred payment gateway (Stripe, Square, or PayPal) in your settings.', 'formidable' );
 		}//end if
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@classes/helpers/FrmAppHelper.php` around lines 4047 - 4066, The modal copy
fails to include PayPal and can produce an empty implode result; update the
connected-gateway branch to also check the PayPal flag (e.g. $paypal_connected)
and add $gateway_texts['paypal'] = esc_html__( 'PayPal', 'formidable' ) when
set, and update the fallback message assigned to
$admin_script_strings['pricingFieldsModal']['msg'] to mention PayPal alongside
Stripe and Square; ensure the sprintf that builds
$admin_script_strings['pricingFieldsModal']['msg'] uses the populated
$gateway_texts (e.g. implode( ' ' . esc_html__( 'and', 'formidable' ) . ' ',
$gateway_texts )) so it no longer produces blank output when PayPal is the only
connected gateway.
♻️ Duplicate comments (1)
classes/controllers/FrmFormActionsController.php (1)

91-93: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard filtered action map access before reading paypal.

Line 91 still reads $action_classes['paypal'] directly even though frm_registered_form_actions can remove keys, which can emit an undefined index notice.

Proposed fix
-		if ( 'FrmPayPalLiteAction' === $action_classes['paypal'] || ! class_exists( 'FrmPaymentAction' ) || ! class_exists( 'FrmDefPayPalLegacyAction' ) ) {
+		if ( 'FrmPayPalLiteAction' === ( $action_classes['paypal'] ?? '' ) || ! class_exists( 'FrmPaymentAction' ) || ! class_exists( 'FrmDefPayPalLegacyAction' ) ) {
 			unset( $action_classes['paypal-legacy'] );
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@classes/controllers/FrmFormActionsController.php` around lines 91 - 93, The
code reads $action_classes['paypal'] without checking existence which can
trigger an undefined index; update the conditional in FrmFormActionsController
(the block that currently checks 'FrmPayPalLiteAction' ===
$action_classes['paypal'] || ! class_exists('FrmPaymentAction') || !
class_exists('FrmDefPayPalLegacyAction')) to first guard the 'paypal' entry
(e.g., use isset($action_classes['paypal']) or array_key_exists('paypal',
$action_classes)) before comparing to 'FrmPayPalLiteAction', keeping the rest of
the OR conditions and still calling unset($action_classes['paypal-legacy']) when
the combined condition is true.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@classes/views/frm-form-actions/settings.php`:
- Around line 103-127: The loop reuses $single_action_attrs so a previously set
'data-limit' leaks into subsequent actions; inside the iteration that builds
attributes for each action (where $single_action_attrs, $action and
$action_controls are used in classes/views/frm-form-actions/settings.php),
explicitly unset or reset $single_action_attrs['data-limit'] before checking
payment actions, then only set it when the current $action is an uninstalled
payment gateway (paypal, stripe, square); apply the same change to the other
occurrence around the 213-237 block to ensure non-payment actions never inherit
a stale data-limit attribute.

---

Outside diff comments:
In `@classes/controllers/FrmFormActionsController.php`:
- Around line 139-143: FrmFormActionsController currently iterates
self::$registered_actions->actions while self::$registered_actions is nullable,
causing a PossiblyNullIterator error; guard the loop by ensuring
self::$registered_actions is not null and that the actions collection exists
(e.g., check self::$registered_actions !== null and
isset(self::$registered_actions->actions) or use the null-coalescing pattern to
iterate an empty array when actions is absent) before running the foreach over
$action so that $action->action_options['description'] assignment and checks
against $descriptions[$action->id_base] are only executed when a valid actions
iterable is present.

In `@classes/helpers/FrmAppHelper.php`:
- Around line 4047-4066: The modal copy fails to include PayPal and can produce
an empty implode result; update the connected-gateway branch to also check the
PayPal flag (e.g. $paypal_connected) and add $gateway_texts['paypal'] =
esc_html__( 'PayPal', 'formidable' ) when set, and update the fallback message
assigned to $admin_script_strings['pricingFieldsModal']['msg'] to mention PayPal
alongside Stripe and Square; ensure the sprintf that builds
$admin_script_strings['pricingFieldsModal']['msg'] uses the populated
$gateway_texts (e.g. implode( ' ' . esc_html__( 'and', 'formidable' ) . ' ',
$gateway_texts )) so it no longer produces blank output when PayPal is the only
connected gateway.

---

Duplicate comments:
In `@classes/controllers/FrmFormActionsController.php`:
- Around line 91-93: The code reads $action_classes['paypal'] without checking
existence which can trigger an undefined index; update the conditional in
FrmFormActionsController (the block that currently checks 'FrmPayPalLiteAction'
=== $action_classes['paypal'] || ! class_exists('FrmPaymentAction') || !
class_exists('FrmDefPayPalLegacyAction')) to first guard the 'paypal' entry
(e.g., use isset($action_classes['paypal']) or array_key_exists('paypal',
$action_classes)) before comparing to 'FrmPayPalLiteAction', keeping the rest of
the OR conditions and still calling unset($action_classes['paypal-legacy']) when
the combined condition is true.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9828eccf-a0b2-4f93-832a-3c5eb6ceaea8

📥 Commits

Reviewing files that changed from the base of the PR and between ddb15ad and d78f128.

📒 Files selected for processing (12)
  • classes/controllers/FrmFormActionsController.php
  • classes/helpers/FrmAppHelper.php
  • classes/views/frm-form-actions/_action_icon.php
  • classes/views/frm-form-actions/settings.php
  • css/frm_admin.css
  • js/formidable-settings-components.js
  • js/formidable-web-components.js
  • js/formidable_admin.js
  • js/formidable_dashboard.js
  • js/src/admin/admin.js
  • js/src/components/tabs/filter.js
  • resources/scss/admin/components/form/_form-actions.scss

Comment thread classes/views/frm-form-actions/settings.php

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
classes/helpers/FrmAppHelper.php (1)

4044-4065: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Include PayPal in the pricing-fields modal copy.

$gateway_connected now treats PayPal as connected, but this branch still builds the connected-gateway list from Stripe/Square only and the fallback CTA still names only Stripe/Square. If PayPal is the only configured gateway, %s renders empty here; if nothing is configured, the copy hides a valid option.

Suggested fix
 		if ( $gateway_connected ) {
 			$gateway_texts = array();
 
 			if ( $stripe_connected ) {
 				$gateway_texts['stripe'] = esc_html__( 'Stripe', 'formidable' );
 			}
 
 			if ( $square_connected ) {
 				$gateway_texts['square'] = esc_html__( 'Square', 'formidable' );
 			}
+
+			if ( $paypal_connected ) {
+				$gateway_texts['paypal'] = esc_html__( 'PayPal', 'formidable' );
+			}
 
 			$admin_script_strings['pricingFieldsModal']['msg'] = sprintf(
-				// translators: %s: Stripe or Square.
+				// translators: %s: Connected payment gateways.
 				esc_html__( 'You already have %s connected, so these have already been unlocked.', 'formidable' ),
 				esc_html( implode( ' ' . esc_html__( 'and', 'formidable' ) . ' ', $gateway_texts ) )
 			);
 		} else {
@@
-			$admin_script_strings['pricingFieldsModal']['msg'] = __( 'We\'ve unlocked Product, Quantity, and Total fields for Lite users! You can now transform your forms into checkout pages. To start collecting revenue, simply connect your preferred payment gateway (Stripe, or Square) in your settings.', 'formidable' );
+			$admin_script_strings['pricingFieldsModal']['msg'] = __( 'We\'ve unlocked Product, Quantity, and Total fields for Lite users! You can now transform your forms into checkout pages. To start collecting revenue, simply connect your preferred payment gateway (Stripe, Square, or PayPal) in your settings.', 'formidable' );
 		}//end if
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@classes/helpers/FrmAppHelper.php` around lines 4044 - 4065, The pricing modal
builds the connected gateway list and fallback CTA text from only
$stripe_connected and $square_connected, causing %s to be empty when PayPal is
the only configured gateway; update the logic in the block that populates
$gateway_texts and the fallback CTA message (references: $gateway_connected,
$stripe_connected, $square_connected, $gateway_texts,
$admin_script_strings['pricingFieldsModal']['msg'] and ['actionText']) to also
check for the PayPal flag (e.g. $paypal_connected or whichever variable tracks
PayPal) and add esc_html__( 'PayPal', 'formidable' ) into $gateway_texts, and
update the fallback CTA copy to mention PayPal alongside Stripe and Square so
the imploded list and messages include PayPal when applicable.
paypal/controllers/FrmPayPalLiteActionsController.php (1)

1116-1116: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix type error: cast $amount to float for number_format().

Psalm is failing because parent::prepare_amount() returns a string, but number_format() expects a float as the first argument.

🐛 Proposed fix
-		return number_format( $amount, $currency['decimals'], '', '' );
+		return number_format( (float) $amount, $currency['decimals'], '', '' );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@paypal/controllers/FrmPayPalLiteActionsController.php` at line 1116, The
return uses number_format($amount, ...) but parent::prepare_amount() returns a
string, causing a type error; cast the value to float before passing to
number_format (e.g., cast the result of parent::prepare_amount() or $amount to
(float)) so number_format receives a float; update the return in
FrmPayPalLiteActionsController (where number_format is called) to use
(float)$amount or cast the call to parent::prepare_amount() to float.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@classes/helpers/FrmAppHelper.php`:
- Around line 4044-4065: The pricing modal builds the connected gateway list and
fallback CTA text from only $stripe_connected and $square_connected, causing %s
to be empty when PayPal is the only configured gateway; update the logic in the
block that populates $gateway_texts and the fallback CTA message (references:
$gateway_connected, $stripe_connected, $square_connected, $gateway_texts,
$admin_script_strings['pricingFieldsModal']['msg'] and ['actionText']) to also
check for the PayPal flag (e.g. $paypal_connected or whichever variable tracks
PayPal) and add esc_html__( 'PayPal', 'formidable' ) into $gateway_texts, and
update the fallback CTA copy to mention PayPal alongside Stripe and Square so
the imploded list and messages include PayPal when applicable.

In `@paypal/controllers/FrmPayPalLiteActionsController.php`:
- Line 1116: The return uses number_format($amount, ...) but
parent::prepare_amount() returns a string, causing a type error; cast the value
to float before passing to number_format (e.g., cast the result of
parent::prepare_amount() or $amount to (float)) so number_format receives a
float; update the return in FrmPayPalLiteActionsController (where number_format
is called) to use (float)$amount or cast the call to parent::prepare_amount() to
float.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8ded26e9-b732-477a-ab8b-692bef65419d

📥 Commits

Reviewing files that changed from the base of the PR and between f71ffdc and 088aa7a.

📒 Files selected for processing (6)
  • classes/helpers/FrmAppHelper.php
  • paypal/controllers/FrmPayPalLiteActionsController.php
  • paypal/js/frontend.js
  • psalm.xml
  • square/js/action.js
  • stripe/controllers/FrmTransLiteSubscriptionsController.php
✅ Files skipped from review due to trivial changes (1)
  • square/js/action.js

Comment thread paypal/controllers/FrmPayPalLiteActionsController.php Outdated
@Crabcyborg
Crabcyborg merged commit c1f032b into master May 25, 2026
20 checks passed
@Crabcyborg
Crabcyborg deleted the forms_actions_refresh_plus_paypal branch May 25, 2026 19:21
@coderabbitai coderabbitai Bot mentioned this pull request May 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant