Solidgate Integration
Solidgate is one of four supported payment providers in the Ever Works template. It provides checkout sessions, webhook handling, subscription management, and multi-currency support through a unified provider interface.
Source Locations
lib/payment/lib/providers/solidgate-provider.ts # Provider implementation
lib/payment/ui/solidgate/solidgate-elements.tsx # React SDK component
lib/payment/config/payment-provider-manager.ts # Config & initialization
app/api/solidgate/checkout/route.ts # Checkout API endpoint
app/api/solidgate/webhook/route.ts # Webhook handler
Environment Variables
Configure Solidgate by setting the following environment variables:
# Required
SOLIDGATE_API_KEY=your_api_key
SOLIDGATE_SECRET_KEY=your_secret_key
SOLIDGATE_MERCHANT_ID=your_merchant_id
SOLIDGATE_WEBHOOK_SECRET=your_webhook_secret
# Optional
NEXT_PUBLIC_SOLIDGATE_PUBLISHABLE_KEY=your_publishable_key
SOLIDGATE_API_BASE_URL=https://api.solidgate.com/v1
The ConfigManager in payment-provider-manager.ts validates these on first access. If any required variable is missing, it throws an error with a descriptive message:
Solidgate configuration is incomplete.
Required: SOLIDGATE_API_KEY, SOLIDGATE_MERCHANT_ID,
SOLIDGATE_WEBHOOK_SECRET, SOLIDGATE_SECRET_KEY
Provider Architecture
The SolidgateProvider implements the PaymentProviderInterface, making it interchangeable with Stripe, LemonSqueezy, and Polar:
export class SolidgateProvider implements PaymentProviderInterface {
private apiKey: string;
private secretKey: string;
private webhookSecret: string;
private publishableKey: string;
private apiBaseUrl: string;
private merchantId: string;
constructor(config: PaymentProviderConfig) {
this.apiKey = config.apiKey;
this.secretKey = config.secretKey || '';
this.webhookSecret = config.webhookSecret || '';
this.publishableKey = config.options?.publishableKey || '';
this.apiBaseUrl = config.options?.apiBaseUrl || SOLIDGATE_API_BASE_URL;
this.merchantId = config.options?.merchantId || '';
}
// ... interface methods
}
Initialization
Access the Solidgate provider through the singleton manager:
import {
getOrCreateSolidgateProvider,
initializeSolidgateProvider,
} from '@/lib/payment/config/payment-provider-manager';
// Get or lazily create the singleton
const provider = getOrCreateSolidgateProvider();
Checkout Flow
1. Client Creates Checkout
The client initiates a checkout by posting to the API endpoint:
const response = await fetch('/api/solidgate/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
amount: 29.99,
currency: 'USD',
mode: 'one_time', // or 'subscription'
successUrl: 'https://example.com/success',
cancelUrl: 'https://example.com/cancel',
metadata: {
planId: 'pro_plan',
planName: 'Pro Plan',
},
}),
});
2. Server Validates and Creates Payment Intent
The checkout route (app/api/solidgate/checkout/route.ts) performs these steps:
- Authenticates the user via
auth()(NextAuth session) - Validates the request body with Zod:
const checkoutSchema = z.object({
amount: z.number().positive(),
currency: z.string().default('USD'),
mode: z.enum(['one_time', 'subscription']).default('one_time'),
successUrl: z.string().url(),
cancelUrl: z.string().url(),
metadata: z.record(z.string(), z.any()).optional(),
}); - Retrieves or creates a Solidgate customer ID
- Creates a payment intent via the Solidgate API
- Returns the payment ID and client secret for the SDK
3. Response Structure
{
"data": {
"id": "payment_1234567890abcdef",
"url": "pi_generated-uuid_secret"
},
"status": 200,
"message": "Checkout session created successfully"
}
4. Client Renders the Payment Form
Use the returned payment intent ID to initialize the Solidgate React SDK.
React SDK Integration
The template wraps the official @solidgate/react-sdk in a custom component:
// lib/payment/ui/solidgate/solidgate-elements.tsx
import Payment from '@solidgate/react-sdk';
export function SolidgatePaymentForm({
onSuccess,
onError,
merchantId,
paymentIntent,
signature,
}: SolidgateElementsWrapperProps) {
const merchantData = {
merchant: merchantId,
signature: signature,
paymentIntent: paymentIntent,
};
return (
<div className="solidgate-payment-form space-y-4">
<Payment
merchantData={merchantData}
onSuccess={handleSuccess}
onError={handleError}
/>
</div>
);
}
The SolidgateProvider.getUIComponents() method automatically injects the merchant ID, payment intent, and HMAC signature into the wrapper:
getUIComponents(): UIComponents {
const SolidgatePaymentFormWithConfig = (props: PaymentFormProps) => {
const paymentIntent = props.clientSecret;
const merchantId = this.getMerchantId();
const signature = this.generatePaymentIntentSignature(
paymentIntent, merchantId
);
return React.createElement(SolidgateElementsWrapper, {
...props,
solidgatePublicKey: this.publishableKey,
merchantId,
paymentIntent,
signature,
});
};
return {
PaymentForm: SolidgatePaymentFormWithConfig,
logo: '/assets/payment/solidgate/solidgate-logo.svg',
cardBrands: solidgateCardBrands,
supportedPaymentMethods: ['card'],
translations: solidgateTranslations,
};
}
Signature Generation
Solidgate requires HMAC-SHA512 signatures for API authentication and webhook verification:
// Generic signature
private generateSignature(data: string, secret: string): string {
return crypto
.createHmac('sha512', secret)
.update(data)
.digest('hex');
}
// Payment intent signature for the React SDK
private generatePaymentIntentSignature(
paymentIntent: string,
merchantId: string
): string {
const data = `${merchantId}${paymentIntent}`;
return crypto
.createHmac('sha512', this.secretKey)
.update(data)
.digest('hex');
}
Customer Management
The provider follows a three-tier lookup strategy for customer IDs:
- User metadata -- check
user.user_metadata.solidgate_customer_id - Database -- query the
PaymentAccounttable viapaymentAccountClient - Create new -- call the Solidgate
/customersAPI and synchronize back to the database
async getCustomerId(user: User | null): Promise<string | null> {
// 1. Check metadata
const fromMetadata = this.extractCustomerIdFromMetadata(user);
if (fromMetadata) return fromMetadata;
// 2. Check database
const fromDatabase = await this.retrieveCustomerIdFromDatabase(user.id);
if (fromDatabase) return fromDatabase;
// 3. Create new customer
const newCustomer = await this.createNewSolidgateCustomer(user);
await this.synchronizePaymentAccount(user.id, newCustomer.id);
return newCustomer.id;
}
Subscription Management
Create Subscription
const subscription = await provider.createSubscription({
customerId: 'cust_abc123',
priceId: 'plan_monthly',
metadata: { userId: 'user-id' },
});
Cancel Subscription
The provider supports both end-of-period and immediate cancellation:
// Cancel at period end (default)
await provider.cancelSubscription(subscriptionId);
// Cancel immediately
await provider.cancelSubscription(subscriptionId, false);
The cancel method selects the appropriate API endpoint based on the flag:
const endpoint = cancelAtPeriodEnd
? `/subscriptions/${subscriptionId}/cancel`
: `/subscriptions/${subscriptionId}/cancel-immediate`;
Status Mapping
Solidgate subscription statuses are mapped to the template's SubscriptionStatus enum:
| Solidgate Status | Template Status |
|---|---|
active | ACTIVE |
cancelled / canceled | CANCELED |
past_due | PAST_DUE |
trialing / trial | TRIALING |
unpaid | UNPAID |
incomplete | INCOMPLETE |
incomplete_expired | INCOMPLETE_EXPIRED |
Supported Card Brands
The provider declares support for Visa, Mastercard, Amex, and Discover with light/dark theme icons:
const solidgateCardBrands: CardBrandIcon[] = [
{ name: 'visa', lightIcon: '/assets/payment/solidgate/visa-light.svg', ... },
{ name: 'mastercard', lightIcon: '/assets/payment/solidgate/mastercard-light.svg', ... },
{ name: 'amex', lightIcon: '/assets/payment/solidgate/amex-light.svg', ... },
{ name: 'discover', lightIcon: '/assets/payment/solidgate/discover-light.svg', ... },
];
Localization
The provider includes built-in translations for English and French:
const solidgateTranslations = {
en: {
cardNumber: 'Card number',
cardExpiry: 'Expiry date',
cardCvc: 'CVV',
submit: 'Pay securely',
processingPayment: 'Processing your payment...',
paymentSuccessful: 'Payment completed successfully',
paymentFailed: 'Your payment could not be processed',
},
fr: {
cardNumber: 'Numero de carte',
cardExpiry: "Date d'expiration",
// ...
},
};
Refunds
Issue a full or partial refund through the provider:
// Full refund
const refund = await provider.refundPayment(paymentId);
// Partial refund (in decimal, e.g., $10.00)
const partialRefund = await provider.refundPayment(paymentId, 10.00);
Amounts are converted to cents before sending to the Solidgate API.
Error Handling
All provider methods use consistent error handling with a structured logger:
private get logger() {
return {
info: (msg, ctx?) => console.log(`[SolidgateProvider] ${msg}`, ctx),
warn: (msg, ctx?) => console.warn(`[SolidgateProvider] ${msg}`, ctx),
error: (msg, ctx?) => console.error(`[SolidgateProvider] ${msg}`, ctx),
};
}
API errors include the HTTP status code and response body for debugging:
Solidgate API error: 422 Unprocessable Entity - {"error":"Invalid amount"}