Add Mellat e-payment, videos/instructions CMS, and legacy domain lookup.
Store gateway credentials per business, initiate/verify Mellat at checkout, and resolve old CMS ids from domains in the migrate modal. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
513673a7da
commit
d5e502cf77
@@ -10,13 +10,21 @@ import {
|
||||
DEFAULT_BUSINESS_SETTINGS,
|
||||
DEFAULT_BUSINESS_THEME_MODE,
|
||||
DEFAULT_ENABLED_BUSINESS_MODULES,
|
||||
DEFAULT_EPAYMENT_SETTINGS,
|
||||
DEFAULT_HOME_CHARTS,
|
||||
DEFAULT_MELLAT_GATEWAY_SETTINGS,
|
||||
DEFAULT_ORDER_PROCESS_STEPS,
|
||||
DEFAULT_STUB_GATEWAY_SETTINGS,
|
||||
EPaymentSettings,
|
||||
HOME_CHART_IDS,
|
||||
MellatGatewaySettings,
|
||||
PAYMENT_GATEWAY_IDS,
|
||||
type DashboardLocale,
|
||||
type DashboardThemeMode,
|
||||
type HomeChartId,
|
||||
type PaymentGatewayId,
|
||||
OrderProcessStep,
|
||||
StubGatewaySettings,
|
||||
} from './business-settings.types';
|
||||
import {
|
||||
defaultOrderStepColor,
|
||||
@@ -80,6 +88,190 @@ function readBoolean(value: unknown, fallback: boolean) {
|
||||
return typeof value === 'boolean' ? value : fallback;
|
||||
}
|
||||
|
||||
function readString(value: unknown, fallback = '') {
|
||||
return typeof value === 'string' ? value.trim() : fallback;
|
||||
}
|
||||
|
||||
function isPaymentGatewayId(value: unknown): value is PaymentGatewayId {
|
||||
return (
|
||||
typeof value === 'string' &&
|
||||
(PAYMENT_GATEWAY_IDS as readonly string[]).includes(value)
|
||||
);
|
||||
}
|
||||
|
||||
function readMellatGateway(value: unknown): MellatGatewaySettings {
|
||||
const source = isRecord(value) ? value : {};
|
||||
return {
|
||||
enabled: readBoolean(
|
||||
source.enabled,
|
||||
DEFAULT_MELLAT_GATEWAY_SETTINGS.enabled,
|
||||
),
|
||||
terminalId: readString(
|
||||
source.terminalId,
|
||||
DEFAULT_MELLAT_GATEWAY_SETTINGS.terminalId,
|
||||
),
|
||||
username: readString(
|
||||
source.username,
|
||||
DEFAULT_MELLAT_GATEWAY_SETTINGS.username,
|
||||
),
|
||||
password:
|
||||
typeof source.password === 'string'
|
||||
? source.password
|
||||
: DEFAULT_MELLAT_GATEWAY_SETTINGS.password,
|
||||
};
|
||||
}
|
||||
|
||||
function readStubGateway(value: unknown): StubGatewaySettings {
|
||||
const source = isRecord(value) ? value : {};
|
||||
return {
|
||||
enabled: readBoolean(
|
||||
source.enabled,
|
||||
DEFAULT_STUB_GATEWAY_SETTINGS.enabled,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeEPaymentSettings(value: unknown): EPaymentSettings {
|
||||
const source = isRecord(value) ? value : {};
|
||||
const gateways = isRecord(source.gateways) ? source.gateways : {};
|
||||
const defaultGateway = isPaymentGatewayId(source.defaultGateway)
|
||||
? source.defaultGateway
|
||||
: null;
|
||||
|
||||
return {
|
||||
enabled: readBoolean(source.enabled, DEFAULT_EPAYMENT_SETTINGS.enabled),
|
||||
defaultGateway,
|
||||
gateways: {
|
||||
mellat: readMellatGateway(gateways.mellat),
|
||||
sep: readStubGateway(gateways.sep),
|
||||
snappay: readStubGateway(gateways.snappay),
|
||||
digipay: readStubGateway(gateways.digipay),
|
||||
zarinpal: readStubGateway(gateways.zarinpal),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Merge e-payment patch; empty mellat password keeps the current secret. */
|
||||
export function mergeEPaymentSettings(
|
||||
current: EPaymentSettings,
|
||||
patch: EPaymentSettings | undefined,
|
||||
): EPaymentSettings {
|
||||
if (!patch) return current;
|
||||
|
||||
const nextPassword = patch.gateways.mellat.password;
|
||||
const password =
|
||||
typeof nextPassword === 'string' && nextPassword.length > 0
|
||||
? nextPassword
|
||||
: current.gateways.mellat.password;
|
||||
|
||||
return {
|
||||
enabled: patch.enabled,
|
||||
defaultGateway: patch.defaultGateway,
|
||||
gateways: {
|
||||
mellat: {
|
||||
enabled: patch.gateways.mellat.enabled,
|
||||
terminalId: patch.gateways.mellat.terminalId.trim(),
|
||||
username: patch.gateways.mellat.username.trim(),
|
||||
password,
|
||||
},
|
||||
sep: { enabled: patch.gateways.sep.enabled },
|
||||
snappay: { enabled: patch.gateways.snappay.enabled },
|
||||
digipay: { enabled: patch.gateways.digipay.enabled },
|
||||
zarinpal: { enabled: patch.gateways.zarinpal.enabled },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Redact gateway secrets for dashboard API responses. */
|
||||
export function sanitizeBusinessSettingsForClient(
|
||||
settings: BusinessSettings,
|
||||
): BusinessSettings {
|
||||
const mellat = settings.store.ePayment.gateways.mellat;
|
||||
return {
|
||||
...settings,
|
||||
store: {
|
||||
...settings.store,
|
||||
ePayment: {
|
||||
...settings.store.ePayment,
|
||||
gateways: {
|
||||
...settings.store.ePayment.gateways,
|
||||
mellat: {
|
||||
...mellat,
|
||||
password: '',
|
||||
// Clients treat non-empty passwordSet via a parallel field — see
|
||||
// get/update response shaping in BusinessSettingsService.
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function mellatPasswordSet(settings: BusinessSettings): boolean {
|
||||
return settings.store.ePayment.gateways.mellat.password.length > 0;
|
||||
}
|
||||
|
||||
/** Whether a gateway is enabled and has enough credentials to initiate. */
|
||||
export function isGatewayReady(
|
||||
settings: EPaymentSettings,
|
||||
gatewayId: PaymentGatewayId,
|
||||
): boolean {
|
||||
if (!settings.enabled) return false;
|
||||
|
||||
switch (gatewayId) {
|
||||
case 'mellat': {
|
||||
const g = settings.gateways.mellat;
|
||||
return (
|
||||
g.enabled &&
|
||||
g.terminalId.length > 0 &&
|
||||
g.username.length > 0 &&
|
||||
g.password.length > 0
|
||||
);
|
||||
}
|
||||
case 'sep':
|
||||
case 'snappay':
|
||||
case 'digipay':
|
||||
case 'zarinpal':
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function listPublicPaymentGateways(settings: EPaymentSettings) {
|
||||
const labels: Record<
|
||||
PaymentGatewayId,
|
||||
{ label: string; labelFa: string }
|
||||
> = {
|
||||
mellat: { label: 'Bank Mellat', labelFa: 'بانک ملت' },
|
||||
sep: { label: 'SEP (Saman)', labelFa: 'سپ (سامان)' },
|
||||
snappay: { label: 'Snapp Pay', labelFa: 'اسنپپی' },
|
||||
digipay: { label: 'DigiPay', labelFa: 'دیجیپی' },
|
||||
zarinpal: { label: 'Zarinpal', labelFa: 'زرینپال' },
|
||||
};
|
||||
|
||||
const gateways = PAYMENT_GATEWAY_IDS.filter((id) =>
|
||||
isGatewayReady(settings, id),
|
||||
).map((id) => ({
|
||||
id,
|
||||
...labels[id],
|
||||
}));
|
||||
|
||||
let defaultGateway = settings.defaultGateway;
|
||||
if (defaultGateway && !gateways.some((g) => g.id === defaultGateway)) {
|
||||
defaultGateway = null;
|
||||
}
|
||||
if (!defaultGateway && gateways.length) {
|
||||
defaultGateway = gateways[0].id;
|
||||
}
|
||||
|
||||
return {
|
||||
enabled: settings.enabled && gateways.length > 0,
|
||||
defaultGateway,
|
||||
gateways,
|
||||
};
|
||||
}
|
||||
|
||||
function readOrderProcessSteps(value: unknown): OrderProcessStep[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return DEFAULT_ORDER_PROCESS_STEPS;
|
||||
@@ -152,6 +344,7 @@ export function normalizeBusinessSettings(raw: unknown): BusinessSettings {
|
||||
DEFAULT_BUSINESS_SETTINGS.store.onlineSellEnabled,
|
||||
),
|
||||
orderProcessSteps: readOrderProcessSteps(store.orderProcessSteps),
|
||||
ePayment: normalizeEPaymentSettings(store.ePayment),
|
||||
},
|
||||
modules: {
|
||||
// Missing `modules` → all enabled (legacy). Explicit `{ enabled: [] }` stays empty.
|
||||
@@ -194,6 +387,10 @@ export function mergeBusinessSettings(
|
||||
patch.store?.onlineSellEnabled ?? current.store.onlineSellEnabled,
|
||||
orderProcessSteps:
|
||||
patch.store?.orderProcessSteps ?? current.store.orderProcessSteps,
|
||||
ePayment: mergeEPaymentSettings(
|
||||
current.store.ePayment,
|
||||
patch.store?.ePayment,
|
||||
),
|
||||
},
|
||||
modules: {
|
||||
enabled: patch.modules?.enabled ?? current.modules.enabled,
|
||||
|
||||
Reference in New Issue
Block a user