diff --git a/src/button-react/GooglePayButton.react19.test.tsx b/src/button-react/GooglePayButton.react19.test.tsx
new file mode 100644
index 0000000..8d84ffe
--- /dev/null
+++ b/src/button-react/GooglePayButton.react19.test.tsx
@@ -0,0 +1,72 @@
+import GooglePayButton from './GooglePayButton';
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+// Dynamically load React 18's createRoot API if available so this test works
+// both with React 16 (CI) and React 18 (local environments).
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const createRootModule: any = (() => {
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ return require('react-dom/client');
+ } catch (e) {
+ return null;
+ }
+})();
+
+import defaults from '../lib/__setup__/defaults';
+
+describe('React 19 compatibility', () => {
+ it('does not access element.ref when mounting (simulates React 19)', () => {
+ const div = document.createElement('div');
+
+ // Save original createElement
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const originalCreateElement: any = React.createElement;
+
+ // Monkeypatch React.createElement to wrap returned element in a Proxy that
+ // throws if code attempts to read the `ref` property (React 19 behavior).
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ React.createElement = function patchedCreateElement(...args: any[]) {
+ const el = originalCreateElement(...args);
+ return new Proxy(el, {
+ get(target, prop) {
+ if (prop === 'ref') {
+ // Allow React internals and render pipeline to read `ref` (they do this
+ // during reconciliation). Only throw if the access does not originate
+ // from React internals — this simulates component/library code
+ // incorrectly accessing `element.ref`.
+ const stack = new Error().stack || '';
+ if (!/(?:react(?:-|\/)dom|react(?:-|\/)cjs|react(?:-|\/)umd)/i.test(stack)) {
+ throw new Error('Accessing element.ref is not allowed (simulating React 19)');
+ }
+ // If stack indicates React internals, allow the access.
+ return (target as any)[prop];
+ }
+ return (target as any)[prop];
+ },
+ });
+ } as unknown as typeof React.createElement;
+
+ // If mounting the component tries to read `element.ref`, the Proxy will throw
+ expect(() => {
+ const createRootFn = createRootModule && createRootModule.createRoot;
+ if (createRootFn) {
+ const root = createRootFn(div);
+ root.render();
+ root.unmount();
+ } else {
+ // For older React versions used in CI (e.g., React 16), fall back to
+ // the legacy render/unmount APIs.
+ // eslint-disable-next-line react/no-deprecated
+ ReactDOM.render(, div);
+ // eslint-disable-next-line react/no-deprecated
+ ReactDOM.unmountComponentAtNode(div);
+ }
+ }).not.toThrow();
+
+ // restore
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ React.createElement = originalCreateElement;
+ });
+});