cashier-frontend/src/config/cashierConfig.ts
2026-01-14 15:29:33 +01:00

302 lines
9.6 KiB
TypeScript

import type { ICustomer, IRedirect } from '@/features/cashier/types';
// Store parameters received via postMessage from parent window
const postMessageParams: Record<string, string> = {};
let postMessageCustomer: Partial<ICustomer> | null = null;
// Initialize postMessage listener to receive params from parent window
if (typeof window !== 'undefined' && window.parent !== window) {
window.addEventListener('message', (event: MessageEvent) => {
// Accept messages from any origin (parent window)
// In production, you might want to validate event.origin
if (event.data && typeof event.data === 'object') {
// If the message contains config params, store them
if (event.data.type === 'CASHIER_CONFIG' || !event.data.type) {
// Convert object to URLSearchParams-like format
Object.entries(event.data).forEach(([key, value]) => {
if (key === 'customer' && typeof value === 'object' && value !== null) {
// Handle customer object separately
postMessageCustomer = value as Partial<ICustomer>;
} else if (key !== 'type' && typeof value === 'string') {
postMessageParams[key] = value;
} else if (typeof value === 'number' || typeof value === 'boolean') {
postMessageParams[key] = String(value);
}
});
console.log('Received params via postMessage:', postMessageParams);
console.log('Received customer via postMessage:', postMessageCustomer);
}
}
});
}
export interface ICashierConfig {
// Core payment configuration
paymentType?: 'deposit' | 'withdrawal';
currency?: string;
amount?: number;
account?: string; // For withdrawal
// Customer information
customer?: Partial<ICustomer>;
// Redirect URLs
redirect?: Partial<IRedirect>;
// Merchant and session info
merchantId?: string;
userId?: string; // Maps to customer.id
sessionId?: string;
environment?: 'production' | 'staging' | 'development';
// UI/UX configuration
locale?: string;
fetchConfig?: boolean;
predefinedValues?: Record<string, unknown>;
prefillCreditcardHolder?: boolean;
showAccounts?: string;
autoOpenFirstPaymentMethod?: boolean;
showTransactionOverview?: boolean;
showAmountLimits?: boolean;
receiptExcludeKeys?: string[];
// Attributes
attributes?: {
hostUri?: string;
bootstrapVersion?: string;
[key: string]: unknown;
};
}
export function getCashierConfig(): ICashierConfig {
const urlParams = new URLSearchParams(window.location.search);
// Merge URL params with postMessage params (postMessage params take precedence)
const params = new URLSearchParams();
// First add URL params
urlParams.forEach((value, key) => {
params.set(key, value);
});
// Then override/add postMessage params
Object.entries(postMessageParams).forEach(([key, value]) => {
params.set(key, value);
});
// Debug: Log all parameters (URL + postMessage)
console.log('All URL params:', Object.fromEntries(urlParams.entries()));
console.log('All postMessage params:', postMessageParams);
console.log('Merged params:', Object.fromEntries(params.entries()));
const config: ICashierConfig = {
};
// Payment type from URL (support both 'method' and 'payment_type' for backward compatibility)
const method = params.get('method');
const paymentType = params.get('payment_type') || method;
if (paymentType === 'deposit' || paymentType === 'withdrawal') {
config.paymentType = paymentType;
}
// Currency from URL
const currency = params.get('currency');
if (currency) {
config.currency = currency;
}
// Amount from URL
const amount = params.get('amount');
if (amount && amount !== 'undefined') {
const parsedAmount = parseFloat(amount);
if (!isNaN(parsedAmount)) {
config.amount = parsedAmount;
}
}
// Account from URL (for withdrawal)
const account = params.get('account');
if (account) {
config.account = account;
}
// Merchant and session info
const merchantId = params.get('merchantId');
if (merchantId) {
config.merchantId = merchantId;
}
const userId = params.get('userId');
if (userId) {
// Use userId from URL remove for production
config.userId = '12345';
} else if (import.meta.env.DEV) {
// Development fallback: use a default numeric userId for testing
// The API expects customer ID to be a numeric value
config.userId = '123';
}
const sessionId = params.get('sessionId');
if (sessionId) {
config.sessionId = sessionId;
}
const environment = params.get('environment');
if (environment === 'production' || environment === 'staging' || environment === 'development') {
config.environment = environment;
}
// Customer data from URL (support both new 'userId' and old 'customer_id' format)
// Start with postMessage customer if available, otherwise create new object
const customer: Partial<ICustomer> = postMessageCustomer ? { ...postMessageCustomer } : {};
const customerId = params.get('customer_id') || userId;
if (customerId) {
customer.id = customerId;
} else if (userId && !customer.id) {
// Ensure userId is set on customer if available
customer.id = userId;
}
// Parse additional customer fields from URL params (only if not already set from postMessage)
if (!customer.first_name) {
const firstName = params.get('customer_first_name') || params.get('first_name');
if (firstName) customer.first_name = firstName;
}
if (!customer.last_name) {
const lastName = params.get('customer_last_name') || params.get('last_name');
if (lastName) customer.last_name = lastName;
}
if (!customer.email) {
const email = params.get('customer_email') || params.get('email');
if (email) customer.email = email;
}
if (Object.keys(customer).length > 0) {
config.customer = customer;
}
// Redirect URLs from URL
const redirect: Partial<IRedirect> = {};
const successUrl = params.get('redirect_success');
const cancelUrl = params.get('redirect_cancel');
const errorUrl = params.get('redirect_error');
if (successUrl) redirect.success = successUrl;
if (cancelUrl) redirect.cancel = cancelUrl;
if (errorUrl) redirect.error = errorUrl;
if (Object.keys(redirect).length > 0) {
config.redirect = redirect;
}
// UI/UX configuration
const locale = params.get('locale');
if (locale) {
config.locale = locale;
}
const fetchConfig = params.get('fetchConfig');
if (fetchConfig !== null) {
config.fetchConfig = fetchConfig === 'true';
}
// Parse predefinedValues (JSON object)
const predefinedValuesStr = params.get('predefinedValues');
if (predefinedValuesStr && predefinedValuesStr !== '[object Object]') {
try {
const decoded = decodeURIComponent(predefinedValuesStr);
config.predefinedValues = JSON.parse(decoded);
} catch {
// If parsing fails, ignore it
}
}
const prefillCreditcardHolder = params.get('prefillCreditcardHolder');
if (prefillCreditcardHolder !== null) {
config.prefillCreditcardHolder = prefillCreditcardHolder === 'true';
}
const showAccounts = params.get('showAccounts');
if (showAccounts) {
config.showAccounts = showAccounts;
}
const autoOpenFirstPaymentMethod = params.get('autoOpenFirstPaymentMethod');
if (autoOpenFirstPaymentMethod !== null) {
config.autoOpenFirstPaymentMethod = autoOpenFirstPaymentMethod === 'true';
}
const showTransactionOverview = params.get('showTransactionOverview');
if (showTransactionOverview !== null) {
config.showTransactionOverview = showTransactionOverview === 'true';
}
const showAmountLimits = params.get('showAmountLimits');
if (showAmountLimits !== null) {
config.showAmountLimits = showAmountLimits === 'true';
}
// Receipt exclude keys (comma-separated string)
const receiptExcludeKeys = params.get('receiptExcludeKeys');
if (receiptExcludeKeys) {
config.receiptExcludeKeys = receiptExcludeKeys.split(',').map(key => key.trim());
}
// Attributes (handle attributes.* parameters)
const attributes: Record<string, unknown> = {};
params.forEach((value, key) => {
if (key.startsWith('attributes.')) {
const attrKey = key.replace('attributes.', '');
attributes[attrKey] = value;
}
});
if (Object.keys(attributes).length > 0) {
config.attributes = attributes;
}
return config;
}
// Default redirect URLs (fallback if not provided)
// Convert relative paths to absolute URLs for payment provider redirects
// Uses hash-based routing for iframe compatibility
export function getDefaultRedirectUrls(): IRedirect {
const currentOrigin = window.location.origin;
const currentPath = window.location.pathname.replace(/\/[^/]*$/, ''); // Remove last path segment if any
return {
success: `${currentOrigin}${currentPath}#/result?status=success`,
cancel: `${currentOrigin}${currentPath}#/result?status=cancel`,
error: `${currentOrigin}${currentPath}#/result?status=error`,
};
}
// Convert relative URLs to absolute URLs with hash-based routing
export function normalizeRedirectUrl(url: string): string {
// If it's already an absolute URL, return as-is
if (url.startsWith('http://') || url.startsWith('https://')) {
return url;
}
// If it's a relative path, convert to absolute with hash routing
const currentOrigin = window.location.origin;
const currentPath = window.location.pathname.replace(/\/[^/]*$/, '');
// Handle hash-based routing - if URL starts with /, convert to #/
// Preserve query parameters
let hashPath = url;
if (hashPath.startsWith('/')) {
hashPath = `#${hashPath}`;
} else if (!hashPath.startsWith('#')) {
hashPath = `#/${hashPath}`;
}
return `${currentOrigin}${currentPath}${hashPath}`;
}