Skip to content

Conversation

@Innocent-Akim
Copy link
Contributor

@Innocent-Akim Innocent-Akim commented Dec 25, 2025

  • Add SolidgateProvider implementing PaymentProviderInterface
  • Create checkout endpoint at /api/solidgate/checkout
  • Create webhook endpoint at /api/solidgate/webhook
  • Integrate @solidgate/react-sdk for frontend payment form
  • Add Solidgate configuration in payment-provider-manager
  • Support for payment intents, subscriptions, and refunds
  • Webhook signature verification with HMAC SHA256
  • Add merchant ID configuration support
  • Update payment-provider-factory to enable Solidgate

Summary

Related Issues

Description

Changes Made

  • [ ]
  • [ ]
  • [ ]

Screenshots / Videos

Type of Change

  • 🐛 Bug fix (non-breaking change addressing an issue)
  • ✨ New feature (non-breaking change adding functionality)
  • 💥 Breaking change (fix or feature causing existing functionality to change)
  • 📝 Documentation update
  • 🔧 Configuration change
  • 🧪 Test addition or modification
  • 🛠️ CI/CD infrastructure improvement
  • ♻️ Refactoring (no functional changes)
  • 🎨 Code style/formatting update
  • 🚀 Performance improvement

Implementation Details

Testing Instructions

Deployment Notes

Checklist

  • I have performed a self-review of my code
  • I have added/updated tests that prove my fix is effective or my feature works
  • I have updated relevant documentation
  • My changes generate no new warnings or errors
  • I have verified my changes in multiple browsers/environments (if applicable)
  • New and existing unit tests pass locally with my changes
  • I have made corresponding changes to the documentation
  • My code follows the established code style of the project
  • I have commented my code, particularly in hard-to-understand areas

Additional Context


Summary by cubic

Adds Solidgate as a first-class payment provider with a React SDK-based checkout and secure webhooks. Supports one-time payments, subscriptions, and refunds end to end.

  • New Features

    • SolidgateProvider with payment intents, subscriptions, and refunds.
    • New endpoints: POST /api/solidgate/checkout and POST /api/solidgate/webhook (HMAC SHA256 verification).
    • React payment form via @solidgate/react-sdk.
    • Provider manager/factory wiring plus merchant/config support (.env).
  • Migration

    • Set env vars: SOLIDGATE_API_KEY, SOLIDGATE_WEBHOOK_SECRET, NEXT_PUBLIC_SOLIDGATE_PUBLISHABLE_KEY, SOLIDGATE_MERCHANT_ID, SOLIDGATE_API_BASE_URL (optional).
    • Configure webhook URL in Solidgate: https:///api/solidgate/webhook.
    • Ensure NEXT_PUBLIC_APP_URL is set for redirects.
    • Install deps: pnpm install.

Written for commit 49d5dfb. Summary will update automatically on new commits.

Summary by CodeRabbit

Release Notes

  • New Features
    • Integrated Solidgate as a payment processing provider
    • Added checkout and payment processing capabilities via Solidgate
    • Enabled subscription management and customer account handling through Solidgate
    • Implemented real-time webhook event processing for payment updates

✏️ Tip: You can customize this high-level summary in your review settings.

- Add SolidgateProvider implementing PaymentProviderInterface
- Create checkout endpoint at /api/solidgate/checkout
- Create webhook endpoint at /api/solidgate/webhook
- Integrate @solidgate/react-sdk for frontend payment form
- Add Solidgate configuration in payment-provider-manager
- Support for payment intents, subscriptions, and refunds
- Webhook signature verification with HMAC SHA256
- Add merchant ID configuration support
- Update payment-provider-factory to enable Solidgate
@Innocent-Akim Innocent-Akim self-assigned this Dec 25, 2025
@vercel
Copy link

vercel bot commented Dec 25, 2025

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

Project Deployment Review Updated (UTC)
ever-works-website-template Error Error Dec 25, 2025 8:38pm

@Innocent-Akim Innocent-Akim marked this pull request as draft December 25, 2025 20:38
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 25, 2025

Walkthrough

This PR adds comprehensive Solidgate payment provider integration, including environment configuration, API checkout and webhook routes, a full SolidgateProvider implementation with customer/payment/subscription lifecycle management, React UI components for payment forms, and the required SDK dependency.

Changes

Cohort / File(s) Summary
Configuration & Environment
.env.example
Added six Solidgate configuration variables (API key, secret key, webhook secret, publishable key, merchant ID, API base URL) after SMTP configuration section.
API Routes
app/api/solidgate/checkout/route.ts, app/api/solidgate/webhook/route.ts
Implemented POST endpoint for creating Solidgate checkout sessions with user authentication, amount validation, and customer/intent creation. Added webhook listener with signature verification, event dispatching, and error handling; includes GET fallback indicating POST-only support.
Payment Provider Configuration
lib/payment/config/payment-provider-manager.ts
Introduced Solidgate to ProviderConfig (required, not optional), added Solidgate-specific private fields to ConfigManager, implemented validateSolidgateConfig() and getSolidgateConfig() methods, added getSolidgateProvider() getter, and extended getOrCreateProvider() factory to support 'solidgate' case.
Payment Provider Factory
lib/payment/lib/payment-provider-factory.ts
Uncommented SolidgateProvider import and implemented its factory creation path; removed placeholder error handling.
Payment Provider Implementation
lib/payment/lib/providers/solidgate-provider.ts
Replaced stub with comprehensive SolidgateProvider class implementing PaymentProviderInterface with API request layer, customer/payment/subscription lifecycle, webhook verification, refund handling, and client config/UI component exposure. Includes signature generation, error handling, status mapping, and multilingual asset support.
UI Components
lib/payment/ui/solidgate/solidgate-elements.tsx
Added SolidgatePaymentForm component managing loading/error state with success and error callbacks, and SolidgateElementsWrapper as default export; integrates Solidgate SDK Payment component with merchantData and user-facing feedback.
Dependencies
package.json
Added @solidgate/react-sdk@^1.24.0 to dependencies.

Sequence Diagrams

sequenceDiagram
    participant Client
    participant CheckoutAPI as POST /api/solidgate/checkout
    participant ConfigMgr as ConfigManager
    participant SolidgateProvider as SolidgateProvider
    participant SolidgateAPI as Solidgate API

    Client->>CheckoutAPI: POST request (amount, currency, URLs)
    CheckoutAPI->>CheckoutAPI: Authenticate user
    CheckoutAPI->>ConfigMgr: getSolidgateProvider()
    ConfigMgr->>SolidgateProvider: instantiate with config
    CheckoutAPI->>SolidgateProvider: createCustomer(user)
    SolidgateProvider->>SolidgateAPI: POST /customers
    SolidgateAPI-->>SolidgateProvider: customerId
    CheckoutAPI->>SolidgateProvider: createPaymentIntent(amount, customerId, URLs)
    SolidgateProvider->>SolidgateAPI: POST /payments
    SolidgateAPI-->>SolidgateProvider: {id, url, clientSecret}
    CheckoutAPI-->>Client: 200 {data: {id, url}, status, message}
Loading
sequenceDiagram
    participant SolidgateAPI as Solidgate Service
    participant WebhookAPI as POST /api/solidgate/webhook
    participant SolidgateProvider as SolidgateProvider
    participant EventHandler as Event Handlers
    participant Database as Database

    SolidgateAPI->>WebhookAPI: POST webhook payload + signature
    WebhookAPI->>WebhookAPI: Verify signature
    WebhookAPI->>SolidgateProvider: handleWebhook(payload, signature)
    SolidgateProvider->>SolidgateProvider: Map event type (payment/subscription)
    
    alt Payment Event
        SolidgateProvider->>EventHandler: Dispatch payment event
        EventHandler->>Database: Update PaymentAccount
    else Subscription Event
        SolidgateProvider->>EventHandler: Dispatch subscription event
        EventHandler->>Database: Update Subscription record
    end
    
    SolidgateProvider-->>WebhookAPI: WebhookResult (processed)
    WebhookAPI-->>SolidgateAPI: 200 OK
