Skip to content

feat(react-paypal-js): add v6 Local Payment Methods (LPM) React wrapper core#993

Open
Mathanjeevika wants to merge 3 commits into
paypal:mainfrom
Mathanjeevika:feature/lpm-react-wrapper-core
Open

feat(react-paypal-js): add v6 Local Payment Methods (LPM) React wrapper core#993
Mathanjeevika wants to merge 3 commits into
paypal:mainfrom
Mathanjeevika:feature/lpm-react-wrapper-core

Conversation

@Mathanjeevika

Copy link
Copy Markdown

Summary

  • Adds a generic, registry-driven React wrapper for all 50 v6 SDK Local Payment Methods (LPM), exposed via a new @paypal/react-paypal-js/sdk-v6/local-payment-methods subpath so the default @paypal/react-paypal-js export is unaffected.
  • Supports three usage patterns: an all-in-one named button (e.g. BancontactOneTimePaymentButton), a split hook + standalone button (useBancontactOneTimePaymentSession + BancontactPaymentButton), and a generic dynamic-selector button (LPMOneTimePaymentButton).
  • New files: src/v6/config/lpmRegistry.ts (registry), src/v6/hooks/useLPMOneTimePaymentSession.ts, src/v6/components/{LPMOneTimePaymentButton,LPMPaymentProvider}.tsx, src/v6/lpmExports.ts (subpath entry), plus a new Rollup build target and exports subpath.

Context

This is PR 2 of 3 split out from #926 per review feedback requesting the original monolithic PR be broken up:

  1. feat(paypal-js): add v6 Local Payment Methods (LPM) types #992paypal-js v6 LPM type declarations
  2. this PRreact-paypal-js core LPM wrapper
  3. (follow-up) — Storybook stories for LPM components

This PR depends on #992 merging first. The LPM types (LPMComponents, LPMSessionMethodName, LPMOneTimePaymentSession, etc.) are re-exported from @paypal/paypal-js/sdk-v6 and consumed here via src/v6/types/index.ts's export type * from "@paypal/paypal-js/sdk-v6". Until #992 merges, npm run typecheck in this package will fail against main — this is expected and resolves once #992 lands (verified locally by overlaying #992's type declarations, which produces a clean tsc --noEmit).

Known pre-existing issue

src/v6/hooks/useLPMOneTimePaymentSession.test.ts hangs under jest in this environment. This is not introduced by this PR — verified by reproducing the identical hang on the original, unmodified feature/lpm-react-wrapper branch running the same test file in isolation. Flagging for visibility; happy to dig into it further if it reproduces in CI.

Test plan

…er core

Adds the generic, registry-driven React wrapper for all 50 v6 Local
Payment Methods, exposed via the ./sdk-v6/local-payment-methods subpath
so the default @paypal/react-paypal-js export is unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Mathanjeevika
Mathanjeevika requested a review from a team as a code owner July 13, 2026 04:27
@changeset-bot

changeset-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fc4705f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@paypal/react-paypal-js Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@EvanReinstein

Copy link
Copy Markdown
Contributor

One thing we'll need to do is update the react-paypal-js README with integration code snippets and appropriate re-directs to integration resources (v6 sample integration repo or dev docs).

},
estonia: {
component: "estoniabank-payments",
buttonTag: "estoniabank-button",

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.

Suggested change
buttonTag: "estoniabank-button",
buttonTag: "estonia-button",

testBuyerCountry: "ID",
},
thailandBanks: {
component: "thailandbanks-payments",

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.

Suggested change
component: "thailandbanks-payments",
component: "thailand-banks-payments",

Comment on lines +17 to +29
test("button tag derives from component name", () => {
// FIUU is the one exception: its SDK component is "fiuu-cash-payments" but
// the registered custom-element button tag is "fiuu-button".
const TAG_EXCEPTIONS: Record<string, string> = {
"fiuu-cash-payments": "fiuu-button",
};
for (const config of Object.values(LPM_REGISTRY)) {
const expectedTag =
TAG_EXCEPTIONS[config.component] ??
config.component.replace("-payments", "-button");
expect(config.buttonTag).toBe(expectedTag);
}
});

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.

This test is using the registry's own component value to compute the expected tag, which is why it didn't catch the estonia button bug that I pointed out elsewhere. This needs an independent list, like checking if config.buttonTag is a key in SDKWebComponents.

Comment on lines +101 to +103
test("has exactly 50 LPM entries", () => {
expect(Object.keys(LPM_REGISTRY)).toHaveLength(50);
});

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.

What is the purpose of this test?

Comment on lines +123 to +131
/**
* Creates a stable SDK-iframe field component for a given field type.
*
* This is a module-level factory (not defined inside the hook render) so
* React concurrent-mode is fully safe and no ESLint suppression is required.
* The returned component is created once per `createEnhancedLPMHook` call via
* `useRef` and will not unmount/remount when the session changes — instead it
* uses a version counter driven by a pub/sub listener on `sessionListenersRef`.
*/

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.

It looks like this implementation goes against the decision from the ADR to create an LPMSessionContext in place of the pub/sub listener set. Can you review the ADR decisions and update the implementation here?

Comment on lines +158 to +161
// Mount (or re-mount) the SDK iframe whenever the session version changes,
// i.e. whenever a new session becomes available via the pub/sub mechanism.
// sessionRef.current is accessed imperatively; fieldType is a stable
// closure variable from the factory — neither belongs in the deps array.

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.

We decided against this pub/sub mechanism in favor of leveraging Context, please review the finalized ADR 👍

Mathanjeevika and others added 2 commits July 17, 2026 10:48
- Fix estonia buttonTag: 'estoniabank-button' -> 'estonia-button'
- Fix thailand: component + buttonTag corrected with proper hyphenation
- Update SDKWebComponents to match corrected thailand button tag
- Add SDK_LPM_BUTTON_TAGS runtime set for independent test validation
- Replace fragile 'button tag derives' test with SDK_LPM_BUTTON_TAGS check
- Remove 'has exactly 50 LPM entries' test
- Replace pub/sub listener set with LPMSessionContext per ADR decision;
  field components now consume useContext(LPMSessionContext);
  createEnhancedLPMHook returns LPMSessionProvider for wrapping fields
- Export LPMSessionContext from lpmExports

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants