refactor(transak): split API keys by environment (staging/production)
Add getTransakApiKey() and getTransakWebhookSecret() helpers that resolve TRANSAK_API_KEY_STAGING or TRANSAK_API_KEY_PRODUCTION based on TRANSAK_ENV, with fallback to legacy TRANSAK_API_KEY. All consumers (rcart, rflows, transak-onramp) now use the shared helpers instead of reading env vars directly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a8b99d3462
commit
357e0bb4c0
|
|
@ -45,7 +45,11 @@ services:
|
|||
- TWENTY_API_URL=http://twenty-ch-server:3000
|
||||
- TWENTY_API_TOKEN=${TWENTY_API_TOKEN:-}
|
||||
- TRANSAK_API_KEY=${TRANSAK_API_KEY:-}
|
||||
- TRANSAK_API_KEY_STAGING=${TRANSAK_API_KEY_STAGING:-}
|
||||
- TRANSAK_API_KEY_PRODUCTION=${TRANSAK_API_KEY_PRODUCTION:-}
|
||||
- TRANSAK_SECRET=${TRANSAK_SECRET:-}
|
||||
- TRANSAK_WEBHOOK_SECRET_STAGING=${TRANSAK_WEBHOOK_SECRET_STAGING:-}
|
||||
- TRANSAK_WEBHOOK_SECRET_PRODUCTION=${TRANSAK_WEBHOOK_SECRET_PRODUCTION:-}
|
||||
- TRANSAK_ENV=${TRANSAK_ENV:-STAGING}
|
||||
- OLLAMA_URL=http://ollama:11434
|
||||
- INFISICAL_AI_CLIENT_ID=${INFISICAL_AI_CLIENT_ID}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import {
|
|||
type CartItem, type CartStatus,
|
||||
} from './schemas';
|
||||
import { extractProductFromUrl } from './extract';
|
||||
import { createTransakWidgetUrl, extractRootDomain } from '../../shared/transak';
|
||||
import { createTransakWidgetUrl, extractRootDomain, getTransakApiKey } from '../../shared/transak';
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
let _syncServer: SyncServer | null = null;
|
||||
|
|
@ -1363,7 +1363,7 @@ routes.post("/api/payments/:id/transak-session", async (c) => {
|
|||
const { email } = await c.req.json();
|
||||
if (!email) return c.json({ error: "Required: email" }, 400);
|
||||
|
||||
const transakApiKey = process.env.TRANSAK_API_KEY;
|
||||
const transakApiKey = getTransakApiKey();
|
||||
if (!transakApiKey) return c.json({ error: "Transak not configured" }, 503);
|
||||
|
||||
const networkMap: Record<number, string> = { 8453: 'base', 84532: 'base', 1: 'ethereum' };
|
||||
|
|
|
|||
|
|
@ -4,18 +4,18 @@
|
|||
*/
|
||||
|
||||
import type { OnrampProvider, OnrampSessionRequest, OnrampSessionResult } from './onramp-provider';
|
||||
import { createTransakWidgetUrl, extractRootDomain } from '../../../shared/transak';
|
||||
import { createTransakWidgetUrl, extractRootDomain, getTransakApiKey } from '../../../shared/transak';
|
||||
|
||||
export class TransakOnrampAdapter implements OnrampProvider {
|
||||
id = 'transak' as const;
|
||||
name = 'Transak';
|
||||
|
||||
isAvailable(): boolean {
|
||||
return !!process.env.TRANSAK_API_KEY;
|
||||
return !!getTransakApiKey();
|
||||
}
|
||||
|
||||
async createSession(req: OnrampSessionRequest): Promise<OnrampSessionResult> {
|
||||
const apiKey = process.env.TRANSAK_API_KEY;
|
||||
const apiKey = getTransakApiKey();
|
||||
if (!apiKey) throw new Error('Transak not configured');
|
||||
|
||||
const widgetParams: Record<string, string> = {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import type { RSpaceModule } from "../../shared/module";
|
|||
import { getModuleInfoList } from "../../shared/module";
|
||||
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
|
||||
import { renderLanding } from "./landing";
|
||||
import { getTransakEnv, getTransakWebhookSecret } from "../../shared/transak";
|
||||
import type { SyncServer } from '../../server/local-first/sync-server';
|
||||
import { flowsSchema, flowsDocId, type FlowsDoc, type SpaceFlow, type CanvasFlow } from './schemas';
|
||||
import { demoNodes } from './lib/presets';
|
||||
|
|
@ -297,7 +298,7 @@ routes.get("/api/onramp/config", (c) => {
|
|||
routes.get("/api/transak/config", (c) => {
|
||||
return c.json({
|
||||
provider: "transak",
|
||||
environment: process.env.TRANSAK_ENV || "STAGING",
|
||||
environment: getTransakEnv(),
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -305,8 +306,8 @@ routes.post("/api/transak/webhook", async (c) => {
|
|||
let body: any;
|
||||
try { body = await c.req.json(); } catch { return c.json({ error: "Invalid JSON" }, 400); }
|
||||
|
||||
// HMAC verification — if TRANSAK_WEBHOOK_SECRET is set, validate signature
|
||||
const webhookSecret = process.env.TRANSAK_WEBHOOK_SECRET;
|
||||
// HMAC verification — if webhook secret is set, validate signature
|
||||
const webhookSecret = getTransakWebhookSecret();
|
||||
if (webhookSecret) {
|
||||
const signature = c.req.header("x-transak-signature") || "";
|
||||
const { createHmac } = await import("crypto");
|
||||
|
|
|
|||
|
|
@ -69,8 +69,12 @@ RUNPOD_API_KEY|AI/MI|RunPod API key
|
|||
X402_PAY_TO|payments|Payment recipient address
|
||||
MAILCOW_API_KEY|EncryptID|Mailcow admin API key
|
||||
ENCRYPTID_DEMO_SPACES|EncryptID|Comma-separated demo space slugs
|
||||
TRANSAK_API_KEY|rFunds|Transak widget API key (public, scoped to app)
|
||||
TRANSAK_WEBHOOK_SECRET|rFunds|Transak webhook HMAC secret for signature verification
|
||||
TRANSAK_API_KEY|rFunds|Transak widget API key (legacy fallback)
|
||||
TRANSAK_API_KEY_STAGING|rFunds|Transak staging API key
|
||||
TRANSAK_API_KEY_PRODUCTION|rFunds|Transak production API key
|
||||
TRANSAK_WEBHOOK_SECRET|rFunds|Transak webhook HMAC secret (legacy fallback)
|
||||
TRANSAK_WEBHOOK_SECRET_STAGING|rFunds|Transak staging webhook HMAC secret
|
||||
TRANSAK_WEBHOOK_SECRET_PRODUCTION|rFunds|Transak production webhook HMAC secret
|
||||
TRANSAK_ENV|rFunds|Transak environment: STAGING or PRODUCTION (default: STAGING)
|
||||
"
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,40 @@
|
|||
* Builds Transak widget URLs using direct query parameters.
|
||||
* The gateway session API has auth issues, so we use the direct
|
||||
* URL approach which Transak still supports.
|
||||
*
|
||||
* Environment: TRANSAK_ENV controls staging vs production.
|
||||
* API keys are split: TRANSAK_API_KEY_STAGING / TRANSAK_API_KEY_PRODUCTION
|
||||
* (falls back to legacy TRANSAK_API_KEY if per-env keys aren't set).
|
||||
*/
|
||||
|
||||
const TRANSAK_WIDGET_BASE = 'https://global.transak.com';
|
||||
const TRANSAK_WIDGET_BASE_STG = 'https://global-stg.transak.com';
|
||||
|
||||
export type TransakEnv = 'STAGING' | 'PRODUCTION';
|
||||
|
||||
/** Get the current Transak environment */
|
||||
export function getTransakEnv(): TransakEnv {
|
||||
return (process.env.TRANSAK_ENV as TransakEnv) || 'STAGING';
|
||||
}
|
||||
|
||||
/** Get the API key for the current Transak environment */
|
||||
export function getTransakApiKey(): string {
|
||||
const env = getTransakEnv();
|
||||
return (env === 'PRODUCTION'
|
||||
? process.env.TRANSAK_API_KEY_PRODUCTION
|
||||
: process.env.TRANSAK_API_KEY_STAGING
|
||||
) || process.env.TRANSAK_API_KEY || '';
|
||||
}
|
||||
|
||||
/** Get the webhook secret for the current Transak environment */
|
||||
export function getTransakWebhookSecret(): string {
|
||||
const env = getTransakEnv();
|
||||
return (env === 'PRODUCTION'
|
||||
? process.env.TRANSAK_WEBHOOK_SECRET_PRODUCTION
|
||||
: process.env.TRANSAK_WEBHOOK_SECRET_STAGING
|
||||
) || process.env.TRANSAK_WEBHOOK_SECRET || '';
|
||||
}
|
||||
|
||||
/** Extract root domain from a hostname (e.g. "demo.rspace.online" → "rspace.online") */
|
||||
export function extractRootDomain(host: string): string {
|
||||
const parts = host.replace(/:\d+$/, '').split('.');
|
||||
|
|
@ -16,7 +45,7 @@ export function extractRootDomain(host: string): string {
|
|||
}
|
||||
|
||||
export function createTransakWidgetUrl(params: Record<string, string>): string {
|
||||
const env = process.env.TRANSAK_ENV || 'STAGING';
|
||||
const env = getTransakEnv();
|
||||
const base = env === 'PRODUCTION' ? TRANSAK_WIDGET_BASE : TRANSAK_WIDGET_BASE_STG;
|
||||
|
||||
const url = new URL(base);
|
||||
|
|
|
|||
Loading…
Reference in New Issue