Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/small-rugs-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/clerk-js': patch
'@clerk/shared': patch
---

Upgrade `@stripe/stripe-js` from 5.6.0 to 9.0.0 and `@stripe/react-stripe-js` from 3.1.1 to 6.0.0
2 changes: 1 addition & 1 deletion packages/clerk-js/bundlewatch.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{ "path": "./dist/vendors*.js", "maxSize": "7KB" },
{ "path": "./dist/coinbase*.js", "maxSize": "36KB" },
{ "path": "./dist/base-account-sdk*.js", "maxSize": "203KB" },
{ "path": "./dist/stripe-vendors*.js", "maxSize": "1KB" },
{ "path": "./dist/stripe-vendors*.js", "maxSize": "2KB" },
{ "path": "./dist/query-core-vendors*.js", "maxSize": "11KB" },
{ "path": "./dist/zxcvbn-ts-core*.js", "maxSize": "12KB" },
{ "path": "./dist/zxcvbn-common*.js", "maxSize": "226KB" }
Expand Down
2 changes: 1 addition & 1 deletion packages/clerk-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
"@solana/wallet-adapter-base": "catalog:module-manager",
"@solana/wallet-adapter-react": "catalog:module-manager",
"@solana/wallet-standard": "catalog:module-manager",
"@stripe/stripe-js": "5.6.0",
"@stripe/stripe-js": "9.0.0",
"@swc/helpers": "catalog:repo",
"@tanstack/query-core": "5.90.16",
"@wallet-standard/core": "catalog:module-manager",
Expand Down
4 changes: 2 additions & 2 deletions packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@
"@solana/wallet-adapter-base": "catalog:module-manager",
"@solana/wallet-adapter-react": "catalog:module-manager",
"@solana/wallet-standard": "catalog:module-manager",
"@stripe/react-stripe-js": "3.1.1",
"@stripe/stripe-js": "5.6.0",
"@stripe/react-stripe-js": "6.0.0",
"@stripe/stripe-js": "9.0.0",
Comment on lines +155 to +156
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify whether test files were part of this PR branch changes
git diff --name-only --diff-filter=ACMRT origin/main...HEAD | rg -n '(\.test\.|\.spec\.)' || true

# Locate current critical Stripe flow callsites covered by tests
rg -n --type=ts --type=tsx 'confirmSetup\s*\(' packages/shared packages/clerk-js
rg -n --type=ts --type=tsx 'PaymentElement|useInitializePaymentMethod' packages/shared packages/clerk-js

Repository: clerk/javascript

Length of output: 128


🏁 Script executed:

# Check if packages/shared/package.json exists and contains the Stripe versions
cat -n packages/shared/package.json | sed -n '150,160p'

Repository: clerk/javascript

Length of output: 670


🏁 Script executed:

# Search for test files related to payments or Stripe
fd -e test.ts -e test.tsx -e spec.ts -e spec.tsx -e test.js -e test.jsx packages/ | head -20

Repository: clerk/javascript

Length of output: 401


🏁 Script executed:

# Search for confirmSetup usage in the codebase
rg -n 'confirmSetup' packages/shared packages/clerk-js --max-count=10

Repository: clerk/javascript

Length of output: 180


🏁 Script executed:

# Search for PaymentElement usage
rg -n 'PaymentElement' packages/shared packages/clerk-js --max-count=10

Repository: clerk/javascript

Length of output: 4393


🏁 Script executed:

cat -n packages/shared/src/react/__tests__/payment-element.test.tsx

Repository: clerk/javascript

Length of output: 11043


🏁 Script executed:

# Check recent git changes to understand what was modified
git status
git log --oneline -10

Repository: clerk/javascript

Length of output: 359


🏁 Script executed:

# Look for any recent payment-related changes
git diff packages/shared/src/react/billing/payment-element.tsx | head -100

Repository: clerk/javascript

Length of output: 42


🏁 Script executed:

# Check if there are other test files that may cover confirmSetup
fd -e test.ts -e test.tsx -e spec.ts -e spec.tsx . packages/ --full-path | grep -i stripe

