Skip to content

feat(paypal)!: migrate to PayPal JavaScript SDK v6#628

Merged
harlan-zw merged 9 commits intomainfrom
feat/paypal-sdk-v6
Mar 5, 2026
Merged

feat(paypal)!: migrate to PayPal JavaScript SDK v6#628
harlan-zw merged 9 commits intomainfrom
feat/paypal-sdk-v6

Conversation

@harlan-zw
Copy link
Copy Markdown
Collaborator

@harlan-zw harlan-zw commented Mar 5, 2026

❓ Type of change

  • 📖 Documentation
  • 🐞 Bug fix
  • 👌 Enhancement
  • ✨ New feature
  • 🧹 Chore
  • ⚠️ Breaking change

📚 Description

PayPal released JS SDK v6 with a completely new architecture — instance-based initialization via createInstance(), eligibility-first rendering, and session-based payment flows replacing the old Buttons()/Marks()/Messages() API. This migrates the integration from v5 (/sdk/js) to v6 (/web-sdk/v6/core).

Types come from @paypal/paypal-js/sdk-v6 (already shipped in @paypal/paypal-js@9.4.0).

⚠️ Breaking Changes

  • PayPalOptions schema reduced to clientId?, clientToken?, sandbox? — v6 config happens at createInstance() time, not via URL query params
  • ScriptPayPalButtons no longer renders buttons directly — exposes the v6 SDK instance via #default="{ sdkInstance }" scoped slot
  • ScriptPayPalMarks component removed (no v6 equivalent — replaced by findEligibleMethods())
  • ScriptPayPalMessages updated for v6 createPayPalMessages() API
  • PayPalNamespace type replaced with PayPalV6Namespace

📝 Migration

- import type { PayPalNamespace } from '@paypal/paypal-js'
+ import type { PayPalV6Namespace, SdkInstance, Components } from '@paypal/paypal-js/sdk-v6'

ScriptPayPalButtons now provides the SDK instance via scoped slot:

<ScriptPayPalButtons
  :client-id="clientId"
  :components="['paypal-payments']"
  @ready="onReady"
>
  <template #default="{ sdkInstance }">
    <button @click="pay(sdkInstance)">Pay with PayPal</button>
  </template>
</ScriptPayPalButtons>

Payment flow uses sessions instead of button callbacks:

const eligibility = await sdkInstance.findEligibleMethods()
if (eligibility.isEligible('paypal')) {
  const session = sdkInstance.createPayPalOneTimePaymentSession({
    onApprove: async (data) => { /* capture order */ },
  })
  await session.start({ presentationMode: 'auto' }, createOrderPromise)
}

BREAKING CHANGE: PayPal integration now uses SDK v6 (`/web-sdk/v6/core`)
instead of v5 (`/sdk/js`). Key changes:

- Script URL changed to `https://www.paypal.com/web-sdk/v6/core`
- `useScriptPayPal` options simplified to `clientId`, `clientToken`, `sandbox`
- v5 query-param options removed (buyerCountry, commit, components, currency,
  debug, disableFunding, enableFunding, integrationDate, intent, locale,
  merchantId, partnerAttributionId, vault)
- `ScriptPayPalButtons` now exposes SDK v6 instance via scoped slot instead of
  rendering PayPal buttons directly — use `paypal.createInstance()` +
  `findEligibleMethods()` + payment sessions
- `ScriptPayPalMarks` removed (no v6 equivalent)
- `ScriptPayPalMessages` updated for v6 messages API
- Types now from `@paypal/paypal-js/sdk-v6` instead of `@paypal/paypal-js`
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Mar 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
scripts-playground Ready Ready Preview, Comment Mar 5, 2026 1:24pm

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Mar 5, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@nuxt/scripts@628

