diff --git a/package.json b/package.json
index b258324c..370c9ef2 100644
--- a/package.json
+++ b/package.json
@@ -99,6 +99,7 @@
     "@nuxt/eslint-config": "^0.5.7",
     "@nuxt/module-builder": "^0.8.4",
     "@nuxt/test-utils": "3.14.2",
+    "@paypal/paypal-js": "^8.1.2",
     "@types/semver": "^7.5.8",
     "@typescript-eslint/typescript-estree": "^8.7.0",
     "@unhead/schema": "1.11.6",
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..38d1cd0c
--- /dev/null
+++ b/playground/pages/third-parties/paypal/nuxt-scripts.vue
@@ -0,0 +1,35 @@
+<template>
+  <div>
+    <h2>Buttons</h2>
+    <ScriptPaypalButtons
+      class="border border-gray-200 dark:border-gray-800 rounded-lg"
+      :button-options="buttonOptions"
+      :disabled="disabled"
+    />
+    <label>
+      Disabled
+      <input v-model="disabled" type="checkbox">
+    </label>
+    <h2>Marks</h2>
+    <ScriptPaypalMarks />
+    <h2>Messages</h2>
+    <ScriptPaypalMessages :messages-options="{ style: { color: 'white-no-border', layout: 'flex' } }" />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, ref } from 'vue'
+import type { PayPalButtonsComponentOptions } from '@paypal/paypal-js'
+
+const buttonOptions = computed(() => ({
+  style: {
+    layout: 'vertical',
+    color: 'blue',
+    shape: 'rect',
+    label: 'paypal',
+  },
+  message: { amount: '10.00' },
+} satisfies PayPalButtonsComponentOptions))
+
+const disabled = ref(false)
+</script>
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 58c655f6..59a7ae74 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -90,7 +90,7 @@ importers:
         version: 3.7.0
       third-party-capital:
         specifier: 2.3.0
-        version: 3.0.0
+        version: 2.3.0
       ufo:
         specifier: ^1.5.4
         version: 1.5.4
@@ -116,6 +116,9 @@ importers:
       '@nuxt/test-utils':
         specifier: 3.14.2
         version: 3.14.2(h3@1.12.0)(magicast@0.3.5)(nitropack@2.9.7(magicast@0.3.5)(webpack-sources@3.2.3))(playwright-core@1.47.2)(rollup@4.21.3)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(vitest@2.1.1(@types/node@22.7.3)(terser@5.34.0))(vue-router@4.4.5(vue@3.5.9(typescript@5.6.2)))(vue@3.5.9(typescript@5.6.2))(webpack-sources@3.2.3)
+      '@paypal/paypal-js':
+        specifier: ^8.1.2
+        version: 8.1.2
       '@types/semver':
         specifier: ^7.5.8
         version: 7.5.8
@@ -175,7 +178,7 @@ importers:
         version: 1.5.1(magicast@0.3.5)(rollup@4.22.4)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(webpack-sources@3.2.3)
       '@nuxt/devtools-ui-kit':
         specifier: latest
-        version: 1.5.1(@nuxt/devtools@1.5.1(rollup@4.22.4)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(vue@3.5.9(typescript@5.6.2))(webpack-sources@3.2.3))(@unocss/webpack@0.62.4(rollup@4.22.4)(webpack@5.95.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.9)(fuse.js@7.0.0)(magicast@0.3.5)(nuxt@3.13.2(@parcel/watcher@2.4.1)(@types/node@22.7.3)(eslint@9.11.1(jiti@2.0.0))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.22.4)(terser@5.34.0)(typescript@5.6.2)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(webpack-sources@3.2.3))(postcss@8.4.47)(rollup@4.22.4)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(vue@3.5.9(typescript@5.6.2))(webpack-sources@3.2.3)(webpack@5.95.0(esbuild@0.23.1))
+        version: 1.5.2(@nuxt/devtools@1.5.1(rollup@4.22.4)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(vue@3.5.9(typescript@5.6.2))(webpack-sources@3.2.3))(@unocss/webpack@0.62.4(rollup@4.22.4)(webpack@5.95.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.9)(fuse.js@7.0.0)(magicast@0.3.5)(nuxt@3.13.2(@parcel/watcher@2.4.1)(@types/node@22.7.3)(eslint@9.11.1(jiti@2.0.0))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.22.4)(terser@5.34.0)(typescript@5.6.2)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(webpack-sources@3.2.3))(postcss@8.4.47)(rollup@4.22.4)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(vue@3.5.9(typescript@5.6.2))(webpack-sources@3.2.3)(webpack@5.95.0(esbuild@0.23.1))
       '@nuxt/kit':
         specifier: ^3.13.2
         version: 3.13.2(magicast@0.3.5)(rollup@4.22.4)(webpack-sources@3.2.3)