Repository: clerk/javascript

Length of output: 42


🏁 Script executed:

# Check the payment-element.tsx source to see confirmSetup implementation
cat packages/shared/src/react/billing/payment-element.tsx | sed -n '320,340p'

Repository: clerk/javascript

Length of output: 678


🏁 Script executed:

# Check what changed in package.json specifically for Stripe
git diff HEAD -- packages/shared/package.json | grep -A 3 -B 3 stripe || echo "No git diff available"

Repository: clerk/javascript

Length of output: 81


🏁 Script executed:

# List all modified/added files in the PR
git show --name-only --oneline | head -30

Repository: clerk/javascript

Length of output: 905


🏁 Script executed:

# Check the changeset files to understand what's being upgraded
cat .changeset/cozy-lands-grow.md .changeset/nasty-insects-follow.md .changeset/new-seas-pick.md .changeset/polite-camels-chew.md .changeset/small-rugs-add.md 2>/dev/null | head -50

Repository: clerk/javascript

Length of output: 744


🏁 Script executed:

# Check if payment-element.test.tsx was modified in this commit
git show HEAD:packages/shared/src/react/__tests__/payment-element.test.tsx > /tmp/test_current.tsx 2>/dev/null && echo "File exists in HEAD" || echo "File does not exist in HEAD"

Repository: clerk/javascript

Length of output: 79


🏁 Script executed:

# Get Stripe SDK version changes from changeset or recent commits
rg -A 5 "stripe" .changeset/ 2>/dev/null | head -30

Repository: clerk/javascript

Length of output: 186


🏁 Script executed:

# Check the Stripe release notes / breaking changes between versions
# Search web for Stripe SDK v5 to v9 migration guide
cat > /tmp/check_stripe.txt << 'EOF'
Checking Stripe SDK upgrade: v5.6.0 -> v9.0.0 for `@stripe/stripe-js`
and v3.1.1 -> v6.0.0 for `@stripe/react-stripe-js`

Need to verify if confirmSetup API changed in breaking ways.
EOF
cat /tmp/check_stripe.txt

Repository: clerk/javascript

Length of output: 238


🏁 Script executed:

# Search for any migration or upgrade guidance in repo
rg -i "stripe.*breaking\|stripe.*migration\|confirmSetup" packages/ --max-count=5

Repository: clerk/javascript

Length of output: 42


🏁 Script executed:

# Check if there are any integration tests or e2e tests that might cover this
fd . packages/ -e cy.ts -e e2e.ts -e integration.test.ts -e integration.spec.ts --full-path

Repository: clerk/javascript

Length of output: 42


Major Stripe SDK upgrade requires verification of payment confirmation flow before merge.

Lines 155-156 upgrade Stripe SDKs across major versions (v5→v9 for stripe-js, v3→v6 for react-stripe-js). These are significant version jumps that may introduce breaking changes. The confirmSetup() call at packages/shared/src/react/billing/payment-element.tsx:329 is critical billing code that must be verified to work correctly after this upgrade. Add test coverage (unit or e2e) or manual verification evidence for the payment confirmation flow before merging to prevent runtime billing failures.

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

In `@packages/shared/package.json` around lines 155 - 156, The Stripe SDK
dependency bumps in package.json may introduce breaking changes; specifically
verify the payment confirmation flow by exercising the confirmSetup() call in
the PaymentElement component: add automated coverage (unit or e2e) that
simulates a full setup confirmation flow (mocking Stripe elements and
confirmSetup responses) or provide documented manual verification steps and
screenshots/logs proving confirmSetup() works with the new `@stripe/stripe-js` and
`@stripe/react-stripe-js` versions; update or add tests targeting the
PaymentElement component and its confirmSetup usage, or revert the dependency
change if tests/manual checks fail.

"@types/glob-to-regexp": "0.4.4",
"@types/js-cookie": "3.0.6",
"@wallet-standard/core": "catalog:module-manager",
Expand Down
227 changes: 227 additions & 0 deletions packages/shared/src/react/billing/__tests__/usePaymentElement.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import { act, renderHook } from '@testing-library/react';
import React from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';

