Skip to content

Update paypal apple pay eligibility checks#3133

Merged
Crabcyborg merged 7 commits into
masterfrom
update_apple_pay_eligiblity_checks
Jun 11, 2026
Merged

Update paypal apple pay eligibility checks#3133
Crabcyborg merged 7 commits into
masterfrom
update_apple_pay_eligiblity_checks

Conversation

@Crabcyborg

@Crabcyborg Crabcyborg commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Still seeing some issues with Apple Pay after merging https://github.com/Strategy11/formidable-forms/pull/3130/changes

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced Apple Pay eligibility detection with comprehensive availability checks and improved error messaging when unavailable
    • Refined card-field display behavior for more predictable rendering

@Crabcyborg Crabcyborg added this to the 6.32 milestone Jun 11, 2026
@Crabcyborg Crabcyborg changed the title Update paypal apple pay elgibility checks Update paypal apple pay eligibility checks Jun 11, 2026
@deepsource-io

deepsource-io Bot commented Jun 11, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 1e8df4e...9692d2d on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

Important

Some issues found as part of this review are outside of the diff in this pull request and aren't shown in the inline review comments due to GitHub's API limitations. You can see those issues on the DeepSource dashboard.

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
PHP Jun 11, 2026 4:24p.m. Review ↗
JavaScript Jun 11, 2026 4:24p.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Crabcyborg, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 13 minutes and 29 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 196140f1-70ca-43ed-ad65-85fbb087557c

📥 Commits

Reviewing files that changed from the base of the PR and between e6e77b8 and 9692d2d.

📒 Files selected for processing (1)
  • paypal/js/frontend.js
📝 Walkthrough

Walkthrough

Apple Pay SDK eligibility discovery is reworked with bounded async readiness checks that wait for SDK availability and cache the instance/config, using an upgraded waitFor() utility supporting Promise-returning predicates. Button rendering now guards against missing ApplePaySession with an inline error. Card-field iframe height adjustment is narrowed to a reasonable pixel range and optimized to avoid repeated observer disconnect/reconnect cycles.

Changes

Apple Pay SDK Eligibility & Rendering

Layer / File(s) Summary
Apple Pay SDK Readiness & Utility Upgrade
paypal/js/frontend.js
The waitFor() utility is upgraded to support async predicates returning Promises. The new resolveApplePayEligibility() function performs bounded stepwise checks (ApplePaySession, canMakePayments(), PayPal SDK, paypal.Applepay function, and successful config() call) and caches instance/config on success. The legacy checkApplePayEligibility() function is removed.
Apple Pay Button Rendering Safety Check
paypal/js/frontend.js
Button rendering adds a runtime guard: when ApplePaySession is unavailable, an inline error message is written to the container and the function returns early without rendering the button component.

Card Field Height Handling

Layer / File(s) Summary
MutationObserver Height Adjustment Range
paypal/js/frontend.js
The iframe height observer callback is optimized to avoid repeated disconnect/reconnect cycles. The 1px height adjustment now applies only when the measured offsetHeight falls within a tighter "reasonable" range (>40 and <100 pixels) instead of for any positive height.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Strategy11/formidable-forms#3130: Modifies paypal/js/frontend.js Apple Pay/GPay SDK readiness discovery using bounded waitFor polling and adjusts Apple Pay option registration and rendering.
  • Strategy11/formidable-forms#3102: Overlaps at Apple Pay session-setup by introducing/using cached Apple Pay SDK instance state in the SDK readiness and click handler flow.

Poem