Loading
sequenceDiagram
    participant User
    participant PaymentForm as SolidgatePaymentForm
    participant SolidgateSDK as Solidgate SDK
    participant PaymentAPI as Payment API

    User->>PaymentForm: Render (merchantId, signature, paymentIntent)
    PaymentForm->>PaymentForm: Prepare merchantData
    PaymentForm->>SolidgateSDK: Render Payment component
    User->>SolidgateSDK: Enter card details
    User->>SolidgateSDK: Submit payment
    SolidgateSDK-->>PaymentForm: success callback (data)
    PaymentForm->>PaymentForm: Extract/derive paymentId
    PaymentForm->>PaymentAPI: onSuccess(paymentId)
    PaymentAPI-->>User: Payment confirmed / Redirect to successUrl
    
    alt Error Case
        SolidgateSDK-->>PaymentForm: error callback
        PaymentForm->>PaymentForm: Display error banner
        PaymentForm-->>User: Error message visible
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • evereq

🐰 A gateway swift, a webhook's call,
Solidgate payments handled all!
Customers dance, intents take flight,
React components render bright. 💳✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: integrating a new Solidgate payment provider with its React SDK. It directly reflects the primary objective of the PR.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/integrate-solidgate-paymen

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.

* Required for the React SDK
*/
private generatePaymentIntentSignature(paymentIntent: string, merchantId: string): string {
const data = `${merchantId}${paymentIntent}`;

Check failure

Code scanning / CodeQL

Use of password hash with insufficient computational effort High

Password from
an access to apiKey
is hashed insecurely.
Password from
an access to apiKey
is hashed insecurely.

Copilot Autofix

AI 7 days ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

Copy link
Contributor

@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: 10

🧹 Nitpick comments (4)
lib/payment/lib/payment-provider-factory.ts (1)

14-33: LGTM - Solidgate provider factory integration.

The factory implementation correctly follows the existing pattern. Minor note: the JSDoc comment on line 16 mentions 'stripe', 'solidgate', or 'lemonsqueezy' but omits 'polar' which is also supported.

🔎 Optional: Update JSDoc to include all providers
 	/**
 	 * Create an instance of a payment provider based on the type
-	 * @param providerType - Type of provider ('stripe', 'solidgate', or 'lemonsqueezy')
+	 * @param providerType - Type of provider ('stripe', 'solidgate', 'lemonsqueezy', or 'polar')
 	 * @param config - Configuration of the provider
 	 * @returns Instance of the payment provider
 	 */
app/api/solidgate/checkout/route.ts (1)

185-185: Avoid as any type assertion.

The type assertion session.user as any bypasses TypeScript's type checking. Define a proper type or interface for the user object expected by getCustomerId.

🔎 Proposed fix
-		const solidgateCustomerId = await solidgateProvider.getCustomerId(session.user as any);
+		const solidgateCustomerId = await solidgateProvider.getCustomerId(session.user);

If there's a type mismatch, consider updating the getCustomerId method signature or creating a proper adapter type.

lib/payment/ui/solidgate/solidgate-elements.tsx (1)

16-31: Remove unused props and refs.

Several destructured props (amount, currency, isSubscription, successUrl, cancelUrl, metadata) and sdkInstanceRef are declared but never used. This is dead code that adds noise.

🔎 Proposed fix
 export function SolidgatePaymentForm({
 	onSuccess,
 	onError,
-	amount,
-	currency,
-	isSubscription,
 	merchantId,
 	paymentIntent,
-	signature,
-	successUrl,
-	cancelUrl,
-	metadata
+	signature
 }: SolidgateElementsWrapperProps) {
 	const [isLoading, setIsLoading] = useState(false);
 	const [errorMessage, setErrorMessage] = useState('');
-	const sdkInstanceRef = useRef<ClientSdkInstance | null>(null);

If these props are needed for future functionality, consider adding a TODO comment explaining the intent.

lib/payment/config/payment-provider-manager.ts (1)

226-248: Consider validating additional required fields.

The validation only checks for SOLIDGATE_API_KEY, but the provider implementation relies on webhookSecret for signature verification and merchantId for SDK integration. Consider warning when these are missing.

🔎 Optional: Add warnings for missing recommended fields
 	private static validateSolidgateConfig(): void {
 		const solidgateApiKey = this.solidgateApiKey;
 		const solidgateWebhookSecret = this.solidgateWebhookSecret;
 		const solidgatePublishableKey = this.solidgatePublishableKey;
 		const solidgateMerchantId = this.solidgateMerchantId;
 		const solidgateApiBaseUrl = this.solidgateApiBaseUrl;

 		if (!solidgateApiKey) {
 			throw new Error('Solidgate configuration is incomplete. Required: SOLIDGATE_API_KEY');
 		}

+		if (!solidgateWebhookSecret) {
+			console.warn('⚠️ SOLIDGATE_WEBHOOK_SECRET not set - webhook signature verification will be skipped');
+		}
+
+		if (!solidgateMerchantId) {
+			console.warn('⚠️ SOLIDGATE_MERCHANT_ID not set - will attempt to extract from API key');
+		}
+
 		this.ensureConfig().solidgate = {
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16a36cb and 49d5dfb.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • .env.example
  • app/api/solidgate/checkout/route.ts
  • app/api/solidgate/webhook/route.ts
  • lib/payment/config/payment-provider-manager.ts
  • lib/payment/lib/payment-provider-factory.ts
  • lib/payment/lib/providers/solidgate-provider.ts
  • lib/payment/ui/solidgate/solidgate-elements.tsx
  • package.json
🧰 Additional context used
📓 Path-based instructions (8)
.env.example

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid changing .env.example semantics without clear instructions

Files:

  • .env.example
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use Node.js >= 20.19.0

Files:

  • lib/payment/ui/solidgate/solidgate-elements.tsx
  • app/api/solidgate/webhook/route.ts
  • lib/payment/lib/payment-provider-factory.ts
  • lib/payment/config/payment-provider-manager.ts
  • app/api/solidgate/checkout/route.ts
  • lib/payment/lib/providers/solidgate-provider.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Prefer TypeScript files over JavaScript

Run pnpm tsc --noEmit for TypeScript type-checking

Files:

  • lib/payment/ui/solidgate/solidgate-elements.tsx
  • app/api/solidgate/webhook/route.ts
  • lib/payment/lib/payment-provider-factory.ts
  • lib/payment/config/payment-provider-manager.ts
  • app/api/solidgate/checkout/route.ts
  • lib/payment/lib/providers/solidgate-provider.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer TypeScript for all code

Files:

  • lib/payment/ui/solidgate/solidgate-elements.tsx
  • app/api/solidgate/webhook/route.ts
  • lib/payment/lib/payment-provider-factory.ts
  • lib/payment/config/payment-provider-manager.ts
  • app/api/solidgate/checkout/route.ts
  • lib/payment/lib/providers/solidgate-provider.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{js,jsx,ts,tsx}: Follow Prettier config with tabs, 4-space tabWidth, and 120-char printWidth
Prefer async/await over raw Promise chains in TypeScript/JavaScript code
Validate input with Zod where appropriate; follow existing schemas in lib/validations
Keep i18n-friendly: avoid hard-coded English strings in logic; use next-intl messages where relevant

Files:

  • lib/payment/ui/solidgate/solidgate-elements.tsx
  • app/api/solidgate/webhook/route.ts
  • lib/payment/lib/payment-provider-factory.ts
  • lib/payment/config/payment-provider-manager.ts
  • app/api/solidgate/checkout/route.ts
  • lib/payment/lib/providers/solidgate-provider.ts
app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Routes and pages should be placed in app/[locale]/** and app/api/** directories following Next.js App Router conventions

Files:

  • app/api/solidgate/webhook/route.ts
  • app/api/solidgate/checkout/route.ts
app/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Next.js App Router under app/[locale]/** and app/api/**

Files:

  • app/api/solidgate/webhook/route.ts
  • app/api/solidgate/checkout/route.ts
app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

For API routes: put shared logic in lib/services or lib/repositories; keep handlers thin with validation, service call, and result mapping to HTTP response

Files:

  • app/api/solidgate/webhook/route.ts
  • app/api/solidgate/checkout/route.ts
🧠 Learnings (1)
📚 Learning: 2025-11-30T21:22:22.296Z
Learnt from: CR
Repo: ever-works/ever-works-website-template PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-30T21:22:22.296Z
Learning: Applies to .env.example : Avoid changing .env.example semantics without clear instructions

Applied to files:

  • .env.example
🧬 Code graph analysis (4)
app/api/solidgate/webhook/route.ts (2)
lib/payment/config/payment-provider-manager.ts (1)
  • getOrCreateSolidgateProvider (362-364)
lib/logger.ts (1)
  • error (113-123)
lib/payment/config/payment-provider-manager.ts (1)
lib/payment/lib/providers/solidgate-provider.ts (1)
  • SolidgateProvider (124-801)
app/api/solidgate/checkout/route.ts (2)
app/api/solidgate/webhook/route.ts (1)
  • POST (91-159)
lib/payment/config/payment-provider-manager.ts (1)
  • getOrCreateSolidgateProvider (362-364)
lib/payment/lib/providers/solidgate-provider.ts (7)
lib/payment/types/payment-types.ts (14)
  • CardBrandIcon (111-117)
  • PaymentProviderInterface (176-216)
  • CustomerResult (244-249)
  • CreateCustomerParams (238-242)
  • SetupIntent (35-38)
  • CreatePaymentParams (218-226)
  • PaymentIntent (26-33)
  • CreateSubscriptionParams (251-257)
  • SubscriptionInfo (40-51)
  • UpdateSubscriptionParams (259-265)
  • WebhookResult (75-80)
  • ClientConfig (268-272)
  • UIComponents (161-174)
  • PaymentFormProps (83-95)
lib/logger.ts (1)
  • error (113-123)
lib/payment/lib/client/payment-account-client.ts (1)
  • paymentAccountClient (127-127)
lib/payment/lib/utils/prices.ts (1)
  • PRICES (3-24)
lib/payment/index.ts (1)
  • PaymentFormProps (8-8)
lib/payment/ui/solidgate/solidgate-elements.tsx (1)
  • SolidgateElementsWrapper (119-121)
lib/db/schema.ts (1)
  • SubscriptionStatus (342-348)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 158-158: [UnorderedKey] The NEXT_PUBLIC_SOLIDGATE_PUBLISHABLE_KEY key should go before the SOLIDGATE_API_KEY key

(UnorderedKey)


[warning] 159-159: [SpaceCharacter] The line has spaces around equal sign

(SpaceCharacter)


[warning] 159-159: [TrailingWhitespace] Trailing whitespace detected

(TrailingWhitespace)


[warning] 159-159: [UnorderedKey] The SOLIDGATE_MERCHANT_ID key should go before the SOLIDGATE_SECRET_KEY key

(UnorderedKey)

🪛 GitHub Check: CodeQL
lib/payment/lib/providers/solidgate-provider.ts

[failure] 203-203: Use of password hash with insufficient computational effort
Password from an access to apiKey is hashed insecurely.
Password from an access to apiKey is hashed insecurely.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Greptile Review
  • GitHub Check: Lint and Build
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
lib/payment/config/payment-provider-manager.ts (1)

67-72: LGTM - Solidgate configuration fields added.

The configuration follows the established pattern for other providers. Note that SOLIDGATE_SECRET_KEY is read from environment but stored separately from solidgateApiKey.

lib/payment/lib/providers/solidgate-provider.ts (1)

193-204: CodeQL warning is a false positive - HMAC-SHA256 is appropriate here.

The static analysis tool flagged this as "password hash with insufficient computational effort," but this is a false positive. HMAC-SHA256 is the correct algorithm for webhook signature verification and payment intent signatures - it's not being used for password hashing. Webhook signatures require fast verification, not slow password hashing algorithms like bcrypt.

app/api/solidgate/webhook/route.ts (1)

1-14: The import path is correct. The function getOrCreateSolidgateProvider is re-exported from @/lib/auth via line 13 in lib/auth/index.ts (export * from '../payment/config/payment-provider-manager'), so importing it from @/lib/auth is valid and follows the established pattern.

package.json (1)

68-68: The @solidgate/react-sdk version must be corrected.

The npm registry shows the latest published version is 1.17.1, not 1.24.0. Installing with ^1.24.0 will fail or install an incorrect version. Update to ^1.17.1 or verify the intended version exists on npm.

⛔ Skipped due to learnings
Learnt from: CR
Repo: ever-works/ever-works-website-template PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-30T21:22:22.296Z
Learning: Use Node.js >= 20.19.0 as specified in package.json.engines
Learnt from: CR
Repo: ever-works/ever-works-website-template PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T18:34:28.233Z
Learning: Applies to **/*.{js,ts,tsx} : Use Node.js >= 20.19.0

Comment on lines +154 to +162
# Solidgate Configuration
SOLIDGATE_API_KEY=
SOLIDGATE_SECRET_KEY=
SOLIDGATE_WEBHOOK_SECRET=
NEXT_PUBLIC_SOLIDGATE_PUBLISHABLE_KEY=
SOLIDGATE_MERCHANT_ID=

# Optional: Custom API base URL
SOLIDGATE_API_BASE_URL=https://api.solidgate.com/v1
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix formatting issues and add missing environment variable.

Static analysis detected formatting issues on line 159:

  1. Trailing whitespace after SOLIDGATE_MERCHANT_ID=
  2. Spaces around the equal sign

Additionally, SOLIDGATE_SECRET_KEY is used in the provider implementation but not documented here.

🔎 Proposed fix
 # Solidgate Configuration
 SOLIDGATE_API_KEY=
 SOLIDGATE_SECRET_KEY=
 SOLIDGATE_WEBHOOK_SECRET=
 NEXT_PUBLIC_SOLIDGATE_PUBLISHABLE_KEY=
-SOLIDGATE_MERCHANT_ID=  
+SOLIDGATE_MERCHANT_ID=

 # Optional: Custom API base URL
 SOLIDGATE_API_BASE_URL=https://api.solidgate.com/v1

Note: SOLIDGATE_SECRET_KEY is already present (line 156), which is good. The main fix is removing the trailing whitespace on line 159.

📝 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
# Solidgate Configuration
SOLIDGATE_API_KEY=
SOLIDGATE_SECRET_KEY=
SOLIDGATE_WEBHOOK_SECRET=
NEXT_PUBLIC_SOLIDGATE_PUBLISHABLE_KEY=
SOLIDGATE_MERCHANT_ID=
# Optional: Custom API base URL
SOLIDGATE_API_BASE_URL=https://api.solidgate.com/v1
# Solidgate Configuration
SOLIDGATE_API_KEY=
SOLIDGATE_SECRET_KEY=
SOLIDGATE_WEBHOOK_SECRET=
NEXT_PUBLIC_SOLIDGATE_PUBLISHABLE_KEY=
SOLIDGATE_MERCHANT_ID=
# Optional: Custom API base URL
SOLIDGATE_API_BASE_URL=https://api.solidgate.com/v1
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 158-158: [UnorderedKey] The NEXT_PUBLIC_SOLIDGATE_PUBLISHABLE_KEY key should go before the SOLIDGATE_API_KEY key

(UnorderedKey)


[warning] 159-159: [SpaceCharacter] The line has spaces around equal sign

(SpaceCharacter)


[warning] 159-159: [TrailingWhitespace] Trailing whitespace detected

(TrailingWhitespace)


[warning] 159-159: [UnorderedKey] The SOLIDGATE_MERCHANT_ID key should go before the SOLIDGATE_SECRET_KEY key

(UnorderedKey)

🤖 Prompt for AI Agents
In .env.example around lines 154 to 162, fix formatting for the Solidgate
entries: remove the trailing whitespace after SOLIDGATE_MERCHANT_ID= (line 159)
and ensure there are no spaces around any equals signs for the SOLIDGATE_*
variables so they follow the same "KEY=value" pattern; confirm
SOLIDGATE_SECRET_KEY remains present (it is already on line 156) and leave the
optional SOLIDGATE_API_BASE_URL as-is.

Comment on lines +4 to +6
const appUrl =
process.env.NEXT_PUBLIC_APP_URL ??
(process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : 'https://demo.ever.works');
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove unused appUrl constant.

The appUrl constant is defined but never used in this file. This is dead code.

🔎 Proposed fix
 import { NextRequest, NextResponse } from 'next/server';
 import { auth, getOrCreateSolidgateProvider } from '@/lib/auth';
-
-const appUrl =
-	process.env.NEXT_PUBLIC_APP_URL ??
-	(process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : 'https://demo.ever.works');
📝 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
const appUrl =
process.env.NEXT_PUBLIC_APP_URL ??
(process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : 'https://demo.ever.works');
🤖 Prompt for AI Agents
In app/api/solidgate/checkout/route.ts around lines 4 to 6, the const appUrl is
declared but never referenced; remove the entire appUrl declaration (including
the conditional fallback expression) to eliminate dead code, or if the value is
actually needed later, replace its unused declaration by using the variable
where required; prefer deleting the declaration when unused and run lint/type
checks to ensure no further references remain.

Comment on lines +156 to +163
const {
amount,
currency = 'USD',
mode = 'one_time',
successUrl,
cancelUrl,
metadata = {}
} = await request.json();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add JSON parsing error handling and consider Zod validation.

request.json() can throw if the request body is not valid JSON. This should be caught and return a 400 error. Per coding guidelines, consider using Zod for input validation.

🔎 Proposed fix with Zod validation
import { z } from 'zod';

const checkoutSchema = z.object({
	amount: z.number().positive('Amount must be greater than 0'),
	currency: z.string().default('USD'),
	mode: z.enum(['one_time', 'subscription']).default('one_time'),
	successUrl: z.string().url('Invalid success URL'),
	cancelUrl: z.string().url('Invalid cancel URL'),
	metadata: z.record(z.unknown()).optional().default({})
});

// In the handler:
let body;
try {
	body = await request.json();
} catch {
	return NextResponse.json(
		{ error: 'Invalid JSON', message: 'Request body must be valid JSON' },
		{ status: 400 }
	);
}

const result = checkoutSchema.safeParse(body);
if (!result.success) {
	return NextResponse.json(
		{ error: 'Validation failed', message: result.error.issues[0].message },
		{ status: 400 }
	);
}

const { amount, currency, mode, successUrl, cancelUrl, metadata } = result.data;
🤖 Prompt for AI Agents
In app/api/solidgate/checkout/route.ts around lines 156-163, the call to
request.json() can throw on invalid JSON and there’s no input validation; catch
JSON parsing errors and return a 400 response, and validate the parsed body
using Zod (define a schema for amount, currency defaulting to 'USD', mode enum
with 'one_time'/'subscription' default, successUrl and cancelUrl as URLs, and
metadata as an optional record), use schema.safeParse to validate and on failure
return a 400 with the validation message, and only then destructure amount,
currency, mode, successUrl, cancelUrl, metadata from the validated result.data.

Comment on lines +91 to +107
export async function POST(request: NextRequest) {
try {
const body = await request.text();
const headersList = await headers();
const signature = headersList.get('x-signature') || headersList.get('solidgate-signature');

if (!signature) {
return NextResponse.json({ error: 'No signature provided' }, { status: 400 });
}

// Get or create Solidgate provider (singleton)
const solidgateProvider = getOrCreateSolidgateProvider();
const webhookResult = await solidgateProvider.handleWebhook(body, signature, body);

if (!webhookResult.received) {
return NextResponse.json({ error: 'Webhook not processed' }, { status: 400 });
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Redundant parameter and missing idempotency handling.

  1. Line 103: body is passed twice to handleWebhook(body, signature, body). The first parameter is the payload and the third is rawBody - these are identical here, which is fine, but consider clarity.

  2. Missing idempotency: Webhook handlers should be idempotent to handle replay attacks or duplicate deliveries. Consider tracking processed webhook IDs to prevent duplicate processing.

🔎 Suggested improvement for idempotency
// At the top of POST handler, after signature verification:
const webhookId = headersList.get('x-webhook-id') || webhookResult.id;

// Check if already processed (requires a cache/database check)
// const isProcessed = await checkWebhookProcessed(webhookId);
// if (isProcessed) {
//     return NextResponse.json({ received: true, duplicate: true });
// }

// After successful processing:
// await markWebhookProcessed(webhookId);
🤖 Prompt for AI Agents
In app/api/solidgate/webhook/route.ts around lines 91 to 107, remove the
confusing duplicate parameter by passing the parsed payload and a clearly named
rawBody (e.g., handleWebhook(payload, signature, rawBody)) or only pass rawBody
if the provider expects it, and implement idempotency: extract a webhook ID from
headers (e.g., x-webhook-id) or from the parsed webhookResult, check a
persistent or in-memory store/cache to see if that ID was already processed and
return a 200/accepted duplicate response if so, and after successful processing
mark the webhook ID as processed (persisting the marker with an expiry to
prevent replay); ensure error paths don’t mark processed and handle missing
webhook ID by falling back to a safe deduplication key (e.g.,
signature+timestamp).

Comment on lines +155 to +158
} catch (error) {
console.error('Solidgate webhook error:', error);
return NextResponse.json({ error: 'Webhook processing failed' }, { status: 400 });
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Distinguish between client and server errors.

All errors return HTTP 400, but signature verification failures (client error) and internal processing failures (server error) should have different status codes for proper observability.

🔎 Proposed fix
 	} catch (error) {
 		console.error('Solidgate webhook error:', error);
-		return NextResponse.json({ error: 'Webhook processing failed' }, { status: 400 });
+		// Check if it's a signature verification error (client error) vs internal error
+		const isSignatureError = error instanceof Error && error.message.includes('signature');
+		return NextResponse.json(
+			{ error: 'Webhook processing failed' },
+			{ status: isSignatureError ? 400 : 500 }
+		);
 	}
📝 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
} catch (error) {
console.error('Solidgate webhook error:', error);
return NextResponse.json({ error: 'Webhook processing failed' }, { status: 400 });
}
} catch (error) {
console.error('Solidgate webhook error:', error);
// Check if it's a signature verification error (client error) vs internal error
const isSignatureError = error instanceof Error && error.message.includes('signature');
return NextResponse.json(
{ error: 'Webhook processing failed' },
{ status: isSignatureError ? 400 : 500 }
);
}
🤖 Prompt for AI Agents
In app/api/solidgate/webhook/route.ts around lines 155-158, the catch block
currently returns HTTP 400 for all failures; change it to return a 4xx for
client-side signature/verification failures and 5xx for internal processing
errors: detect signature verification errors (e.g., by checking instanceof
SignatureVerificationError or by checking a specific error.code/message produced
by your verification helper) and return NextResponse.json({ error: 'Invalid
signature' }, { status: 401 }); for those, otherwise log the full error and
return NextResponse.json({ error: 'Webhook processing failed' }, { status: 500
});; ensure your logs include the error details for both cases.

Comment on lines +414 to +423
async createSetupIntent(user: User | null): Promise<SetupIntent> {
// Solidgate doesn't have a setup intent concept like Stripe
// Return a mock setup intent for compatibility
return {
id: `seti_${crypto.randomUUID()}`,
client_secret: `seti_${crypto.randomUUID()}_secret`,
status: 'requires_payment_method',
payment_method_types: ['card']
} as SetupIntent;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "createSetupIntent" --type ts -A 3 -B 3

Repository: ever-works/ever-works-website-template

Length of output: 10437


🏁 Script executed:

# Check how the active payment provider is determined and used
rg -n "getOrCreateSolidgateProvider|solidgate-provider" --type ts -B 2 -A 2

Repository: ever-works/ever-works-website-template

Length of output: 3193


🏁 Script executed:

# Check if payment-service.createSetupIntent is called directly
rg -n "payment-service|PaymentService" --type ts | grep -i "create" | head -20

Repository: ever-works/ever-works-website-template

Length of output: 64


🏁 Script executed:

# Check all API routes that might handle setup intent
fd -e ts -path "*/api/*" -path "*setup*" -o -path "*/api/*" -path "*intent*"

Repository: ever-works/ever-works-website-template

Length of output: 250


🏁 Script executed:

# Check if there's a Solidgate setup-intent API route
find . -type f -name "*.ts" -path "*/api/*" | xargs grep -l "setup-intent" 2>/dev/null

Repository: ever-works/ever-works-website-template

Length of output: 164


🏁 Script executed:

# Check the payment-provider-manager to understand provider selection
cat -n lib/payment/config/payment-provider-manager.ts | head -100

Repository: ever-works/ever-works-website-template

Length of output: 4591


🏁 Script executed:

# Search for direct calls to provider.createSetupIntent in context
rg -n "provider\.createSetupIntent|paymentService\.createSetupIntent" --type ts -B 5 -A 5

Repository: ever-works/ever-works-website-template

Length of output: 704


Setup-intent implementation is not invoked by calling code and won't cause production issues.

The Solidgate provider's createSetupIntent returns mock data as a placeholder since Solidgate doesn't natively support setup intents. However, this implementation is never called—the only setup-intent API endpoint is hardcoded to use Stripe. If Solidgate becomes the active provider, the setup-intent flow would break due to the hardcoded endpoint. Consider documenting this limitation or making the endpoint provider-agnostic.

🤖 Prompt for AI Agents
In lib/payment/lib/providers/solidgate-provider.ts around lines 414-423, the
Solidgate provider implements a mock createSetupIntent but the application's
setup-intent API is hardcoded to Stripe so this method is never invoked; make
the setup-intent endpoint provider-agnostic by routing calls to the configured
payment provider's createSetupIntent (i.e., resolve the active provider from
configuration or DI and call provider.createSetupIntent(user)), or alternatively
document the limitation clearly and keep the Stripe-only behavior; ensure the
endpoint handles providers that return mocks (like Solidgate) and add a
unit/integration test to cover the provider selection behavior.

Comment on lines +425 to +437
async createPaymentIntent(params: CreatePaymentParams): Promise<PaymentIntent> {
try {
const { amount, currency, metadata, customerId, successUrl, cancelUrl } = params;

const orderId = `order_${crypto.randomUUID()}`;
const paymentIntentId = `pi_${crypto.randomUUID()}`;
const paymentAmount = Math.round(
metadata?.planId === '1'
? (PRICES.us?.free?.amount || amount) * 100
: metadata?.planId === '2'
? (PRICES.us?.oneTime?.amount || amount) * 100
: (PRICES.us?.subscription?.amount || amount) * 100
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded US pricing may cause issues for international users.

The price tier logic hardcodes PRICES.us which won't work correctly for users in other regions. Consider parameterizing the locale or using the amount passed directly.

🔎 Proposed fix
 	async createPaymentIntent(params: CreatePaymentParams): Promise<PaymentIntent> {
 		try {
-			const { amount, currency, metadata, customerId, successUrl, cancelUrl } = params;
+			const { amount, currency, metadata, customerId, successUrl, cancelUrl, locale = 'us' } = params;

 			const orderId = `order_${crypto.randomUUID()}`;
 			const paymentIntentId = `pi_${crypto.randomUUID()}`;
+			const pricing = PRICES[locale] || PRICES.us;
 			const paymentAmount = Math.round(
 				metadata?.planId === '1'
-					? (PRICES.us?.free?.amount || amount) * 100
+					? (pricing?.free?.amount || amount) * 100
 					: metadata?.planId === '2'
-						? (PRICES.us?.oneTime?.amount || amount) * 100
-						: (PRICES.us?.subscription?.amount || amount) * 100
+						? (pricing?.oneTime?.amount || amount) * 100
+						: (pricing?.subscription?.amount || amount) * 100
 			);

Alternatively, consider whether the PRICES lookup is needed at all - the amount parameter is already validated in the checkout route.

📝 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
async createPaymentIntent(params: CreatePaymentParams): Promise<PaymentIntent> {
try {
const { amount, currency, metadata, customerId, successUrl, cancelUrl } = params;
const orderId = `order_${crypto.randomUUID()}`;
const paymentIntentId = `pi_${crypto.randomUUID()}`;
const paymentAmount = Math.round(
metadata?.planId === '1'
? (PRICES.us?.free?.amount || amount) * 100
: metadata?.planId === '2'
? (PRICES.us?.oneTime?.amount || amount) * 100
: (PRICES.us?.subscription?.amount || amount) * 100
);
async createPaymentIntent(params: CreatePaymentParams): Promise<PaymentIntent> {
try {
const { amount, currency, metadata, customerId, successUrl, cancelUrl, locale = 'us' } = params;
const orderId = `order_${crypto.randomUUID()}`;
const paymentIntentId = `pi_${crypto.randomUUID()}`;
const pricing = PRICES[locale] || PRICES.us;
const paymentAmount = Math.round(
metadata?.planId === '1'
? (pricing?.free?.amount || amount) * 100
: metadata?.planId === '2'
? (pricing?.oneTime?.amount || amount) * 100
: (pricing?.subscription?.amount || amount) * 100
);

Comment on lines +648 to +655
try {
// Verify webhook signature
if (this.webhookSecret && rawBody) {
const expectedSignature = this.generateSignature(rawBody, this.webhookSecret);
if (signature !== expectedSignature) {
throw new Error('Invalid webhook signature');
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Timing attack vulnerability in signature verification.

Using direct string comparison (===) for cryptographic signatures is vulnerable to timing attacks. An attacker can measure response times to gradually determine the correct signature.

🔎 Proposed fix using constant-time comparison
 	async handleWebhook(
 		payload: any,
 		signature: string,
 		rawBody?: string,
 		timestamp?: string,
 		webhookId?: string
 	): Promise<WebhookResult> {
 		try {
 			// Verify webhook signature
 			if (this.webhookSecret && rawBody) {
 				const expectedSignature = this.generateSignature(rawBody, this.webhookSecret);
-				if (signature !== expectedSignature) {
+				// Use constant-time comparison to prevent timing attacks
+				const signatureBuffer = Buffer.from(signature);
+				const expectedBuffer = Buffer.from(expectedSignature);
+				if (signatureBuffer.length !== expectedBuffer.length || 
+				    !crypto.timingSafeEqual(signatureBuffer, expectedBuffer)) {
 					throw new Error('Invalid webhook signature');
 				}
 			}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In lib/payment/lib/providers/solidgate-provider.ts around lines 648 to 655, the
code uses direct string comparison (===) to check the webhook signature which is
vulnerable to timing attacks; replace this with a constant-time comparison by
converting both the expected signature and the provided signature to Buffer
(ensuring they are the same length) and using a constant-time comparison
function (e.g., crypto.timingSafeEqual) to validate equality; if lengths differ,
fail the check without using normal string compare, and throw the same 'Invalid
webhook signature' error on mismatch.

Comment on lines +751 to +776
getUIComponents(): UIComponents {
// Create a function that will inject the required props into the SolidgateElements component
const SolidgatePaymentFormWithConfig = (props: PaymentFormProps) => {
// Generate payment intent and signature for the SDK
const paymentIntent = props.clientSecret || `payment_${crypto.randomUUID()}`;
const merchantId = this.getMerchantId();
const signature = this.generatePaymentIntentSignature(paymentIntent, merchantId);

return React.createElement(SolidgateElementsWrapper, {
...props,
solidgatePublicKey: this.publishableKey,
merchantId: merchantId,
paymentIntent: paymentIntent,
signature: signature
});
};

return {
// We use our wrapper function to configure the component with the required props
PaymentForm: SolidgatePaymentFormWithConfig,
logo: '/assets/payment/solidgate/solidgate-logo.svg',
cardBrands: solidgateCardBrands,
supportedPaymentMethods: ['card'],
translations: solidgateTranslations
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Signature generated at render time may cause issues.

In getUIComponents, the signature is generated using crypto.randomUUID() for the payment intent if clientSecret is not provided. This means every render will generate a new signature, which may cause payment processing issues.

🔎 Analysis

The issue is on line 755:

const paymentIntent = props.clientSecret || `payment_${crypto.randomUUID()}`;

If clientSecret is not provided, a new random payment intent ID is generated on each render, along with a new signature. This could cause:

  1. Multiple payment intents being created
  2. Signature mismatch errors if the SDK retries with a stale signature

Consider either:

  1. Requiring clientSecret to be provided
  2. Memoizing the generated payment intent ID
  3. Moving this logic to an effect that runs once

Comment on lines +68 to +84
const handleError = useCallback(
(error: any) => {
setIsLoading(false);
// Extract error message from various possible formats
const errorMsg =
error?.message ||
error?.error ||
error?.data?.message ||
error?.data?.error ||
typeof error === 'string'
? error
: 'Payment processing failed';
setErrorMessage(errorMsg);
handlePaymentError(new Error(errorMsg));
},
[handlePaymentError]
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix operator precedence bug in error message extraction.

The ternary operator has incorrect precedence. The typeof error === 'string' ? error : 'Payment processing failed' branch will never execute correctly because of how the ternary is parsed.

🔎 Proposed fix
 	const handleError = useCallback(
 		(error: any) => {
 			setIsLoading(false);
 			// Extract error message from various possible formats
 			const errorMsg =
-				error?.message ||
-				error?.error ||
-				error?.data?.message ||
-				error?.data?.error ||
-				typeof error === 'string'
-					? error
-					: 'Payment processing failed';
+				error?.message ||
+				error?.error ||
+				error?.data?.message ||
+				error?.data?.error ||
+				(typeof error === 'string' ? error : 'Payment processing failed');
 			setErrorMessage(errorMsg);
 			handlePaymentError(new Error(errorMsg));
 		},
 		[handlePaymentError]
 	);

Without parentheses, the expression parses as:

(error?.message || error?.error || ... || typeof error === 'string') ? error : 'Payment processing failed'

Which means if any of the earlier conditions are truthy, error (the whole object) is assigned, not the extracted message.

📝 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
const handleError = useCallback(
(error: any) => {
setIsLoading(false);
// Extract error message from various possible formats
const errorMsg =
error?.message ||
error?.error ||
error?.data?.message ||
error?.data?.error ||
typeof error === 'string'
? error
: 'Payment processing failed';
setErrorMessage(errorMsg);
handlePaymentError(new Error(errorMsg));
},
[handlePaymentError]
);
const handleError = useCallback(
(error: any) => {
setIsLoading(false);
// Extract error message from various possible formats
const errorMsg =
error?.message ||
error?.error ||
error?.data?.message ||
error?.data?.error ||
(typeof error === 'string' ? error : 'Payment processing failed');
setErrorMessage(errorMsg);
handlePaymentError(new Error(errorMsg));
},
[handlePaymentError]
);
🤖 Prompt for AI Agents
In lib/payment/ui/solidgate/solidgate-elements.tsx around lines 68 to 84, the
error message extraction uses a ternary without parentheses causing operator
precedence to bind the ternary to only the last operand; change the expression
so the ternary is evaluated as intended by grouping the OR chain before the
ternary (e.g., wrap the OR chain in parentheses) or rewrite it to explicitly
check for a string error last: compute const errorMsg = (error?.message ||
error?.error || error?.data?.message || error?.data?.error) || (typeof error ===
'string' ? error : 'Payment processing failed') then use
setErrorMessage(errorMsg) and handlePaymentError(new Error(errorMsg)).

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Dec 25, 2025

Greptile Summary

  • Integrates Solidgate as a new payment provider alongside existing Stripe, LemonSqueezy, and Polar providers with full support for payment intents, subscriptions, refunds, and webhook processing
  • Implements complete Solidgate integration including React SDK components, API endpoints (/api/solidgate/checkout, /api/solidgate/webhook), configuration management, and provider factory integration
  • Adds comprehensive environment variable configuration for Solidgate including API keys, webhook secrets, merchant ID, and optional custom base URL settings

Important Files Changed

Filename Overview
lib/payment/lib/providers/solidgate-provider.ts Complete implementation of Solidgate payment provider with payment processing, webhook handling, and signature verification
app/api/solidgate/webhook/route.ts New webhook endpoint with event processing and duplicate case handling that needs review for potential logic issues
lib/payment/config/payment-provider-manager.ts Added Solidgate configuration management and validation following established provider patterns

Confidence score: 3/5

  • This PR adds a complex payment integration with potential webhook processing issues that could cause production problems
  • Score lowered due to duplicate webhook event case handling, incomplete error validation in webhook endpoint, and unused code in React components that indicate rushed implementation
  • Pay close attention to app/api/solidgate/webhook/route.ts for the duplicate case handling pattern and lib/payment/ui/solidgate/solidgate-elements.tsx for unused imports

Sequence Diagram

sequenceDiagram
    participant User
    participant Frontend
    participant CheckoutAPI
    participant SolidgateProvider
    participant SolidgateAPI
    participant WebhookAPI

    User->>Frontend: "Click pay button"
    Frontend->>CheckoutAPI: "POST /api/solidgate/checkout"
    CheckoutAPI->>SolidgateProvider: "getCustomerId(user)"
    SolidgateProvider->>SolidgateAPI: "Create customer if needed"
    SolidgateAPI-->>SolidgateProvider: "Customer ID"
    CheckoutAPI->>SolidgateProvider: "createPaymentIntent(params)"
    SolidgateProvider->>SolidgateAPI: "POST /payments"
    SolidgateAPI-->>SolidgateProvider: "Payment response"
    SolidgateProvider-->>CheckoutAPI: "Payment intent"
    CheckoutAPI-->>Frontend: "Checkout session URL"
    Frontend->>User: "Redirect to Solidgate checkout"
    User->>SolidgateAPI: "Complete payment"
    SolidgateAPI->>WebhookAPI: "POST /api/solidgate/webhook"
    WebhookAPI->>SolidgateProvider: "handleWebhook(payload, signature)"
    WebhookAPI->>WebhookAPI: "handlePaymentSucceeded(data)"
    WebhookAPI-->>SolidgateAPI: "Webhook confirmed"
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

8 files reviewed, 8 comments

Edit Code Review Agent Settings | Greptile

SOLIDGATE_SECRET_KEY=
SOLIDGATE_WEBHOOK_SECRET=
NEXT_PUBLIC_SOLIDGATE_PUBLISHABLE_KEY=
SOLIDGATE_MERCHANT_ID=
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Extra trailing space after SOLIDGATE_MERCHANT_ID=

Suggested change
SOLIDGATE_MERCHANT_ID=
SOLIDGATE_MERCHANT_ID=

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: .env.example
Line: 159:159

Comment:
**style:** Extra trailing space after SOLIDGATE_MERCHANT_ID=

```suggestion
SOLIDGATE_MERCHANT_ID=
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

apiKey: solidgateApiKey,
webhookSecret: solidgateWebhookSecret || '',
options: {
publishableKey: solidgatePublishableKey || solidgateApiKey,
Copy link
Contributor

Choose a reason for hiding this comment

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

style: fallback to solidgateApiKey when publishableKey is missing may cause issues - publishable keys are typically different from API keys and have different security scopes. Should the publishable key fallback to the API key, or should this be left empty if not provided?

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/payment/config/payment-provider-manager.ts
Line: 241:241

Comment:
**style:** fallback to `solidgateApiKey` when publishableKey is missing may cause issues - publishable keys are typically different from API keys and have different security scopes. Should the publishable key fallback to the API key, or should this be left empty if not provided?

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

);
}

const solidgateCustomerId = await solidgateProvider.getCustomerId(session.user as any);
Copy link
Contributor

Choose a reason for hiding this comment

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

style: avoid as any type assertion - consider proper typing for session.user

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/api/solidgate/checkout/route.ts
Line: 185:185

Comment:
**style:** avoid `as any` type assertion - consider proper typing for session.user

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

return NextResponse.json({
data: {
id: paymentIntent.id,
url: paymentIntent.clientSecret // In Solidgate, clientSecret contains the payment URL
Copy link
Contributor

Choose a reason for hiding this comment

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

style: potential confusion: comment suggests clientSecret contains payment URL, but property is named 'url'. does Solidgate's clientSecret actually contain the payment URL or should this be clarified?

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/api/solidgate/checkout/route.ts
Line: 214:214

Comment:
**style:** potential confusion: comment suggests clientSecret contains payment URL, but property is named 'url'. does Solidgate's clientSecret actually contain the payment URL or should this be clarified?

How can I resolve this? If you propose a fix, please make it concise.


// Get or create Solidgate provider (singleton)
const solidgateProvider = getOrCreateSolidgateProvider();
const webhookResult = await solidgateProvider.handleWebhook(body, signature, body);
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: The third parameter to handleWebhook is the same as the first (body). This looks like a copy-paste error or incorrect API usage. Is the third parameter supposed to be different from the body, perhaps a parsed JSON object?

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/api/solidgate/webhook/route.ts
Line: 103:103

Comment:
**logic:** The third parameter to handleWebhook is the same as the first (body). This looks like a copy-paste error or incorrect API usage. Is the third parameter supposed to be different from the body, perhaps a parsed JSON object?

How can I resolve this? If you propose a fix, please make it concise.


constructor(config: PaymentProviderConfig) {
this.apiKey = config.apiKey;
this.secretKey = config.secretKey || config.apiKey; // Use apiKey as secretKey if not provided
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: Using apiKey as fallback for secretKey may compromise security. Solidgate likely provides separate keys for different purposes. Should the secret key have its own required environment variable instead of falling back to the API key?

Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/payment/lib/providers/solidgate-provider.ts
Line: 134:134

Comment:
**logic:** Using `apiKey` as fallback for `secretKey` may compromise security. Solidgate likely provides separate keys for different purposes. Should the secret key have its own required environment variable instead of falling back to the API key?

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +650 to +655
if (this.webhookSecret && rawBody) {
const expectedSignature = this.generateSignature(rawBody, this.webhookSecret);
if (signature !== expectedSignature) {
throw new Error('Invalid webhook signature');
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: Webhook signature verification is optional - if webhookSecret is empty, signatures are not verified, which could allow unauthorized webhook calls.

Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/payment/lib/providers/solidgate-provider.ts
Line: 650:655

Comment:
**logic:** Webhook signature verification is optional - if `webhookSecret` is empty, signatures are not verified, which could allow unauthorized webhook calls.

How can I resolve this? If you propose a fix, please make it concise.

// Create a function that will inject the required props into the SolidgateElements component
const SolidgatePaymentFormWithConfig = (props: PaymentFormProps) => {
// Generate payment intent and signature for the SDK
const paymentIntent = props.clientSecret || `payment_${crypto.randomUUID()}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Using clientSecret as payment intent fallback could cause confusion since clientSecret typically contains sensitive payment data in other providers. Is this the intended behavior for the Solidgate SDK integration?

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/payment/lib/providers/solidgate-provider.ts
Line: 755:755

Comment:
**style:** Using `clientSecret` as payment intent fallback could cause confusion since `clientSecret` typically contains sensitive payment data in other providers. Is this the intended behavior for the Solidgate SDK integration?

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

7 issues found across 9 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="lib/payment/config/payment-provider-manager.ts">

<violation number="1" location="lib/payment/config/payment-provider-manager.ts:241">
P1: Security risk: Falling back to the secret `solidgateApiKey` for `publishableKey` could expose the secret API key in client-side code. The `publishableKey` (from `NEXT_PUBLIC_*` env var) is intended for browser exposure, while `solidgateApiKey` is a secret. If `NEXT_PUBLIC_SOLIDGATE_PUBLISHABLE_KEY` is not set, the secret key would be leaked to the client.

Consider either throwing an error if `publishableKey` is not set (like Stripe validation does), or use an empty string as fallback.</violation>
</file>

<file name="app/api/solidgate/checkout/route.ts">

<violation number="1" location="app/api/solidgate/checkout/route.ts:165">
P2: Amount validation doesn&#39;t verify the type is a number. Consider using `typeof amount !== &#39;number&#39;` check or Zod schema validation to ensure proper type safety.</violation>
</file>

<file name=".env.example">

<violation number="1" location=".env.example:159">
P2: Trailing whitespace after `=` will cause the merchant ID to have spaces as its value instead of being empty. Remove the trailing spaces.</violation>
</file>

<file name="lib/payment/lib/providers/solidgate-provider.ts">

<violation number="1" location="lib/payment/lib/providers/solidgate-provider.ts:651">
P0: Webhook signature verification is silently skipped when `webhookSecret` is empty or `rawBody` is not provided. This allows attackers to forge webhook events. Consider requiring signature verification or at least logging a warning when verification cannot be performed in production.</violation>

<violation number="2" location="lib/payment/lib/providers/solidgate-provider.ts:653">
P1: Webhook signature verification uses simple string comparison which is vulnerable to timing attacks. Use `crypto.timingSafeEqual()` for constant-time comparison, as done in other payment providers in this codebase.</violation>

<violation number="3" location="lib/payment/lib/providers/solidgate-provider.ts:756">
P1: Using `crypto.randomUUID()` inside a React component function that may render client-side will fail since `crypto` from Node.js is not available in browsers. This should use `props.clientSecret` or throw an error if it&#39;s not provided rather than generating a fallback UUID.</violation>
</file>

<file name="lib/payment/ui/solidgate/solidgate-elements.tsx">

<violation number="1" location="lib/payment/ui/solidgate/solidgate-elements.tsx:77">
P1: Operator precedence bug: The ternary operator has lower precedence than `||`, causing the entire expression to evaluate incorrectly. When `error?.message` exists, `errorMsg` will be set to the entire `error` object instead of the message string, displaying `[object Object]` to users.</violation>
</file>

Reply to cubic to teach it or ask questions. Tag @cubic-dev-ai to re-run a review.

apiKey: solidgateApiKey,
webhookSecret: solidgateWebhookSecret || '',
options: {
publishableKey: solidgatePublishableKey || solidgateApiKey,
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 25, 2025

Choose a reason for hiding this comment

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

P1: Security risk: Falling back to the secret solidgateApiKey for publishableKey could expose the secret API key in client-side code. The publishableKey (from NEXT_PUBLIC_* env var) is intended for browser exposure, while solidgateApiKey is a secret. If NEXT_PUBLIC_SOLIDGATE_PUBLISHABLE_KEY is not set, the secret key would be leaked to the client.

Consider either throwing an error if publishableKey is not set (like Stripe validation does), or use an empty string as fallback.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/payment/config/payment-provider-manager.ts, line 241:

<comment>Security risk: Falling back to the secret `solidgateApiKey` for `publishableKey` could expose the secret API key in client-side code. The `publishableKey` (from `NEXT_PUBLIC_*` env var) is intended for browser exposure, while `solidgateApiKey` is a secret. If `NEXT_PUBLIC_SOLIDGATE_PUBLISHABLE_KEY` is not set, the secret key would be leaked to the client.

Consider either throwing an error if `publishableKey` is not set (like Stripe validation does), or use an empty string as fallback.</comment>

<file context>
@@ -198,6 +222,30 @@ class ConfigManager {
+			apiKey: solidgateApiKey,
+			webhookSecret: solidgateWebhookSecret || &#39;&#39;,
+			options: {
+				publishableKey: solidgatePublishableKey || solidgateApiKey,
+				merchantId: solidgateMerchantId || &#39;&#39;,
+				apiBaseUrl: solidgateApiBaseUrl
</file context>
Suggested change
publishableKey: solidgatePublishableKey || solidgateApiKey,
publishableKey: solidgatePublishableKey || '',
Fix with Cubic

metadata = {}
} = await request.json();

if (!amount || amount <= 0) {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 25, 2025

Choose a reason for hiding this comment

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

P2: Amount validation doesn't verify the type is a number. Consider using typeof amount !== 'number' check or Zod schema validation to ensure proper type safety.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/solidgate/checkout/route.ts, line 165:

<comment>Amount validation doesn&#39;t verify the type is a number. Consider using `typeof amount !== &#39;number&#39;` check or Zod schema validation to ensure proper type safety.</comment>

<file context>
@@ -0,0 +1,233 @@
+			metadata = {}
+		} = await request.json();
+
+		if (!amount || amount &lt;= 0) {
+			return NextResponse.json(
+				{
</file context>
Fix with Cubic

SOLIDGATE_SECRET_KEY=
SOLIDGATE_WEBHOOK_SECRET=
NEXT_PUBLIC_SOLIDGATE_PUBLISHABLE_KEY=
SOLIDGATE_MERCHANT_ID=
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 25, 2025

Choose a reason for hiding this comment

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

P2: Trailing whitespace after = will cause the merchant ID to have spaces as its value instead of being empty. Remove the trailing spaces.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .env.example, line 159:

<comment>Trailing whitespace after `=` will cause the merchant ID to have spaces as its value instead of being empty. Remove the trailing spaces.</comment>

<file context>
@@ -151,6 +151,16 @@ SMTP_PORT=587
+SOLIDGATE_SECRET_KEY=
+SOLIDGATE_WEBHOOK_SECRET=
+NEXT_PUBLIC_SOLIDGATE_PUBLISHABLE_KEY=
+SOLIDGATE_MERCHANT_ID=  
+
+# Optional: Custom API base URL
</file context>
Suggested change
SOLIDGATE_MERCHANT_ID=
SOLIDGATE_MERCHANT_ID=
Fix with Cubic

const SolidgatePaymentFormWithConfig = (props: PaymentFormProps) => {
// Generate payment intent and signature for the SDK
const paymentIntent = props.clientSecret || `payment_${crypto.randomUUID()}`;
const merchantId = this.getMerchantId();
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 25, 2025

Choose a reason for hiding this comment

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

P1: Using crypto.randomUUID() inside a React component function that may render client-side will fail since crypto from Node.js is not available in browsers. This should use props.clientSecret or throw an error if it's not provided rather than generating a fallback UUID.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/payment/lib/providers/solidgate-provider.ts, line 756:

<comment>Using `crypto.randomUUID()` inside a React component function that may render client-side will fail since `crypto` from Node.js is not available in browsers. This should use `props.clientSecret` or throw an error if it&#39;s not provided rather than generating a fallback UUID.</comment>

<file context>
@@ -1,177 +1,801 @@
+		const SolidgatePaymentFormWithConfig = (props: PaymentFormProps) =&gt; {
+			// Generate payment intent and signature for the SDK
+			const paymentIntent = props.clientSecret || `payment_${crypto.randomUUID()}`;
+			const merchantId = this.getMerchantId();
+			const signature = this.generatePaymentIntentSignature(paymentIntent, merchantId);
+
</file context>
Fix with Cubic

if (this.webhookSecret && rawBody) {
const expectedSignature = this.generateSignature(rawBody, this.webhookSecret);
if (signature !== expectedSignature) {
throw new Error('Invalid webhook signature');
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 25, 2025

Choose a reason for hiding this comment

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

P1: Webhook signature verification uses simple string comparison which is vulnerable to timing attacks. Use crypto.timingSafeEqual() for constant-time comparison, as done in other payment providers in this codebase.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/payment/lib/providers/solidgate-provider.ts, line 653:

<comment>Webhook signature verification uses simple string comparison which is vulnerable to timing attacks. Use `crypto.timingSafeEqual()` for constant-time comparison, as done in other payment providers in this codebase.</comment>

<file context>
@@ -1,177 +1,801 @@
+			if (this.webhookSecret &amp;&amp; rawBody) {
+				const expectedSignature = this.generateSignature(rawBody, this.webhookSecret);
+				if (signature !== expectedSignature) {
+					throw new Error(&#39;Invalid webhook signature&#39;);
+				}
+			}
</file context>
Fix with Cubic

try {
// Verify webhook signature
if (this.webhookSecret && rawBody) {
const expectedSignature = this.generateSignature(rawBody, this.webhookSecret);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 25, 2025

Choose a reason for hiding this comment

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

P0: Webhook signature verification is silently skipped when webhookSecret is empty or rawBody is not provided. This allows attackers to forge webhook events. Consider requiring signature verification or at least logging a warning when verification cannot be performed in production.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/payment/lib/providers/solidgate-provider.ts, line 651:

<comment>Webhook signature verification is silently skipped when `webhookSecret` is empty or `rawBody` is not provided. This allows attackers to forge webhook events. Consider requiring signature verification or at least logging a warning when verification cannot be performed in production.</comment>

<file context>
@@ -1,177 +1,801 @@
+		try {
+			// Verify webhook signature
+			if (this.webhookSecret &amp;&amp; rawBody) {
+				const expectedSignature = this.generateSignature(rawBody, this.webhookSecret);
+				if (signature !== expectedSignature) {
+					throw new Error(&#39;Invalid webhook signature&#39;);
</file context>
Fix with Cubic

error?.error ||
error?.data?.message ||
error?.data?.error ||
typeof error === 'string'
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 25, 2025

Choose a reason for hiding this comment

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

P1: Operator precedence bug: The ternary operator has lower precedence than ||, causing the entire expression to evaluate incorrectly. When error?.message exists, errorMsg will be set to the entire error object instead of the message string, displaying [object Object] to users.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/payment/ui/solidgate/solidgate-elements.tsx, line 77:

<comment>Operator precedence bug: The ternary operator has lower precedence than `||`, causing the entire expression to evaluate incorrectly. When `error?.message` exists, `errorMsg` will be set to the entire `error` object instead of the message string, displaying `[object Object]` to users.</comment>

<file context>
@@ -0,0 +1,121 @@
+				error?.error ||
+				error?.data?.message ||
+				error?.data?.error ||
+				typeof error === &#39;string&#39;
+					? error
+					: &#39;Payment processing failed&#39;;
</file context>
Fix with Cubic

@evereq evereq changed the base branch from develop to feat/config-service-foundation December 27, 2025 17:30
@evereq evereq changed the base branch from feat/config-service-foundation to develop December 27, 2025 17:30
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.

2 participants