@@ -1333,11 +1336,21 @@ packages:
     peerDependencies:
       vite: '*'
 
+  '@nuxt/devtools-kit@1.5.2':
+    resolution: {integrity: sha512-IMbwflL/JLuK1JcM5yWKa+T5JGjwnCACZJw218/8bUTt/uTVgtkMueE+1/p9rhCWxvGQiT3xnCIXKhEg7xP58Q==}
+    peerDependencies:
+      vite: '*'
+
   '@nuxt/devtools-ui-kit@1.5.1':
     resolution: {integrity: sha512-/1B2AYXuuPePWVuoHd/UGIKR3z3vO2bW73UAEszpHVLc/OwLA19K9f5o91sgyamAi2Qb5NymAMc/UZL0ijN8uA==}
     peerDependencies:
       '@nuxt/devtools': 1.5.1
 
+  '@nuxt/devtools-ui-kit@1.5.2':
+    resolution: {integrity: sha512-J8rYJ3Pl6yZTybR6Wbq63RbsJMy3z4e2cQ/Ohiij2iCpZHEdqI22w0LALpmfH0mj1T/0903vWe+ce4jtyGO2zw==}
+    peerDependencies:
+      '@nuxt/devtools': 1.5.2
+
   '@nuxt/devtools-wizard@1.4.2':
     resolution: {integrity: sha512-TyhmPBg/xJKPOdnwR3DAh8KMUt6/0dUNABCxGVeY7PYbIiXt4msIGVJkBc4y+WwIJHOYPrSRClmZVsXQfRlB4A==}
     hasBin: true
@@ -1554,6 +1567,9 @@ packages:
     resolution: {integrity: sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==}
     engines: {node: '>= 10.0.0'}
 
+  '@paypal/paypal-js@8.1.2':
+    resolution: {integrity: sha512-EKshGSWRxLWU1NyPB9P1TiOkPajVmpTo5I9HuZKoSv8y2uk0XIskXqMkAJ/Y9qAg9iJyP102Jb/atX63tTy24w==}
+
   '@pkgjs/parseargs@0.11.0':
     resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
     engines: {node: '>=14'}