🐰 A wild async awaits the Apple Pay day,
When SDK readiness's checked—no errors in the way!
Heights adjust just right, observers stay quite still,
The button renders safely now, by guard and guarded will. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Update paypal apple pay eligibility checks' accurately reflects the main change in the changeset, which focuses on refactoring Apple Pay eligibility discovery and rendering logic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch update_apple_pay_eligiblity_checks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread paypal/js/frontend.js Outdated
try {
const instance = paypal.Applepay();
const config = await instance.config();
return config && config.isEligible;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Prefer using an optional chain expression instead, as it's more concise and easier to read


The optional chaining operator can be used to perform null checks before accessing a property, or calling a function.

@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

🧹 Nitpick comments (2)
paypal/js/frontend.js (2)

421-432: ⚡ Quick win

Async callback in setInterval can cause overlapping executions.

The setInterval callback is async, but setInterval doesn't await its callback. If checkPredicate() takes longer than the interval (50ms), multiple invocations can overlap. While the current predicates are likely fast enough, this could cause unexpected behavior with slower async predicates.

Consider using a recursive setTimeout pattern instead:

♻️ Suggested fix using recursive setTimeout
 		const start = Date.now();
 		return new Promise( resolve => {
-			const timer = setInterval( async () => {
-				if ( await checkPredicate() ) {
-					clearInterval( timer );
-					resolve( true );
-				} else if ( Date.now() - start >= timeout ) {
-					clearInterval( timer );
-					resolve( false );
-				}
-			}, interval );
+			const poll = async () => {
+				if ( await checkPredicate() ) {
+					resolve( true );
+				} else if ( Date.now() - start >= timeout ) {
+					resolve( false );
+				} else {
+					setTimeout( poll, interval );
+				}
+			};
+			setTimeout( poll, interval );
 		} );
🤖 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/js/frontend.js` around lines 421 - 432, The current Promise uses an
async callback inside setInterval which can overlap when checkPredicate() is
slow; replace the setInterval pattern with a recursive setTimeout poll loop:
create an inner async function (e.g., poll) that awaits checkPredicate(),
resolves the Promise when it's true, checks elapsed time against timeout to
resolve false, and otherwise schedules the next invocation with setTimeout(poll,
interval); update the Promise at the start (where start, timeout, interval and
checkPredicate are used) to call poll() instead of using setInterval so
overlapping executions cannot occur.

379-396: ⚡ Quick win

Redundant config() call and instance creation.

The async predicate (lines 379-387) successfully creates an instance and calls config() to verify eligibility, but these are discarded. Lines 394-396 then create a new instance and call config() again. This doubles the API calls unnecessarily.

Consider caching directly in the predicate to avoid the redundant call:

♻️ Suggested refactor to avoid double config() call
 		// Wait for the config call to actually succeed, not just the function to exist.
 		// This handles the race condition where the function exists but SDK isn't fully initialized.
+		let tempInstance = null;
+		let tempConfig = null;
 		const configReady = await waitFor( async () => {
 			try {
 				const instance = paypal.Applepay();
 				const config = await instance.config();
-				return config?.isEligible;
+				if ( config?.isEligible ) {
+					tempInstance = instance;
+					tempConfig = config;
+					return true;
+				}
+				return false;
 			} catch ( e ) {
 				return false;
 			}
 		} );

 		if ( ! configReady ) {
 			return false;
 		}

 		// Config succeeded, cache it and return true.
-		applePayInstance = paypal.Applepay();
-		applePayConfig = await applePayInstance.config();
+		applePayInstance = tempInstance;
+		applePayConfig = tempConfig;
 		return true;
🤖 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/js/frontend.js` around lines 379 - 396, The predicate passed to
waitFor currently creates an instance and calls paypal.Applepay().config(), but
those results are discarded and later code recreates them, causing duplicate API
calls; modify the predicate used in waitFor so that when it successfully obtains
an instance and a config (i.e., paypal.Applepay() and instance.config()), it
assigns them to the outer-scope variables applePayInstance and applePayConfig
and then returns true, and leave the fallback path to return false on
error—remove the later paypal.Applepay() and config() calls so the cached
applePayInstance/applePayConfig are reused.
🤖 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 `@paypal/js/frontend.js`:
- Around line 797-809: The observerCallback currently mutates
mutation.target.style.height which retriggers the mutation and causes a runaway
loop; modify observerCallback to skip elements already adjusted by either (A)
checking/setting a marker like mutation.target.dataset.paypalHeightAdjusted (set
it when you increase the height so subsequent mutations ignore that element) or
(B) temporarily suspending the observer around the style change (call the
MutationObserver instance's disconnect(), change mutation.target.style.height,
then re-observe the target) and only perform the +1px adjustment when the marker
is not present or while disconnected; update/remove the marker as needed if you
expect future legitimate height changes.

---

Nitpick comments:
In `@paypal/js/frontend.js`:
- Around line 421-432: The current Promise uses an async callback inside
setInterval which can overlap when checkPredicate() is slow; replace the
setInterval pattern with a recursive setTimeout poll loop: create an inner async
function (e.g., poll) that awaits checkPredicate(), resolves the Promise when
it's true, checks elapsed time against timeout to resolve false, and otherwise
schedules the next invocation with setTimeout(poll, interval); update the
Promise at the start (where start, timeout, interval and checkPredicate are
used) to call poll() instead of using setInterval so overlapping executions
cannot occur.
- Around line 379-396: The predicate passed to waitFor currently creates an
instance and calls paypal.Applepay().config(), but those results are discarded
and later code recreates them, causing duplicate API calls; modify the predicate
used in waitFor so that when it successfully obtains an instance and a config
(i.e., paypal.Applepay() and instance.config()), it assigns them to the
outer-scope variables applePayInstance and applePayConfig and then returns true,
and leave the fallback path to return false on error—remove the later
paypal.Applepay() and config() calls so the cached
applePayInstance/applePayConfig are reused.
🪄 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: d8854200-ec10-4d86-b17b-ee67994dbc69

📥 Commits

Reviewing files that changed from the base of the PR and between 1e8df4e and e6e77b8.

📒 Files selected for processing (1)
  • paypal/js/frontend.js

Comment thread paypal/js/frontend.js Outdated
Comment on lines 797 to 809
const observerCallback = ( mutationsList ) => {
for ( const mutation of mutationsList ) {
if ( mutation.type !== 'attributes' || mutation.attributeName !== 'style' ) {
continue;
}

const currentHeight = mutation.target.offsetHeight;
if ( currentHeight > 0 ) {
// Only adjust if height is reasonable and not already adjusted.
if ( currentHeight > 40 && currentHeight < 100 ) {
mutation.target.style.height = `${ currentHeight + 1 }px`;
}
}

wrappers.forEach( w => observer.observe( w, observerOptions ) );
};

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Potential runaway loop: height adjustment triggers new mutations.

Setting mutation.target.style.height triggers another mutation, causing the callback to fire again. Since there's no check to prevent re-adjustment of an already-adjusted height, this will keep incrementing until currentHeight >= 100:

  1. PayPal sets height to 50px → callback adjusts to 51px
  2. Mutation fires → callback adjusts to 52px
  3. Repeats until height reaches 100px

The comment mentions "not already adjusted" but there's no such guard in the code.

🐛 Proposed fix to prevent re-adjustment
 		const observerCallback = ( mutationsList ) => {
 			for ( const mutation of mutationsList ) {
 				if ( mutation.type !== 'attributes' || mutation.attributeName !== 'style' ) {
 					continue;
 				}

 				const currentHeight = mutation.target.offsetHeight;
+				const currentStyle = mutation.target.style.height;
+				// Skip if we already adjusted (style ends with our +1 adjustment)
+				if ( currentStyle && currentStyle === `${ currentHeight }px` ) {
+					continue;
+				}
 				// Only adjust if height is reasonable and not already adjusted.
 				if ( currentHeight > 40 && currentHeight < 100 ) {
 					mutation.target.style.height = `${ currentHeight + 1 }px`;
 				}
 			}
 		};

Alternatively, track adjusted elements or disconnect during adjustment:

+		const adjustedHeights = new WeakMap();
 		const observerCallback = ( mutationsList ) => {
 			for ( const mutation of mutationsList ) {
 				if ( mutation.type !== 'attributes' || mutation.attributeName !== 'style' ) {
 					continue;
 				}

 				const currentHeight = mutation.target.offsetHeight;
+				if ( adjustedHeights.get( mutation.target ) === currentHeight + 1 ) {
+					continue;
+				}
 				// Only adjust if height is reasonable and not already adjusted.
 				if ( currentHeight > 40 && currentHeight < 100 ) {
+					adjustedHeights.set( mutation.target, currentHeight + 1 );
 					mutation.target.style.height = `${ currentHeight + 1 }px`;
 				}
 			}
 		};
🤖 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/js/frontend.js` around lines 797 - 809, The observerCallback currently
mutates mutation.target.style.height which retriggers the mutation and causes a
runaway loop; modify observerCallback to skip elements already adjusted by
either (A) checking/setting a marker like
mutation.target.dataset.paypalHeightAdjusted (set it when you increase the
height so subsequent mutations ignore that element) or (B) temporarily
suspending the observer around the style change (call the MutationObserver
instance's disconnect(), change mutation.target.style.height, then re-observe
the target) and only perform the +1px adjustment when the marker is not present
or while disconnected; update/remove the marker as needed if you expect future
legitimate height changes.

@Crabcyborg
Crabcyborg merged commit 0a52357 into master Jun 11, 2026
20 of 22 checks passed
@Crabcyborg
Crabcyborg deleted the update_apple_pay_eligiblity_checks branch June 11, 2026 16:30
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