commit: 273feb4

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR migrates PayPal integrations from SDK v5 to SDK v6: it updates imports/types to @paypal/paypal-js/sdk-v6, replaces per-button flows with v6 createInstance/session readiness (exposing sdkInstance/messagesSession and emitting ready/error), removes ScriptPayPalMarks and its registry mapping, updates the PayPal registry and script meta to the v6 core URL, simplifies PayPalOptions schema to require either clientId or clientToken, revises runtime components (Buttons, Messages) and playground/docs to demonstrate v6 usage and a custom startPayment flow, and updates script-size analytics.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(paypal)!: migrate to PayPal JavaScript SDK v6' accurately and concisely summarizes the main change: migrating the PayPal integration from v5 to v6.
Description check ✅ Passed The PR description is comprehensive and covers all critical requirements from the template: type of change (✨ New feature and ⚠️ Breaking change) are properly marked, clear description of the v6 migration and its architecture, documented breaking changes, migration examples with code snippets showing the new API usage, and type imports.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/paypal-sdk-v6

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
src/runtime/registry/schemas.ts (1)

587-602: ⚠️ Potential issue | 🟠 Major

Enforce the documented clientId/clientToken requirement in schema.

Line 589 and Line 594 document “one is required if the other is absent,” but Line 592 and Line 597 allow both to be omitted. That lets invalid config pass validation and fail later at runtime.

💡 Suggested schema guard
-export const PayPalOptions = object({
+export const PayPalOptions = pipe(object({
   /**
    * Your PayPal client ID. Required if `clientToken` is not provided.
    * `@see` https://developer.paypal.com/sdk/js/reference/
    */
   clientId: optional(string()),
   /**
    * A server-generated client token for authentication. Required if `clientId` is not provided.
    * `@see` https://docs.paypal.ai/payments/methods/paypal/sdk/js/v6/paypal-checkout
    */
   clientToken: optional(string()),
   /**
    * Use the PayPal sandbox environment. Defaults to `true` in development.
    */
   sandbox: optional(boolean()),
-})
+}), custom(input => Boolean(input.clientId || input.clientToken)))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/registry/schemas.ts` around lines 587 - 602, The PayPalOptions
schema currently allows both clientId and clientToken to be omitted despite doc
comments; update PayPalOptions to enforce that at least one of clientId or
clientToken is present by either (a) creating a union type of two schemas (one
requiring clientId, one requiring clientToken) or (b) adding a
refinement/validation to the existing PayPalOptions object that checks that
(clientId || clientToken) is truthy and returns a clear validation error; target
the PayPalOptions definition and the clientId/clientToken fields to implement
this guard.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/runtime/components/ScriptPayPalButtons.vue`:
- Around line 96-103: The error path for createInstance in
ScriptPayPalButtons.vue leaves ready false and the component stuck in a loading
state; update the catch block to set a local failure/loading state (e.g., set
ready.value = false and set a new failed/error reactive variable or clear
sdkInstance.value) so the template can render a terminal error UI, then
emit('error', err) as before; ensure you reference sdkInstance, ready, emit and
the createInstance call when adding the failed flag and updating state in the
catch.

In `@src/runtime/components/ScriptPayPalMessages.vue`:
- Around line 101-109: The catch block after paypal.createInstance currently
only emits the error but leaves ready false and messagesSession possibly unset;
update the catch to set a local error/ref (e.g., error.value = err) and clear
any partial session state (e.g., messagesSession.value = null) and explicitly
set ready.value to false before emitting the error so the component can switch
out of its loading/placeholder state; adjust the catch handling around
paypal.createInstance / instance.createPayPalMessages to ensure ready,
messagesSession, and the new error ref are consistently updated on failure.
- Around line 77-81: The call to useScriptPayPal is passing non-schema keys
merchantId and partnerAttributionId; update the call so its input only includes
the PayPalInput schema fields (clientId, clientToken, sandbox and
paypalScriptOptions) and remove the spread entries for merchantId and
partnerAttributionId, leaving those values to be passed later to createInstance
(referenced in createInstance where merchantId and partnerAttributionId are
already handled).

In `@src/runtime/registry/paypal.ts`:
- Around line 2-10: The exported runtime types were updated to use
PayPalV6Namespace (PayPalApi.paypal) but the generated registry-types.json still
references the old PayPalNamespace (v5); regenerate the registry types so the
public API matches the new symbol names: run the repository's type/registry
generation step to refresh src/registry-types.json, confirm PayPalV6Namespace
(not PayPalNamespace) appears in the JSON, and ensure any consumers or exported
declarations reference the PayPalV6Namespace/PayPalApi symbols consistently.

---

