-
Notifications
You must be signed in to change notification settings - Fork 7
feat: integrate Solidgate payment provider with React SDK #434
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
- 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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis 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
Sequence DiagramssequenceDiagram
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}
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
| * 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
an access to apiKey
Password from
an access to apiKey
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.
There was a problem hiding this 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: Avoidas anytype assertion.The type assertion
session.user as anybypasses TypeScript's type checking. Define a proper type or interface for the user object expected bygetCustomerId.🔎 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
getCustomerIdmethod 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) andsdkInstanceRefare 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 onwebhookSecretfor signature verification andmerchantIdfor 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (8)
.env.exampleapp/api/solidgate/checkout/route.tsapp/api/solidgate/webhook/route.tslib/payment/config/payment-provider-manager.tslib/payment/lib/payment-provider-factory.tslib/payment/lib/providers/solidgate-provider.tslib/payment/ui/solidgate/solidgate-elements.tsxpackage.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.tsxapp/api/solidgate/webhook/route.tslib/payment/lib/payment-provider-factory.tslib/payment/config/payment-provider-manager.tsapp/api/solidgate/checkout/route.tslib/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.tsxapp/api/solidgate/webhook/route.tslib/payment/lib/payment-provider-factory.tslib/payment/config/payment-provider-manager.tsapp/api/solidgate/checkout/route.tslib/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.tsxapp/api/solidgate/webhook/route.tslib/payment/lib/payment-provider-factory.tslib/payment/config/payment-provider-manager.tsapp/api/solidgate/checkout/route.tslib/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.tsxapp/api/solidgate/webhook/route.tslib/payment/lib/payment-provider-factory.tslib/payment/config/payment-provider-manager.tsapp/api/solidgate/checkout/route.tslib/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.tsapp/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.tsapp/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.tsapp/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_KEYis read from environment but stored separately fromsolidgateApiKey.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 functiongetOrCreateSolidgateProvideris re-exported from@/lib/authvia line 13 inlib/auth/index.ts(export * from '../payment/config/payment-provider-manager'), so importing it from@/lib/authis 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.0will fail or install an incorrect version. Update to^1.17.1or 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.enginesLearnt 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
| # 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix formatting issues and add missing environment variable.
Static analysis detected formatting issues on line 159:
- Trailing whitespace after
SOLIDGATE_MERCHANT_ID= - 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/v1Note: 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.
| # 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.
| const appUrl = | ||
| process.env.NEXT_PUBLIC_APP_URL ?? | ||
| (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : 'https://demo.ever.works'); |
There was a problem hiding this comment.
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.
| 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.
| const { | ||
| amount, | ||
| currency = 'USD', | ||
| mode = 'one_time', | ||
| successUrl, | ||
| cancelUrl, | ||
| metadata = {} | ||
| } = await request.json(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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 }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant parameter and missing idempotency handling.
-
Line 103:
bodyis passed twice tohandleWebhook(body, signature, body). The first parameter is the payload and the third israwBody- these are identical here, which is fine, but consider clarity. -
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).
| } catch (error) { | ||
| console.error('Solidgate webhook error:', error); | ||
| return NextResponse.json({ error: 'Webhook processing failed' }, { status: 400 }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| } 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.
| 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; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
rg -n "createSetupIntent" --type ts -A 3 -B 3Repository: 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 2Repository: 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 -20Repository: 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/nullRepository: 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 -100Repository: 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 5Repository: 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.
| 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 | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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 | |
| ); |
| try { | ||
| // Verify webhook signature | ||
| if (this.webhookSecret && rawBody) { | ||
| const expectedSignature = this.generateSignature(rawBody, this.webhookSecret); | ||
| if (signature !== expectedSignature) { | ||
| throw new Error('Invalid webhook signature'); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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 | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
- Multiple payment intents being created
- Signature mismatch errors if the SDK retries with a stale signature
Consider either:
- Requiring
clientSecretto be provided - Memoizing the generated payment intent ID
- Moving this logic to an effect that runs once
| 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] | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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 Summary
Important Files Changed
Confidence score: 3/5
Sequence DiagramsequenceDiagram
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"
|
There was a problem hiding this 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
| SOLIDGATE_SECRET_KEY= | ||
| SOLIDGATE_WEBHOOK_SECRET= | ||
| NEXT_PUBLIC_SOLIDGATE_PUBLISHABLE_KEY= | ||
| SOLIDGATE_MERCHANT_ID= |
There was a problem hiding this comment.
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=
| 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, |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.| if (this.webhookSecret && rawBody) { | ||
| const expectedSignature = this.generateSignature(rawBody, this.webhookSecret); | ||
| if (signature !== expectedSignature) { | ||
| throw new Error('Invalid webhook signature'); | ||
| } | ||
| } |
There was a problem hiding this comment.
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()}`; |
There was a problem hiding this comment.
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.There was a problem hiding this 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't verify the type is a number. Consider using `typeof amount !== 'number'` 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'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, |
There was a problem hiding this comment.
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 || '',
+ options: {
+ publishableKey: solidgatePublishableKey || solidgateApiKey,
+ merchantId: solidgateMerchantId || '',
+ apiBaseUrl: solidgateApiBaseUrl
</file context>
| publishableKey: solidgatePublishableKey || solidgateApiKey, | |
| publishableKey: solidgatePublishableKey || '', |
| metadata = {} | ||
| } = await request.json(); | ||
|
|
||
| if (!amount || amount <= 0) { |
There was a problem hiding this comment.
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't verify the type is a number. Consider using `typeof amount !== 'number'` check or Zod schema validation to ensure proper type safety.</comment>
<file context>
@@ -0,0 +1,233 @@
+ metadata = {}
+ } = await request.json();
+
+ if (!amount || amount <= 0) {
+ return NextResponse.json(
+ {
</file context>
| SOLIDGATE_SECRET_KEY= | ||
| SOLIDGATE_WEBHOOK_SECRET= | ||
| NEXT_PUBLIC_SOLIDGATE_PUBLISHABLE_KEY= | ||
| SOLIDGATE_MERCHANT_ID= |
There was a problem hiding this comment.
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>
| SOLIDGATE_MERCHANT_ID= | |
| SOLIDGATE_MERCHANT_ID= |
| const SolidgatePaymentFormWithConfig = (props: PaymentFormProps) => { | ||
| // Generate payment intent and signature for the SDK | ||
| const paymentIntent = props.clientSecret || `payment_${crypto.randomUUID()}`; | ||
| const merchantId = this.getMerchantId(); |
There was a problem hiding this comment.
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's not provided rather than generating a fallback UUID.</comment>
<file context>
@@ -1,177 +1,801 @@
+ 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);
+
</file context>
| if (this.webhookSecret && rawBody) { | ||
| const expectedSignature = this.generateSignature(rawBody, this.webhookSecret); | ||
| if (signature !== expectedSignature) { | ||
| throw new Error('Invalid webhook signature'); |
There was a problem hiding this comment.
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 && rawBody) {
+ const expectedSignature = this.generateSignature(rawBody, this.webhookSecret);
+ if (signature !== expectedSignature) {
+ throw new Error('Invalid webhook signature');
+ }
+ }
</file context>
| try { | ||
| // Verify webhook signature | ||
| if (this.webhookSecret && rawBody) { | ||
| const expectedSignature = this.generateSignature(rawBody, this.webhookSecret); |
There was a problem hiding this comment.
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 && rawBody) {
+ const expectedSignature = this.generateSignature(rawBody, this.webhookSecret);
+ if (signature !== expectedSignature) {
+ throw new Error('Invalid webhook signature');
</file context>
| error?.error || | ||
| error?.data?.message || | ||
| error?.data?.error || | ||
| typeof error === 'string' |
There was a problem hiding this comment.
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 === 'string'
+ ? error
+ : 'Payment processing failed';
</file context>
Summary
Related Issues
Description
Changes Made
Screenshots / Videos
Type of Change
Implementation Details
Testing Instructions
Deployment Notes
Checklist
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
Migration
Written for commit 49d5dfb. Summary will update automatically on new commits.
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.