diff --git a/docs/app/components/content/PayPalDemo.vue b/docs/app/components/content/PayPalDemo.vue
new file mode 100644
index 00000000..f299c6fb
--- /dev/null
+++ b/docs/app/components/content/PayPalDemo.vue
@@ -0,0 +1,32 @@
+
+
+
+
+ Disabled
+
+
+
+
+
+
+
+
diff --git a/docs/content/scripts/payments/paypal.md b/docs/content/scripts/payments/paypal.md
new file mode 100644
index 00000000..8709fa5a
--- /dev/null
+++ b/docs/content/scripts/payments/paypal.md
@@ -0,0 +1,80 @@
+---
+title: PayPal
+description: Use PayPal in your Nuxt app.
+links:
+ - label: useScriptPayPal
+ icon: i-simple-icons-github
+ to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/paypal.ts
+ size: xs
+ - label: ""
+ icon: i-simple-icons-github
+ to: https://github.com/nuxt/scripts/blob/main/src/runtime/components/ScriptPayPalButtons.vue
+ size: xs
+ - label: ""
+ icon: i-simple-icons-github
+ to: https://github.com/nuxt/scripts/blob/main/src/runtime/components/ScriptPayPalMarks.vue
+ size: xs
+ - label: ""
+ icon: i-simple-icons-github
+ to: https://github.com/nuxt/scripts/blob/main/src/runtime/components/ScriptPayPalMessages.vue
+ size: xs
+---
+
+[PayPal](https://www.paypal.com) is a popular payment gateway that allows you to accept payments online.
+
+Nuxt Scripts provides multiple PayPal features:
+- `useScriptPayPal` composable which loads the script `https://www.paypal.com/sdk/js`.
+- `ScriptPayPalButtons` component that allows you to embed [PayPal Buttons](https://developer.paypal.com/sdk/js/reference/#buttons) on your site.
+- `ScriptPayPalMarks` component that allows you to embed [PayPal Marks](https://developer.paypal.com/sdk/js/reference/#marks) on your site.
+- `ScriptPayPalMessages` component that allows you to embed [PayPal Messages](https://developer.paypal.com/studio/checkout/pay-later/us/customize/reference) on your site.
+
+## Types
+
+To use the PayPal with full TypeScript support, you will need
+to install the `@paypal/paypal-js` dependency.
+
+```bash
+pnpm add -D @paypal/paypal-js
+```
+### Demo
+
+::code-group
+
+:pay-pal-demo{label="Output"}
+
+```vue [Input]
+
+
+
+
+ Disabled
+
+
+
+
+
+
+
+
+```
+
+::
diff --git a/package.json b/package.json
index 89fc41e5..6bbcccdb 100644
--- a/package.json
+++ b/package.json
@@ -73,6 +73,7 @@
},
"peerDependencies": {
"@stripe/stripe-js": "^7.0.0",
+ "@paypal/paypal-js": "^8.1.2",
"@types/google.maps": "^3.58.1",
"@types/vimeo__player": "^2.18.3",
"@types/youtube": "^0.1.0",
@@ -82,6 +83,9 @@
"@stripe/stripe-js": {
"optional": true
},
+ "@paypal/paypal-js": {
+ "optional": true
+ },
"@types/google.maps": {
"optional": true
},
diff --git a/playground/pages/index.vue b/playground/pages/index.vue
index 2f9e45fe..fe059d1d 100644
--- a/playground/pages/index.vue
+++ b/playground/pages/index.vue
@@ -54,6 +54,10 @@ const thirdParties = [
name: 'Stripe',
path: '/third-parties/stripe/nuxt-scripts',
},
+ {
+ name: 'PayPal',
+ path: '/third-parties/paypal/nuxt-scripts',
+ },
{
name: 'Segment',
path: '/third-parties/segment',
diff --git a/playground/pages/third-parties/paypal/nuxt-scripts.vue b/playground/pages/third-parties/paypal/nuxt-scripts.vue
new file mode 100644
index 00000000..f299c6fb
--- /dev/null
+++ b/playground/pages/third-parties/paypal/nuxt-scripts.vue
@@ -0,0 +1,32 @@
+
+
+
+
+ Disabled
+
+
+
+
+
+
+
+
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6300f566..ee11aa83 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -23,6 +23,9 @@ importers:
'@nuxt/kit':
specifier: 'catalog:'
version: 4.0.3(magicast@0.3.5)
+ '@paypal/paypal-js':
+ specifier: ^8.1.2
+ version: 8.2.0
'@stripe/stripe-js':
specifier: ^7.0.0
version: 7.8.0
@@ -1571,6 +1574,9 @@ packages:
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
engines: {node: '>= 10.0.0'}
+ '@paypal/paypal-js@8.2.0':
+ resolution: {integrity: sha512-hLg5wNORW3WiyMiRNJOm6cN2IqjPlClpxd971bEdm0LNpbbejQZYtesb0/0arTnySSbGcxg7MxjkZ/N5Z5qBNQ==}
+
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@@ -5749,6 +5755,9 @@ packages:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
+ promise-polyfill@8.3.0:
+ resolution: {integrity: sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==}
+
prompts@2.4.2:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
@@ -9063,6 +9072,10 @@ snapshots:
'@parcel/watcher-win32-ia32': 2.5.1
'@parcel/watcher-win32-x64': 2.5.1
+ '@paypal/paypal-js@8.2.0':
+ dependencies:
+ promise-polyfill: 8.3.0
+
'@pkgjs/parseargs@0.11.0':
optional: true
@@ -14074,6 +14087,8 @@ snapshots:
process@0.11.10: {}
+ promise-polyfill@8.3.0: {}
+
prompts@2.4.2:
dependencies:
kleur: 3.0.3
diff --git a/src/registry.ts b/src/registry.ts
index c4615b2f..aa56ce78 100644
--- a/src/registry.ts
+++ b/src/registry.ts
@@ -205,6 +205,16 @@ export async function registry(resolve?: (path: string, opts?: ResolvePathOption
from: await resolve('./runtime/registry/lemon-squeezy'),
},
},
+ {
+ label: 'PayPal',
+ src: false, // should not be bundled
+ category: 'payments',
+ logo: ` `,
+ import: {
+ name: 'useSciptPayPal',
+ from: await resolve('./runtime/registry/paypal'),
+ },
+ },
// content
{
label: 'Vimeo Player',
diff --git a/src/runtime/components/ScriptPayPalButtons.vue b/src/runtime/components/ScriptPayPalButtons.vue
new file mode 100644
index 00000000..736286f8
--- /dev/null
+++ b/src/runtime/components/ScriptPayPalButtons.vue
@@ -0,0 +1,187 @@
+
+
+
+
+
diff --git a/src/runtime/components/ScriptPayPalMarks.vue b/src/runtime/components/ScriptPayPalMarks.vue
new file mode 100644
index 00000000..a77661ff
--- /dev/null
+++ b/src/runtime/components/ScriptPayPalMarks.vue
@@ -0,0 +1,102 @@
+
+
+
+
+
diff --git a/src/runtime/components/ScriptPayPalMessages.vue b/src/runtime/components/ScriptPayPalMessages.vue
new file mode 100644
index 00000000..458cdd04
--- /dev/null
+++ b/src/runtime/components/ScriptPayPalMessages.vue
@@ -0,0 +1,136 @@
+
+
+
+
+
diff --git a/src/runtime/registry/paypal.ts b/src/runtime/registry/paypal.ts
new file mode 100644
index 00000000..3ca6ceff
--- /dev/null
+++ b/src/runtime/registry/paypal.ts
@@ -0,0 +1,109 @@
+import { withQuery } from 'ufo'
+import type { PayPalNamespace } from '@paypal/paypal-js'
+import { useRegistryScript } from '../utils'
+import { object, string, optional, array, union, boolean } from '#nuxt-scripts-validator'
+import type { RegistryScriptInput } from '#nuxt-scripts/types'
+
+export interface PayPalApi {
+ paypal: PayPalNamespace
+}
+
+declare global {
+ interface Window extends PayPalApi {
+ }
+}
+
+export const PayPalOptions = object({
+ clientId: string(),
+ buyerCountry: optional(string()),
+ commit: optional(string()),
+ components: optional(union([string(), array(string())])),
+ currency: optional(string()),
+ debug: optional(union([string(), boolean()])),
+ disableFunding: optional(union([string(), array(string())])),
+ enableFunding: optional(union([string(), array(string())])),
+ integrationDate: optional(string()),
+ intent: optional(string()),
+ locale: optional(string()),
+ /**
+ * loadScript() supports an array for merchantId, even though
+ * merchant-id technically may not contain multiple values.
+ * For an array with a length of > 1 it automatically sets
+ * merchantId to "*" and moves the actual values to dataMerchantId
+ */
+ merchantId: optional(union([string(), array(string())])),
+ partnerAttributionId: optional(string()),
+ vault: optional(union([string(), boolean()])),
+ // own props
+ sandbox: optional(boolean()),
+})
+
+export type PayPalInput = RegistryScriptInput
+
+export function useScriptPayPal(_options?: PayPalInput) {
+ return useRegistryScript('paypal', (options) => {
+ let dataMerchantId = undefined
+
+ if (Array.isArray(options?.merchantId) && options?.merchantId.length > 1) {
+ dataMerchantId = JSON.stringify(options.merchantId)
+ options.merchantId = '*'
+ }
+
+ if (Array.isArray(options?.components)) {
+ options.components = options.components.join(',')
+ }
+
+ if (Array.isArray(options?.disableFunding)) {
+ options.disableFunding = options.disableFunding.join(',')
+ }
+
+ if (Array.isArray(options?.enableFunding)) {
+ options.enableFunding = options.enableFunding.join(',')
+ }
+
+ if (options?.sandbox === undefined) {
+ options.sandbox = import.meta.dev
+ }
+
+ let components = ['buttons', 'messages', 'marks', 'card-fields', 'funding-eligibility'].join(',')
+
+ if (options.components) {
+ if (Array.isArray(options.components)) {
+ components = options.components.join(',')
+ }
+ else {
+ components = options.components
+ }
+ }
+
+ return {
+ scriptInput: {
+ 'src': withQuery(options.sandbox ? 'https://www.sandbox.paypal.com/sdk/js' : 'https://www.paypal.com/sdk/js', {
+ 'client-id': options.clientId,
+ 'buyer-country': options.buyerCountry,
+ 'commit': options.commit,
+ 'components': components,
+ 'currency': options.currency,
+ 'debug': options.debug,
+ 'disable-funding': options.disableFunding,
+ 'enable-funding': options.enableFunding,
+ 'integration-date': options.integrationDate,
+ 'intent': options.intent,
+ 'locale': options.locale,
+ 'vault': options.vault,
+ }),
+ 'data-merchant-id': dataMerchantId,
+ 'data-partner-attribution-id': options.partnerAttributionId, // TODO: maybe nuxt specific default
+ },
+ schema: import.meta.dev ? PayPalOptions : undefined,
+ // trigger: 'client',
+ scriptOptions: {
+ use() {
+ return {
+ paypal: window.paypal,
+ }
+ },
+ },
+ }
+ }, _options)
+}