@@ -2072,6 +2088,10 @@ packages:
     resolution: {integrity: sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  '@typescript-eslint/scope-manager@8.8.1':
+    resolution: {integrity: sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
   '@typescript-eslint/type-utils@8.5.0':
     resolution: {integrity: sha512-N1K8Ix+lUM+cIDhL2uekVn/ZD7TZW+9/rwz8DclQpcQ9rk4sIL5CAlBC0CugWKREmDjBzI/kQqU4wkg46jWLYA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -2089,6 +2109,10 @@ packages:
     resolution: {integrity: sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  '@typescript-eslint/types@8.8.1':
+    resolution: {integrity: sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
   '@typescript-eslint/typescript-estree@8.5.0':
     resolution: {integrity: sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -2107,12 +2131,27 @@ packages:
       typescript:
         optional: true
 
+  '@typescript-eslint/typescript-estree@8.8.1':
+    resolution: {integrity: sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
   '@typescript-eslint/utils@8.5.0':
     resolution: {integrity: sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: ^8.57.0 || ^9.0.0
 
+  '@typescript-eslint/utils@8.8.1':
+    resolution: {integrity: sha512-/QkNJDbV0bdL7H7d0/y0qBbV2HTtf0TIyjSDTvvmQEzeVx8jEImEbLuOA4EsvE8gIgqMitns0ifb5uQhMj8d9w==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      eslint: ^8.57.0 || ^9.0.0
+
   '@typescript-eslint/visitor-keys@8.5.0':
     resolution: {integrity: sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -2121,6 +2160,10 @@ packages:
     resolution: {integrity: sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  '@typescript-eslint/visitor-keys@8.8.1':
+    resolution: {integrity: sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
   '@ungap/structured-clone@1.2.0':
     resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
 
@@ -5274,6 +5317,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'}
@@ -5895,8 +5941,8 @@ packages:
   thenify@3.3.1:
     resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
 
-  third-party-capital@3.0.0:
-    resolution: {integrity: sha512-zCrYkb4lnFTpsz0cyZ7c3q9aqQ5zX4po1Gy4utqOf8UkIV9Jx45P8ty6kpF1gLVvfkfNB36nEKv3isQhJv7Ftw==}
+  third-party-capital@2.3.0:
+    resolution: {integrity: sha512-p4rGOF4JCkI18HH3a1Vfd89Mg37TLlAsGmePBt20MA52frviYSBE6ToGOmTpqRC4EIlS5/4Owv6TpegFJUSkOg==}
 
   tiny-inflate@1.0.3:
     resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
@@ -7579,6 +7625,18 @@ snapshots:
       - supports-color
       - webpack-sources
 
+  '@nuxt/devtools-kit@1.5.2(magicast@0.3.5)(rollup@4.22.4)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(webpack-sources@3.2.3)':
+    dependencies:
+      '@nuxt/kit': 3.13.2(magicast@0.3.5)(rollup@4.22.4)(webpack-sources@3.2.3)
+      '@nuxt/schema': 3.13.2(rollup@4.22.4)(webpack-sources@3.2.3)
+      execa: 7.2.0
+      vite: 5.4.5(@types/node@22.7.3)(terser@5.34.0)
+    transitivePeerDependencies:
+      - magicast
+      - rollup
+      - supports-color
+      - webpack-sources
+
   '@nuxt/devtools-ui-kit@1.5.1(@nuxt/devtools@1.5.1(rollup@4.21.3)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(vue@3.5.9(typescript@5.6.2))(webpack-sources@3.2.3))(@unocss/webpack@0.62.4(rollup@4.21.3)(webpack@5.95.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.9)(fuse.js@7.0.0)(magicast@0.3.5)(nuxt@3.13.2(@parcel/watcher@2.4.1)(@types/node@22.7.3)(eslint@9.11.1(jiti@2.0.0))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.3)(terser@5.34.0)(typescript@5.6.2)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(webpack-sources@3.2.3))(postcss@8.4.47)(rollup@4.21.3)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(vue@3.5.9(typescript@5.6.2))(webpack-sources@3.2.3)(webpack@5.95.0(esbuild@0.23.1))':
     dependencies:
       '@iconify-json/carbon': 1.2.1
@@ -7627,14 +7685,14 @@ snapshots:
       - webpack
       - webpack-sources
 
-  '@nuxt/devtools-ui-kit@1.5.1(@nuxt/devtools@1.5.1(rollup@4.22.4)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(vue@3.5.9(typescript@5.6.2))(webpack-sources@3.2.3))(@unocss/webpack@0.62.4(rollup@4.22.4)(webpack@5.95.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.9)(fuse.js@7.0.0)(magicast@0.3.5)(nuxt@3.13.2(@parcel/watcher@2.4.1)(@types/node@22.7.3)(eslint@9.11.1(jiti@2.0.0))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.22.4)(terser@5.34.0)(typescript@5.6.2)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(webpack-sources@3.2.3))(postcss@8.4.47)(rollup@4.22.4)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(vue@3.5.9(typescript@5.6.2))(webpack-sources@3.2.3)(webpack@5.95.0(esbuild@0.23.1))':
+  '@nuxt/devtools-ui-kit@1.5.2(@nuxt/devtools@1.5.1(rollup@4.22.4)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(vue@3.5.9(typescript@5.6.2))(webpack-sources@3.2.3))(@unocss/webpack@0.62.4(rollup@4.22.4)(webpack@5.95.0(esbuild@0.23.1)))(@vue/compiler-core@3.5.9)(fuse.js@7.0.0)(magicast@0.3.5)(nuxt@3.13.2(@parcel/watcher@2.4.1)(@types/node@22.7.3)(eslint@9.11.1(jiti@2.0.0))(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.22.4)(terser@5.34.0)(typescript@5.6.2)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(webpack-sources@3.2.3))(postcss@8.4.47)(rollup@4.22.4)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(vue@3.5.9(typescript@5.6.2))(webpack-sources@3.2.3)(webpack@5.95.0(esbuild@0.23.1))':
     dependencies:
       '@iconify-json/carbon': 1.2.1
       '@iconify-json/logos': 1.2.0
       '@iconify-json/ri': 1.2.0
       '@iconify-json/tabler': 1.2.3
       '@nuxt/devtools': 1.5.1(rollup@4.22.4)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(vue@3.5.9(typescript@5.6.2))(webpack-sources@3.2.3)
-      '@nuxt/devtools-kit': 1.5.1(magicast@0.3.5)(rollup@4.22.4)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(webpack-sources@3.2.3)
+      '@nuxt/devtools-kit': 1.5.2(magicast@0.3.5)(rollup@4.22.4)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(webpack-sources@3.2.3)
       '@nuxt/kit': 3.13.2(magicast@0.3.5)(rollup@4.22.4)(webpack-sources@3.2.3)
       '@unocss/core': 0.62.4
       '@unocss/nuxt': 0.62.4(magicast@0.3.5)(postcss@8.4.47)(rollup@4.22.4)(vite@5.4.5(@types/node@22.7.3)(terser@5.34.0))(webpack-sources@3.2.3)(webpack@5.95.0(esbuild@0.23.1))
@@ -8810,6 +8868,10 @@ snapshots:
       '@parcel/watcher-win32-ia32': 2.4.1
       '@parcel/watcher-win32-x64': 2.4.1
 
+  '@paypal/paypal-js@8.1.2':
+    dependencies:
+      promise-polyfill: 8.3.0
+
   '@pkgjs/parseargs@0.11.0':
     optional: true
 
@@ -9190,7 +9252,7 @@ snapshots:
 
   '@stylistic/eslint-plugin@2.8.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
     dependencies:
-      '@typescript-eslint/utils': 8.5.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
+      '@typescript-eslint/utils': 8.8.1(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)
       eslint: 9.11.1(jiti@2.0.0)
       eslint-visitor-keys: 4.0.0
       espree: 10.1.0
@@ -9322,6 +9384,11 @@ snapshots:
       '@typescript-eslint/types': 8.5.0
       '@typescript-eslint/visitor-keys': 8.5.0
 
+  '@typescript-eslint/scope-manager@8.8.1':
+    dependencies:
+      '@typescript-eslint/types': 8.8.1
+      '@typescript-eslint/visitor-keys': 8.8.1
+
   '@typescript-eslint/type-utils@8.5.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
     dependencies:
       '@typescript-eslint/typescript-estree': 8.5.0(typescript@5.6.2)
@@ -9338,6 +9405,8 @@ snapshots:
 
   '@typescript-eslint/types@8.7.0': {}
 
+  '@typescript-eslint/types@8.8.1': {}
+
   '@typescript-eslint/typescript-estree@8.5.0(typescript@5.6.2)':
     dependencies:
       '@typescript-eslint/types': 8.5.0
@@ -9368,6 +9437,21 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@typescript-eslint/typescript-estree@8.8.1(typescript@5.6.2)':
+    dependencies:
+      '@typescript-eslint/types': 8.8.1
+      '@typescript-eslint/visitor-keys': 8.8.1
+      debug: 4.3.7
+      fast-glob: 3.3.2
+      is-glob: 4.0.3
+      minimatch: 9.0.5
+      semver: 7.6.3
+      ts-api-utils: 1.3.0(typescript@5.6.2)
+    optionalDependencies:
+      typescript: 5.6.2
+    transitivePeerDependencies:
+      - supports-color
+
   '@typescript-eslint/utils@8.5.0(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
     dependencies:
       '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1(jiti@2.0.0))
@@ -9379,6 +9463,17 @@ snapshots:
       - supports-color
       - typescript
 
+  '@typescript-eslint/utils@8.8.1(eslint@9.11.1(jiti@2.0.0))(typescript@5.6.2)':
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1(jiti@2.0.0))
+      '@typescript-eslint/scope-manager': 8.8.1
+      '@typescript-eslint/types': 8.8.1
+      '@typescript-eslint/typescript-estree': 8.8.1(typescript@5.6.2)
+      eslint: 9.11.1(jiti@2.0.0)
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
+
   '@typescript-eslint/visitor-keys@8.5.0':
     dependencies:
       '@typescript-eslint/types': 8.5.0
@@ -9389,6 +9484,11 @@ snapshots:
       '@typescript-eslint/types': 8.7.0
       eslint-visitor-keys: 3.4.3
 
+  '@typescript-eslint/visitor-keys@8.8.1':
+    dependencies:
+      '@typescript-eslint/types': 8.8.1
+      eslint-visitor-keys: 3.4.3
+
   '@ungap/structured-clone@1.2.0': {}
 
   '@unhead/addons@1.11.2(rollup@4.22.4)(webpack-sources@3.2.3)':
@@ -14002,6 +14102,8 @@ snapshots:
 
   process@0.11.10: {}
 
+  promise-polyfill@8.3.0: {}
+
   prompts@2.4.2:
     dependencies:
       kleur: 3.0.3
@@ -14837,7 +14939,7 @@ snapshots:
     dependencies:
       any-promise: 1.3.0
 
-  third-party-capital@3.0.0:
+  third-party-capital@2.3.0:
     dependencies:
       semver: 7.6.3
 
diff --git a/src/runtime/components/ScriptPaypalButtons.vue b/src/runtime/components/ScriptPaypalButtons.vue
new file mode 100644
index 00000000..a086cf1c
--- /dev/null
+++ b/src/runtime/components/ScriptPaypalButtons.vue
@@ -0,0 +1,188 @@
+<script setup lang="ts">
+import { computed, type HTMLAttributes, onMounted, ref, type ReservedProps, shallowRef, watch } from 'vue'
+import { defu } from 'defu'
+import type {
+  OnApproveActions,
+  OnApproveData,
+  OnCancelledActions,
+  OnClickActions,
+  OnShippingAddressChangeActions,
+  OnShippingAddressChangeData,
+  OnShippingOptionsChangeActions,
+  OnShippingOptionsChangeData,
+  PayPalButtonsComponent,
+  PayPalButtonsComponentOptions,
+} from '@paypal/paypal-js'
+import type { OnInitActions } from '@paypal/paypal-js/types/components/buttons'
+import { onBeforeUnmount, type PaypalInput, resolveComponent, useScriptPaypal, useScriptTriggerElement } from '#imports'
+import type { ElementScriptTrigger } from '#nuxt-scripts'
+
+const el = ref<HTMLDivElement | null>(null)
+const rootEl = ref<HTMLDivElement | null>(null)
+
+const props = withDefaults(defineProps<{
+  /**
+   * Customize the root element attributes.
+   */
+  rootAttrs?: HTMLAttributes & ReservedProps & Record<string, unknown>
+  /**
+   * Defines the trigger event to load the script.
+   */
+  trigger?: ElementScriptTrigger
+  /**
+   * The client id for the paypal script.
+   */
+  clientId?: string
+  /**
+   * The options for the paypal buttons.
+   */
+  buttonOptions?: PayPalButtonsComponentOptions
+  /**
+   * The paypal script options.
+   */
+  paypalScriptOptions?: Partial<PaypalInput>
+  /**
+   * Disables the paypal buttons.
+   */
+  disabled?: boolean
+}>(), {
+  trigger: 'visible',
+  clientId: 'test',
+  disabled: false,
+  buttonOptions: () => ({}),
+  paypalScriptOptions: () => ({}),
+})
+
+const ready = ref(false)
+
+const { onLoaded, status } = useScriptPaypal({
+  clientId: props.clientId,
+  ...props.paypalScriptOptions,
+})
+
+const emit = defineEmits<{
+  approve: [data: OnApproveData, actions: OnApproveActions]
+  error: [error: Record<string, unknown>]
+  cancel: [data: Record<string, unknown>, actions: OnCancelledActions]
+  clickButtons: [data: Record<string, unknown>, actions: OnClickActions]
+  shippingOptionsChange: [
+    data: OnShippingOptionsChangeData,
+    actions: OnShippingOptionsChangeActions,
+  ]
+  shippingAddressChange: [
+    data: OnShippingAddressChangeData,
+    actions: OnShippingAddressChangeActions,
+  ]
+  init: [data: Record<string, unknown>, actions: OnInitActions]
+}>()
+
+const initActions = shallowRef<OnInitActions | null>(null)
+
+const handleDisabled = () => {
+  if (!initActions.value) return
+  if (props.disabled) {
+    initActions.value.disable()
+  }
+  else {
+    initActions.value.enable()
+  }
+}
+
+const options = computed(() => {
+  const _options: PayPalButtonsComponentOptions = {
+    onApprove: async (data, actions) => {
+      emit('approve', data, actions)
+      return props.buttonOptions?.onApprove?.(data, actions)
+    },
+    onError: (err) => {
+      emit('error', err)
+      return props.buttonOptions?.onError?.(err)
+    },
+    onCancel: (data, actions) => {
+      emit('cancel', data, actions)
+      return props.buttonOptions?.onCancel?.(data, actions)
+    },
+    onClick: (data, actions) => {
+      emit('clickButtons', data, actions)
+      return props.buttonOptions?.onClick?.(data, actions)
+    },
+    onShippingOptionsChange: async (data, actions) => {
+      emit('shippingOptionsChange', data, actions)
+      return props.buttonOptions?.onShippingOptionsChange?.(data, actions)
+    },
+    onShippingAddressChange: async (data, actions) => {
+      emit('shippingAddressChange', data, actions)
+      return props.buttonOptions?.onShippingAddressChange?.(data, actions)
+    },
+    onInit: (data, actions) => {
+      initActions.value = actions
+      actions.disable()
+      handleDisabled()
+      emit('init', data, actions)
+      return props.buttonOptions?.onInit?.(data, actions)
+    },
+  }
+  return defu(_options, props.buttonOptions)
+})
+
+watch(() => props.disabled, handleDisabled)
+
+const buttonInst = shallowRef<PayPalButtonsComponent>()
+
+onMounted(() => {
+  onLoaded(async ({ paypal }) => {
+    if (!el.value) return
+    buttonInst.value = paypal?.Buttons?.(options.value)
+    await buttonInst.value?.render(el.value)
+    ready.value = true
+
+    watch(() => options.value, async (_options) => {
+      if (!el.value) return
+      await buttonInst.value?.updateProps(_options)
+    })
+  })
+})
+
+async function destroy() {
+  if (buttonInst.value) {
+    await buttonInst.value?.close()
+  }
+}
+
+onBeforeUnmount(async () => {
+  await destroy()
+})
+
+const ScriptLoadingIndicator = resolveComponent('ScriptLoadingIndicator')
+
+const trigger = useScriptTriggerElement({ trigger: props.trigger, el: rootEl })
+
+const rootAttrs = computed(() => {
+  return defu(props.rootAttrs, {
+    'aria-busy': status.value === 'loading',
+    'aria-label': status.value === 'awaitingLoad'
+      ? 'Paypal Script Placeholder'
+      : status.value === 'loading'
+        ? 'Paypal Buttons Loading'
+        : 'Paypal Buttons',
+    'aria-live': 'polite',
+    'role': 'application',
+    ...(trigger instanceof Promise ? trigger.ssrAttrs || {} : {}),
+  } satisfies HTMLAttributes)
+})
+</script>
+
+<template>
+  <div v-bind="rootAttrs" id="test">
+    <div v-show="ready" ref="el" />
+    <slot v-if="!ready" name="placeholder">
+      placeholder
+    </slot>
+    <slot v-if="status !== 'awaitingLoad' && !ready" name="loading">
+      <ScriptLoadingIndicator color="black" />
+    </slot>
+    <slot v-if="status === 'awaitingLoad'" name="awaitingLoad" />
+    <slot v-else-if="status === 'error'" name="error" />
+    <slot />
+  </div>
+</template>
diff --git a/src/runtime/components/ScriptPaypalMarks.vue b/src/runtime/components/ScriptPaypalMarks.vue
new file mode 100644
index 00000000..a5d229bc
--- /dev/null
+++ b/src/runtime/components/ScriptPaypalMarks.vue
@@ -0,0 +1,105 @@
+<script setup lang="ts">
+import { computed, type HTMLAttributes, onMounted, ref, type ReservedProps, shallowRef, watch } from 'vue'
+import { defu } from 'defu'
+import type { PayPalMarksComponent, PayPalMarksComponentOptions } from '@paypal/paypal-js'
+import { onBeforeUnmount, type PaypalInput, resolveComponent, useScriptPaypal, useScriptTriggerElement } from '#imports'
+import type { ElementScriptTrigger } from '#nuxt-scripts'
+
+const el = ref<HTMLDivElement | null>(null)
+const rootEl = ref<HTMLDivElement | null>(null)
+
+const props = withDefaults(defineProps<{
+  /**
+   * Customize the root element attributes.
+   */
+  rootAttrs?: HTMLAttributes & ReservedProps & Record<string, unknown>
+  /**
+   * Defines the trigger event to load the script.
+   */
+  trigger?: ElementScriptTrigger
+  /**
+   * The client id for the paypal script.
+   */
+  clientId?: string
+  /**
+   * The options for the paypal marks.
+   */
+  marksOptions?: PayPalMarksComponentOptions
+  /**
+   * The paypal script options.
+   */
+  paypalScriptOptions?: Partial<PaypalInput>
+}>(), {
+  trigger: 'visible',
+  clientId: 'test',
+  marksOptions: () => ({}),
+  paypalScriptOptions: () => ({}),
+})
+
+const ready = ref(false)
+
+const { onLoaded, status } = useScriptPaypal({
+  clientId: props.clientId,
+  ...props.paypalScriptOptions,
+})
+
+const marksInst = shallowRef<PayPalMarksComponent>()
+
+onMounted(() => {
+  onLoaded(async ({ paypal }) => {
+    if (!el.value) return
+    marksInst.value = paypal?.Marks?.(props.marksOptions)
+    await marksInst.value?.render(el.value)
+    ready.value = true
+
+    watch(() => props.marksOptions, async (_options) => {
+      if (!el.value) return
+      destroy()
+      marksInst.value = paypal?.Marks?.(_options)
+      await marksInst.value?.render(el.value)
+    })
+  })
+})
+
+function destroy() {
+  if (!el.value) return
+  el.value?.replaceChildren()
+}
+
+onBeforeUnmount(() => {
+  destroy()
+})
+
+const ScriptLoadingIndicator = resolveComponent('ScriptLoadingIndicator')
+
+const trigger = useScriptTriggerElement({ trigger: props.trigger, el: rootEl })
+
+const rootAttrs = computed(() => {
+  return defu(props.rootAttrs, {
+    'aria-busy': status.value === 'loading',
+    'aria-label': status.value === 'awaitingLoad'
+      ? 'Paypal Script Placeholder'
+      : status.value === 'loading'
+        ? 'Paypal Marks Loading'
+        : 'Paypal Marks',
+    'aria-live': 'polite',
+    'role': 'application',
+    ...(trigger instanceof Promise ? trigger.ssrAttrs || {} : {}),
+  } satisfies HTMLAttributes)
+})
+</script>
+
+<template>
+  <div v-bind="rootAttrs" id="test">
+    <div v-show="ready" ref="el" />
+    <slot v-if="!ready" name="placeholder">
+      placeholder
+    </slot>
+    <slot v-if="status !== 'awaitingLoad' && !ready" name="loading">
+      <ScriptLoadingIndicator color="black" />
+    </slot>
+    <slot v-if="status === 'awaitingLoad'" name="awaitingLoad" />
+    <slot v-else-if="status === 'error'" name="error" />
+    <slot />
+  </div>
+</template>
diff --git a/src/runtime/components/ScriptPaypalMessages.vue b/src/runtime/components/ScriptPaypalMessages.vue
new file mode 100644
index 00000000..4cbfebdc
--- /dev/null
+++ b/src/runtime/components/ScriptPaypalMessages.vue
@@ -0,0 +1,139 @@
+<script setup lang="ts">
+import { computed, type HTMLAttributes, onMounted, ref, type ReservedProps, shallowRef, watch } from 'vue'
+import { defu } from 'defu'
+import type { PayPalMessagesComponent, PayPalMessagesComponentOptions } from '@paypal/paypal-js'
+import { onBeforeUnmount, type PaypalInput, resolveComponent, useScriptPaypal, useScriptTriggerElement } from '#imports'
+import type { ElementScriptTrigger } from '#nuxt-scripts'
+
+const el = ref<HTMLDivElement | null>(null)
+const rootEl = ref<HTMLDivElement | null>(null)
+
+const props = withDefaults(defineProps<{
+  /**
+   * Customize the root element attributes.
+   */
+  rootAttrs?: HTMLAttributes & ReservedProps & Record<string, unknown>
+  /**
+   * Defines the trigger event to load the script.
+   */
+  trigger?: ElementScriptTrigger
+  /**
+   * The client id for the paypal script.
+   */
+  clientId?: string
+  /**
+   * The options for the paypal buttons.
+   */
+  messagesOptions?: PayPalMessagesComponentOptions
+  /**
+   * The merchant id for the paypal script.
+   */
+  merchantId?: string
+  /**
+   * The partner attribution id for the paypal script.
+   */
+  partnerAttributionId?: string
+  /**
+   * The options for the paypal scipt.
+   */
+  paypalScriptOptions?: Partial<PaypalInput>
+}>(), {
+  trigger: 'visible',
+  clientId: 'test',
+  paypalScriptOptions: () => ({}),
+  messagesOptions: () => ({}),
+})
+
+const ready = ref(false)
+
+const { onLoaded, status } = useScriptPaypal({
+  clientId: props.clientId,
+  merchantId: props.merchantId,
+  partnerAttributionId: props.partnerAttributionId,
+  ...props.paypalScriptOptions,
+})
+
+const emit = defineEmits<{
+  apply: [data: Record<string, unknown>]
+  clickMessages: [data: Record<string, unknown>]
+  render: [data: Record<string, unknown>]
+}>()
+
+const options = computed(() => {
+  const _options: PayPalMessagesComponentOptions = {
+    onApply: (data) => {
+      emit('apply', data)
+      return props.messagesOptions?.onApply?.(data)
+    },
+    onClick: (data) => {
+      emit('clickMessages', data)
+      return props.messagesOptions?.onClick?.(data)
+    },
+    onRender: (data) => {
+      emit('render', data)
+      return props.messagesOptions?.onRender?.(data)
+    },
+  }
+  return defu(_options, props.messagesOptions)
+})
+
+const messageInst = shallowRef<PayPalMessagesComponent>()
+
+onMounted(() => {
+  onLoaded(async ({ paypal }) => {
+    if (!el.value) return
+    messageInst.value = paypal?.Messages?.(options.value)
+    await messageInst.value?.render(el.value)
+    ready.value = true
+
+    watch(() => options.value, async (_options) => {
+      if (!el.value) return
+      // don't destroy the element
+      messageInst.value = paypal?.Messages?.(_options)
+      await messageInst.value?.render(el.value)
+    })
+  })
+})
+
+function destroy() {
+  if (!el.value) return
+  el.value?.replaceChildren()
+}
+
+onBeforeUnmount(() => {
+  destroy()
+})
+
+const ScriptLoadingIndicator = resolveComponent('ScriptLoadingIndicator')
+
+const trigger = useScriptTriggerElement({ trigger: props.trigger, el: rootEl })
+
+const rootAttrs = computed(() => {
+  return defu(props.rootAttrs, {
+    'aria-busy': status.value === 'loading',
+    'aria-label': status.value === 'awaitingLoad'
+      ? 'Paypal Script Placeholder'
+      : status.value === 'loading'
+        ? 'Paypal Buttons Loading'
+        : 'Paypal Buttons',
+    'aria-live': 'polite',
+    'role': 'application',
+    ...(trigger instanceof Promise ? trigger.ssrAttrs || {} : {}),
+  } satisfies HTMLAttributes)
+})
+</script>
+
+<template>
+  <div v-bind="rootAttrs" id="test">
+    <div v-show="ready" ref="el" />
+    <slot v-if="!ready" name="placeholder">
+      placeholder
+    </slot>
+    <slot v-if="status !== 'awaitingLoad' && !ready" name="loading">
+      <ScriptLoadingIndicator color="black" />
+    </slot>
+    <slot v-if="status === 'awaitingLoad'" name="awaitingLoad" />
+    <slot v-else-if="status === 'error'" name="error" />
+    <slot />
+  </div>
+</template>
diff --git a/src/runtime/registry/paypal.ts b/src/runtime/registry/paypal.ts
new file mode 100644
index 00000000..7bafee8b
--- /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'
+
+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<typeof PaypalOptions>
+
+export function useScriptPaypal<T extends PaypalApi>(_options?: PaypalInput) {
+  return useRegistryScript<T, typeof PaypalOptions>('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)
+}