Outside diff comments:
In `@src/runtime/registry/schemas.ts`:
- Around line 587-602: The PayPalOptions schema currently allows both clientId
and clientToken to be omitted despite doc comments; update PayPalOptions to
enforce that at least one of clientId or clientToken is present by either (a)
creating a union type of two schemas (one requiring clientId, one requiring
clientToken) or (b) adding a refinement/validation to the existing PayPalOptions
object that checks that (clientId || clientToken) is truthy and returns a clear
validation error; target the PayPalOptions definition and the
clientId/clientToken fields to implement this guard.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9eaea169-c2dc-41e1-9159-929ae78d5717

📥 Commits

Reviewing files that changed from the base of the PR and between da46f08 and 7531414.

📒 Files selected for processing (9)
  • docs/content/scripts/paypal.md
  • playground/pages/third-parties/paypal/nuxt-scripts.vue
  • scripts/generate-registry-types.ts
  • src/runtime/components/ScriptPayPalButtons.vue
  • src/runtime/components/ScriptPayPalMarks.vue
  • src/runtime/components/ScriptPayPalMessages.vue
  • src/runtime/registry/paypal.ts
  • src/runtime/registry/schemas.ts
  • src/script-meta.ts
💤 Files with no reviewable changes (2)
  • src/runtime/components/ScriptPayPalMarks.vue
  • scripts/generate-registry-types.ts

Comment thread src/runtime/components/ScriptPayPalButtons.vue
Comment thread src/runtime/components/ScriptPayPalMessages.vue
Comment thread src/runtime/components/ScriptPayPalMessages.vue
Comment thread src/runtime/registry/paypal.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/script-sizes.json (1)

55-55: Normalize non-script resource URLs before persisting snapshots.

Line 55 stores a beacon URL with volatile query params (h, sid, cid), which can create noisy diffs and leak run-specific identifiers into git history. Recommend sanitizing query/hash for non-script resources in scripts/generate-sizes.ts before writing url.