// --- Mock state ---

let mockStripe: any = null;
let mockElements: any = null;
Comment on lines +7 to +8
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify no explicit `any` remains in this test file.
rg -nP '\bany\b' packages/shared/src/react/billing/__tests__/usePaymentElement.spec.tsx

Repository: clerk/javascript

Length of output: 220


Remove explicit any type annotations in this test file.

The Stripe mock objects and submit results use any, which violates the repository's TypeScript guidelines and can mask type regressions in upgrade tests. Provide proper types for mockStripe, mockElements, and submitResult variables.

Proposed fix
-let mockStripe: any = null;
-let mockElements: any = null;
+type MockStripe = {
+  confirmSetup: ReturnType<typeof vi.fn>;
+};
+
+type MockElements = {
+  create: ReturnType<typeof vi.fn>;
+  update: ReturnType<typeof vi.fn>;
+};
+
+type SubmitResult = Awaited<
+  ReturnType<ReturnType<typeof __experimental_usePaymentElement>['submit']>
+>;
+
+let mockStripe: MockStripe | null = null;
+let mockElements: MockElements | null = null;
@@
-      let submitResult: any;
+      let submitResult: SubmitResult;
@@
-      let submitResult: any;
+      let submitResult: SubmitResult;
@@
-      let submitResult: any;
+      let submitResult: SubmitResult;

Per coding guidelines: "Avoid any type - prefer unknown when type is uncertain, then narrow with type guards" and "No any types without justification in code review". Instances at lines 7, 8, 135, 167, 198.

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

In `@packages/shared/src/react/billing/__tests__/usePaymentElement.spec.tsx`
around lines 7 - 8, Replace the explicit any annotations on mockStripe,
mockElements and submitResult with safer types (e.g., use unknown or the
specific Stripe types from `@stripe/stripe-js`) and narrow them where used; for
example change mockStripe: any and mockElements: any to mockStripe: unknown and
mockElements: unknown (or to Stripe | null / StripeElements | null if you import
those interfaces) and update the test usages by adding type guards or casts only
at the points of interaction (e.g., before calling
mockStripe.createPaymentMethod or mockElements.getElement) so the code is
statically typed and still mockable; ensure submitResult is typed (unknown or a
specific result interface) and narrowed before asserting fields.

const mockInitializePaymentMethod = vi.fn();

vi.mock('../../stripe-react', () => ({
Elements: ({ children }: { children: React.ReactNode }) => <>{children}</>,
PaymentElement: () => null,
useElements: () => mockElements,
useStripe: () => mockStripe,
}));

vi.mock('../../hooks/useClerk', () => ({
useClerk: () => ({
__internal_getOption: () => undefined,
__internal_environment: {
commerceSettings: {
billing: {
stripePublishableKey: 'pk_test_123',
},
},
displayConfig: {
userProfileUrl: 'https://example.com/profile',
organizationProfileUrl: 'https://example.com/org-profile',
},
},
}),
}));

vi.mock('../useInitializePaymentMethod', () => ({
__internal_useInitializePaymentMethod: () => ({
initializedPaymentMethod: {
externalGatewayId: 'acct_123',
externalClientSecret: 'seti_123',
paymentMethodOrder: ['card'],
},
initializePaymentMethod: mockInitializePaymentMethod,
}),
}));

vi.mock('../useStripeClerkLibs', () => ({
__internal_useStripeClerkLibs: () => ({
loadStripe: vi.fn().mockResolvedValue({}),
}),
}));

vi.mock('../useStripeLoader', () => ({
__internal_useStripeLoader: () => ({}),
}));

const { __experimental_PaymentElementProvider, __experimental_usePaymentElement } = await import('../payment-element');

function createWrapper() {
return function Wrapper({ children }: { children: React.ReactNode }) {
return <__experimental_PaymentElementProvider>{children}</__experimental_PaymentElementProvider>;
};
}