♻️ Proposed generator hardening
+function sanitizeResourceUrl(rawUrl: string, initiatorType: string): string {
+  if (initiatorType === 'script')
+    return rawUrl
+  try {
+    const u = new URL(rawUrl)
+    u.search = ''
+    u.hash = ''
+    return u.toString()
+  }
+  catch {
+    return rawUrl
+  }
+}
...
-      const detail: ScriptSizeDetail = {
-        url: entry.name,
+      const detail: ScriptSizeDetail = {
+        url: sanitizeResourceUrl(entry.name, entry.initiatorType),
         transferKb: round(transferBytes),
         decodedKb: round(decodedBytes),
         encoding,
         durationMs: Math.round(entry.duration),
         initiatorType: entry.initiatorType,
         protocol: entry.nextHopProtocol || 'unknown',
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/script-sizes.json` at line 55, The stored beacon URL includes volatile
query parameters and should be sanitized before persisting; update the generator
in scripts/generate-sizes.ts (the function that builds/writes the snapshot
entries, e.g., generateSizes or the writeSnapshot flow) to detect non-script
resources (by MIME/content-type or by file extension) and normalize their URLs
by removing query string and hash or stripping known volatile params such as h,
sid, cid before assigning to the url property; implement a small normalizeUrl
helper used where snapshot objects are created so persisted
src/script-sizes.json contains stable, query-free URLs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/script-sizes.json`:
- Line 55: The stored beacon URL includes volatile query parameters and should
be sanitized before persisting; update the generator in
scripts/generate-sizes.ts (the function that builds/writes the snapshot entries,
e.g., generateSizes or the writeSnapshot flow) to detect non-script resources
(by MIME/content-type or by file extension) and normalize their URLs by removing
query string and hash or stripping known volatile params such as h, sid, cid
before assigning to the url property; implement a small normalizeUrl helper used
where snapshot objects are created so persisted src/script-sizes.json contains
stable, query-free URLs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8289def1-7a80-45df-a2fb-46fb3f202666

📥 Commits

Reviewing files that changed from the base of the PR and between 7531414 and f1d5868.

📒 Files selected for processing (1)
  • src/script-sizes.json

- Add `failed` state to Buttons/Messages so error slot renders on
  createInstance failure instead of stuck loading
- Remove non-schema keys (merchantId, partnerAttributionId) from
  useScriptPayPal call in Messages (they're createInstance options)
- Enforce at least one of clientId/clientToken in PayPalOptions via union
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/runtime/components/ScriptPayPalButtons.vue`:
- Around line 143-150: The placeholder/loading slots can still render when
status === 'error' because their v-if conditions don't exclude the error state;
update the gating so placeholder and loading only render when status is not
'error' (e.g., add status !== 'error' to the v-if expressions or short-circuit
render the error slot first), ensuring the error slot (name="error") exclusively
shows when status === 'error' or failed is true; adjust conditions referencing
status, ready, and failed in the ScriptPayPalButtons component to prevent
overlap between placeholder/loading and the error state.

In `@src/runtime/components/ScriptPayPalMessages.vue`:
- Around line 146-153: The placeholder and loading slots can render alongside
the error slot because their v-if checks don't exclude status === 'error';
update the slot predicates in ScriptPayPalMessages.vue so both the placeholder
(currently using !ready && !failed) and loading (currently using status !==
'awaitingLoad' && !ready && !failed) also require status !== 'error' (or
equivalently ensure status === 'error' is the exclusive branch for the error
slot). Concretely, tighten the conditions for the placeholder and loading slots
to include a check against status === 'error' (or add status !== 'error') while
leaving the error slot (status === 'error' || failed) as the exclusive branch.

In `@src/runtime/registry/schemas.ts`:
- Around line 587-612: The PayPalOptions union currently allows a clientId-only
branch which will break PayPal SDK v6 (its createInstance() requires
clientToken); update the PayPalOptions schema so clientToken is required and
clientId is optional (remove the branch that mandates clientId-only and ensure
the schema reflects clientToken as required), then verify usages such as
createInstance() calls in ScriptPayPalButtons.vue and ScriptPayPalMessages.vue
rely on clientToken being present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5ca780b9-601c-40a3-8084-cd70d0be858d

📥 Commits

Reviewing files that changed from the base of the PR and between f1d5868 and d20b17a.

📒 Files selected for processing (3)
  • src/runtime/components/ScriptPayPalButtons.vue
  • src/runtime/components/ScriptPayPalMessages.vue
  • src/runtime/registry/schemas.ts

Comment thread src/runtime/components/ScriptPayPalButtons.vue Outdated
Comment thread src/runtime/components/ScriptPayPalMessages.vue Outdated
Comment on lines +587 to +612
export const PayPalOptions = union([
object({
/**
* Your PayPal client ID.
* @see https://developer.paypal.com/sdk/js/reference/
*/
clientId: string(),
clientToken: optional(string()),
/**
* Use the PayPal sandbox environment. Defaults to `true` in development.
*/
sandbox: optional(boolean()),
}),
object({
clientId: optional(string()),
/**
* A server-generated client token for authentication.
* @see https://docs.paypal.ai/payments/methods/paypal/sdk/js/v6/paypal-checkout
*/
clientToken: string(),
/**
* Use the PayPal sandbox environment. Defaults to `true` in development.
*/
sandbox: optional(boolean()),
}),
])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, check the current schema implementation
cat -n src/runtime/registry/schemas.ts | sed -n '587,612p'

Repository: nuxt/scripts

Length of output: 954


🏁 Script executed:

# Find where PayPalOptions is imported/used
rg "PayPalOptions" --type ts --type vue -A 2 -B 2

Repository: nuxt/scripts

Length of output: 83


🏁 Script executed:

# Check the PayPal components to see how they use the config
cat -n src/runtime/components/ScriptPayPalButtons.vue | head -100

Repository: nuxt/scripts

Length of output: 3686


🏁 Script executed:

# Look for any type definitions or imports related to PayPal
rg "paypal|PayPal" --type ts --type vue -l | head -20

Repository: nuxt/scripts

Length of output: 83


🏁 Script executed:

# Search for CreateInstanceOptions references and usage
rg "CreateInstanceOptions" -A 5 -B 5

Repository: nuxt/scripts

Length of output: 3646


🏁 Script executed:

# Check the paypal registry implementation
cat -n src/runtime/registry/paypal.ts | head -80

Repository: nuxt/scripts

Length of output: 1416


🏁 Script executed:

# Look for any type definitions or comments about CreateInstanceOptions
rg "clientToken|clientId" src/runtime/registry/ -A 3 -B 1

Repository: nuxt/scripts

Length of output: 2500


🌐 Web query:

PayPal SDK v6 CreateInstanceOptions clientToken required vs clientId

💡 Result:

In PayPal JavaScript SDK v6, CreateInstanceOptions requires clientToken and does not take clientId.

  • clientToken (required): You must call window.paypal.createInstance({ clientToken, ... }) with a browser-safe, short‑lived token generated on your server. PayPal documents it as generated using your PayPal client ID + secret, expiring in ~15 minutes. [1]
  • clientId (not used in createInstance): In v6, the browser fetches a client token from your server before creating the SDK instance; the client ID is part of your server credentials used to mint that token, not something you pass to createInstance. [2]
  • The official TypeScript typings/example for v6 likewise show createInstance({ clientToken: "..." }) (no clientId). [3]

So: use clientId (and secret) on the server to generate a clientToken, then pass only clientToken into createInstance in the browser.

Sources: [1] [2] [3]


🏁 Script executed:

# Check if there are any tests or other usages that show clientId-only configs working
rg "createInstance" -A 3 -B 3

Repository: nuxt/scripts

Length of output: 1238


🏁 Script executed:

# Look at ScriptPayPalMessages.vue to confirm similar issue
cat -n src/runtime/components/ScriptPayPalMessages.vue | sed -n '75,110p'

Repository: nuxt/scripts

Length of output: 1618


PayPalOptions schema allows clientId-only configuration that will fail at SDK v6 runtime.

The first union branch permits clientId without clientToken, but PayPal SDK v6's createInstance() method requires only clientTokenclientId is not accepted as a parameter. The components (ScriptPayPalButtons.vue and ScriptPayPalMessages.vue) conditionally pass either one to createInstance(), meaning a clientId-only config will fail when the SDK is initialized.

Require clientToken and make clientId optional in the schema to enforce the SDK v6 contract:

Suggested fix
-export const PayPalOptions = union([
-  object({
-    clientId: string(),
-    clientToken: optional(string()),
-    sandbox: optional(boolean()),
-  }),
-  object({
-    clientId: optional(string()),
-    clientToken: string(),
-    sandbox: optional(boolean()),
-  }),
-])
+export const PayPalOptions = object({
+  /**
+   * A server-generated client token for SDK v6 initialization.
+   */
+  clientToken: string(),
+  /**
+   * Optional merchant app identifier kept for upstream integrations/telemetry.
+   */
+  clientId: optional(string()),
+  sandbox: optional(boolean()),
+})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const PayPalOptions = union([
object({
/**
* Your PayPal client ID.
* @see https://developer.paypal.com/sdk/js/reference/
*/
clientId: string(),
clientToken: optional(string()),
/**
* Use the PayPal sandbox environment. Defaults to `true` in development.
*/
sandbox: optional(boolean()),
}),
object({
clientId: optional(string()),
/**
* A server-generated client token for authentication.
* @see https://docs.paypal.ai/payments/methods/paypal/sdk/js/v6/paypal-checkout
*/
clientToken: string(),
/**
* Use the PayPal sandbox environment. Defaults to `true` in development.
*/
sandbox: optional(boolean()),
}),
])
export const PayPalOptions = object({
/**
* A server-generated client token for SDK v6 initialization.
*/
clientToken: string(),
/**
* Optional merchant app identifier kept for upstream integrations/telemetry.
*/
clientId: optional(string()),
/**
* Use the PayPal sandbox environment. Defaults to `true` in development.
*/
sandbox: optional(boolean()),
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/registry/schemas.ts` around lines 587 - 612, The PayPalOptions
union currently allows a clientId-only branch which will break PayPal SDK v6
(its createInstance() requires clientToken); update the PayPalOptions schema so
clientToken is required and clientId is optional (remove the branch that
mandates clientId-only and ensure the schema reflects clientToken as required),
then verify usages such as createInstance() calls in ScriptPayPalButtons.vue and
ScriptPayPalMessages.vue rely on clientToken being present.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/runtime/components/ScriptPayPalButtons.vue`:
- Line 139: The root container in ScriptPayPalButtons.vue is missing the ref
that useScriptTriggerElement expects; update the element that currently has
v-bind="rootAttrs" to also include ref="rootEl" so the root trigger ref from
useScriptTriggerElement (rootEl) is bound to the DOM node (ensure the component
still spreads rootAttrs and keeps the same element). This ties the rootEl used
by useScriptTriggerElement to the template and enables the trigger logic to
work.
- Around line 140-142: The default scoped slot is currently rendered with v-show
and receives undefined sdkInstance when ready is false; keep the container div
with ref="el" mounted but move the readiness check onto the slot so the slot is
only mounted when ready is true (e.g., use a template or v-if on the <slot
name="default" :sdk-instance="sdkInstance" /> itself), ensuring the slot only
renders when the component's ready state provides a defined sdkInstance in
ScriptPayPalButtons.vue.

In `@src/runtime/components/ScriptPayPalMessages.vue`:
- Line 142: The root DOM element isn't attaching the ref created as rootEl and
passed into useScriptTriggerElement, so trigger-based loading (e.g., trigger:
'visible') won't work; update the root node that currently spreads rootAttrs
(rootAttrs) to also attach the ref by binding rootEl (e.g., add a ref binding
referencing the rootEl used with useScriptTriggerElement) so the hook can
observe the actual element.
- Around line 143-154: The default slot is being rendered twice and mounted
before readiness; change the default slot to render only when ready and remove
the stray unnamed slot. Specifically, replace the v-show usage on the named
default slot (the element passing :messages-session) with v-if="ready" so it
only mounts when ready, keep the named slot signature
(:messages-session="messagesSession"), and delete the trailing unnamed <slot />
at the end; ensure other conditional slots (loading/awaitingLoad/error) remain
unchanged and continue to reference status and failed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c635a5a-c499-4d4c-8cb9-b6cd4dfe741a

📥 Commits

Reviewing files that changed from the base of the PR and between d20b17a and 215627d.

📒 Files selected for processing (2)
  • src/runtime/components/ScriptPayPalButtons.vue
  • src/runtime/components/ScriptPayPalMessages.vue

Comment thread src/runtime/components/ScriptPayPalButtons.vue Outdated
Comment thread src/runtime/components/ScriptPayPalButtons.vue Outdated
Comment thread src/runtime/components/ScriptPayPalMessages.vue Outdated
Comment thread src/runtime/components/ScriptPayPalMessages.vue Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/registry-types.json`:
- Line 798: The registry-types.json contains outdated PayPal v5 typings (e.g.,
PayPalNamespace and ScriptPayPalMarksProps) that must be updated to match the
PayPal v6 runtime API; regenerate the file by rerunning the project's registry
type generation script/command (the same tool you used to produce
src/registry-types.json originally) so that PayPalNamespace and
ScriptPayPalMarksProps are removed/replaced with the v6 types and the published
types align with the runtime API.

In `@src/runtime/components/ScriptPayPalButtons.vue`:
- Around line 58-59: The auth resolution is inconsistent between useScriptPayPal
and createInstance: ensure both use the same resolved credentials by extracting
a single auth-resolver that merges props and paypalScriptOptions (preserve
explicit props over paypalScriptOptions or vice versa as intended) and use that
resolved auth when initializing the SDK and when calling createInstance; update
useScriptPayPal to call the shared resolver (referencing paypalScriptOptions and
props) and pass the resolved auth into createInstance (or have createInstance
accept the resolved auth) so initialization and instance creation always use
identical credentials.

In `@src/runtime/components/ScriptPayPalMessages.vue`:
- Around line 59-60: The loader input and createInstance are using different
credential sources which can cause mismatched clientId/clientToken
(paypalScriptOptions vs top-level props); to fix, compute a single resolved auth
object (merge top-level props' clientId/clientToken with
paypalScriptOptions.clientId/clientToken, preferring explicit
paypalScriptOptions values) inside the ScriptPayPalMessages setup, then pass
that resolvedAuth to both the loader input and createInstance calls so both use
the same credentials and avoid default 'test' mismatches.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 728e1e94-6d73-40a3-a20a-3e409d16e514

📥 Commits

Reviewing files that changed from the base of the PR and between 215627d and 4bb848d.

📒 Files selected for processing (3)
  • src/registry-types.json
  • src/runtime/components/ScriptPayPalButtons.vue
  • src/runtime/components/ScriptPayPalMessages.vue

Comment thread src/registry-types.json
Comment on lines +58 to 59
paypalScriptOptions?: Partial<PayPalInput>
}>(), {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Auth resolution is inconsistent between useScriptPayPal and createInstance.

Line 78 allows credentials from paypalScriptOptions, but Line 87-89 rebuilds auth only from props. This can initialize the SDK with one credential source and create the instance with another.

🔧 Suggested fix
-  paypalScriptOptions?: Partial<PayPalInput>
+  paypalScriptOptions?: Partial<Pick<PayPalInput, 'sandbox' | 'clientId' | 'clientToken'>>
@@
+const resolvedAuth = computed(() => {
+  const clientToken = props.clientToken ?? props.paypalScriptOptions?.clientToken
+  if (clientToken)
+    return { clientToken }
+  return { clientId: props.paypalScriptOptions?.clientId ?? props.clientId }
+})
+
 const { onLoaded, status } = useScriptPayPal({
-  ...(props.clientToken ? { clientToken: props.clientToken } : { clientId: props.clientId }),
   ...props.paypalScriptOptions,
+  ...resolvedAuth.value,
 })
@@
     const instanceOptions = {
-      ...(props.clientToken
-        ? { clientToken: props.clientToken }
-        : { clientId: props.clientId }),
+      ...resolvedAuth.value,
       components: props.components,
       ...(props.pageType && { pageType: props.pageType }),

Also applies to: 77-80, 86-95

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/ScriptPayPalButtons.vue` around lines 58 - 59, The
auth resolution is inconsistent between useScriptPayPal and createInstance:
ensure both use the same resolved credentials by extracting a single
auth-resolver that merges props and paypalScriptOptions (preserve explicit props
over paypalScriptOptions or vice versa as intended) and use that resolved auth
when initializing the SDK and when calling createInstance; update
useScriptPayPal to call the shared resolver (referencing paypalScriptOptions and
props) and pass the resolved auth into createInstance (or have createInstance
accept the resolved auth) so initialization and instance creation always use
identical credentials.

Comment on lines 59 to 60
paypalScriptOptions?: Partial<PayPalInput>
}>(), {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use one resolved auth source for both loader input and createInstance.

Line 78 can be overridden by paypalScriptOptions.clientId/clientToken, but Line 90 still uses only top-level props. This can produce mismatched credentials and runtime init failures (especially with default clientId: 'test').

🔧 Suggested fix
-  paypalScriptOptions?: Partial<PayPalInput>
+  paypalScriptOptions?: Partial<Pick<PayPalInput, 'sandbox' | 'clientId' | 'clientToken'>>
@@
+const resolvedAuth = computed(() => {
+  const clientToken = props.clientToken ?? props.paypalScriptOptions?.clientToken
+  if (clientToken)
+    return { clientToken }
+  return { clientId: props.paypalScriptOptions?.clientId ?? props.clientId }
+})
+
 const { onLoaded, status } = useScriptPayPal({
-  ...(props.clientToken ? { clientToken: props.clientToken } : { clientId: props.clientId }),
   ...props.paypalScriptOptions,
+  ...resolvedAuth.value,
 })
@@
     const instanceOptions = {
-      ...(props.clientToken
-        ? { clientToken: props.clientToken }
-        : { clientId: props.clientId }),
+      ...resolvedAuth.value,
       components,
       ...(props.pageType && { pageType: props.pageType }),
       ...(props.locale && { locale: props.locale }),

Also applies to: 78-80, 89-98

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/ScriptPayPalMessages.vue` around lines 59 - 60, The
loader input and createInstance are using different credential sources which can
cause mismatched clientId/clientToken (paypalScriptOptions vs top-level props);
to fix, compute a single resolved auth object (merge top-level props'
clientId/clientToken with paypalScriptOptions.clientId/clientToken, preferring
explicit paypalScriptOptions values) inside the ScriptPayPalMessages setup, then
pass that resolvedAuth to both the loader input and createInstance calls so both
use the same credentials and avoid default 'test' mismatches.

@harlan-zw harlan-zw merged commit 89e3333 into main Mar 5, 2026
9 checks passed
@harlan-zw harlan-zw deleted the feat/paypal-sdk-v6 branch March 5, 2026 13:27
@harlan-zw harlan-zw mentioned this pull request Mar 19, 2026
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.

1 participant