describe('usePaymentElement', () => {
beforeEach(() => {
vi.clearAllMocks();
mockStripe = null;
mockElements = null;
});

describe('when provider is not ready (no stripe/elements)', () => {
it('returns isProviderReady=false and isFormReady=false', () => {
const { result } = renderHook(() => __experimental_usePaymentElement(), {
wrapper: createWrapper(),
});

expect(result.current.isProviderReady).toBe(false);
expect(result.current.isFormReady).toBe(false);
expect(result.current.provider).toBeUndefined();
});

it('submit throws when stripe is not loaded', () => {
const { result } = renderHook(() => __experimental_usePaymentElement(), {
wrapper: createWrapper(),
});

expect(() => result.current.submit()).toThrow('Clerk: Unable to submit, Stripe libraries are not yet loaded');
});

it('reset throws when stripe is not loaded', () => {
const { result } = renderHook(() => __experimental_usePaymentElement(), {
wrapper: createWrapper(),
});

expect(() => result.current.reset()).toThrow('Clerk: Unable to submit, Stripe libraries are not yet loaded');
});
});

describe('when provider is ready', () => {
beforeEach(() => {
mockStripe = {
confirmSetup: vi.fn(),
};
mockElements = {
create: vi.fn(),
update: vi.fn(),
};
});

it('returns isProviderReady=true with stripe provider info', () => {
const { result } = renderHook(() => __experimental_usePaymentElement(), {
wrapper: createWrapper(),
});

expect(result.current.isProviderReady).toBe(true);
expect(result.current.provider).toEqual({ name: 'stripe' });
});

it('submit returns paymentToken on successful confirmSetup', async () => {
mockStripe.confirmSetup.mockResolvedValue({
setupIntent: { payment_method: 'pm_test_123' },
error: null,
});

const { result } = renderHook(() => __experimental_usePaymentElement(), {
wrapper: createWrapper(),
});

let submitResult: any;
await act(async () => {
submitResult = await result.current.submit();
});

expect(mockStripe.confirmSetup).toHaveBeenCalledWith({
elements: mockElements,
confirmParams: {
return_url: window.location.href,
},
redirect: 'if_required',
});
expect(submitResult).toEqual({
data: { gateway: 'stripe', paymentToken: 'pm_test_123' },
error: null,
});
});

it('submit returns structured error when confirmSetup fails', async () => {
mockStripe.confirmSetup.mockResolvedValue({
setupIntent: null,
error: {
type: 'card_error',
code: 'card_declined',
message: 'Your card was declined.',
},
});

const { result } = renderHook(() => __experimental_usePaymentElement(), {
wrapper: createWrapper(),
});

let submitResult: any;
await act(async () => {
submitResult = await result.current.submit();
});

expect(submitResult).toEqual({
data: null,
error: {
gateway: 'stripe',
error: {
type: 'card_error',
code: 'card_declined',
message: 'Your card was declined.',
},
},
});
});

it('submit handles validation_error type from confirmSetup', async () => {
mockStripe.confirmSetup.mockResolvedValue({
setupIntent: null,
error: {
type: 'validation_error',
message: 'Your card number is incomplete.',
},
});

const { result } = renderHook(() => __experimental_usePaymentElement(), {
wrapper: createWrapper(),
});

let submitResult: any;
await act(async () => {
submitResult = await result.current.submit();
});

expect(submitResult).toEqual({
data: null,
error: {
gateway: 'stripe',
error: {
type: 'validation_error',
code: undefined,
message: 'Your card number is incomplete.',
},
},
});
});

it('reset calls initializePaymentMethod', async () => {
mockInitializePaymentMethod.mockResolvedValue({
externalClientSecret: 'seti_new_456',
gateway: 'stripe',
});

const { result } = renderHook(() => __experimental_usePaymentElement(), {
wrapper: createWrapper(),
});

await act(async () => {
await result.current.reset();
});

expect(mockInitializePaymentMethod).toHaveBeenCalledTimes(1);
});
});
});
30 changes: 15 additions & 15 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading