feat(landing,billing): public pricing, global discount, services bar & estimator
Backend
- Add platform-wide global discount (platform_settings: global_discount_percent),
applied centrally in PricingCatalogService.computeTotalsFromDb so it reaches
every real charge (previews, deploys, renewals, upgrades, invoices). Admin
GET/PATCH /billing/settings/global-discount.
- Add unauthenticated PublicPricingController (catalog + calculate) for the
public landing page, returning gross/net and the discount percentage.
- Bill application replicas by the user-selected footprint: app CPU/RAM/storage
now all scale by replica count; the single-replica database stays unscaled.
Frontend
- Landing: Services bar (PaaS active, DBaaS, KaaS/LaaS "coming soon" with
expandable runtime/database menus), transparent Pricing section (per-resource
rate cards with cycle toggle + discount strikethrough), and a cost Estimator
("estimate your package").
- Optional services and the database are priced like runtimes: the estimator
lets users pick their CPU/RAM/storage (and DB type) so the cost scales by need.
- Admin billing: global-discount editor.
- i18n: fa/en strings for services, pricing, estimator and global discount.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -66,6 +66,32 @@ const en: Dictionary = {
|
||||
ctaSecondary: 'Sign in',
|
||||
scrollHint: 'Dive into the clouds',
|
||||
},
|
||||
services: {
|
||||
title: 'Platform services',
|
||||
subtitle: 'Everything you need to build and run your product, in one place.',
|
||||
comingSoon: 'Coming soon',
|
||||
explore: 'Click to explore',
|
||||
paas: {
|
||||
label: 'PaaS',
|
||||
name: 'Platform as a Service',
|
||||
desc: 'Ship your app straight from code to Kubernetes — automatic build and release.',
|
||||
},
|
||||
dbaas: {
|
||||
label: 'DBaaS',
|
||||
name: 'Database as a Service',
|
||||
desc: 'Managed, reliable databases ready in seconds.',
|
||||
},
|
||||
kaas: {
|
||||
label: 'KaaS',
|
||||
name: 'Kubernetes as a Service',
|
||||
desc: 'A dedicated, managed Kubernetes cluster for your team.',
|
||||
},
|
||||
laas: {
|
||||
label: 'LaaS',
|
||||
name: 'Logging as a Service',
|
||||
desc: 'Centralized log collection, retention and analysis.',
|
||||
},
|
||||
},
|
||||
value: {
|
||||
lead: 'A ',
|
||||
highlight: 'self-service PaaS',
|
||||
@@ -103,6 +129,53 @@ const en: Dictionary = {
|
||||
{ kpi: 'Iran-native', label: 'Optimized for Iranian users' },
|
||||
],
|
||||
},
|
||||
pricing: {
|
||||
title: 'Transparent pricing',
|
||||
subtitle: 'Pay only for the resources you use. These rates are read straight from the platform’s active plans.',
|
||||
runtimeLabel: 'Application type',
|
||||
cycle: { hourly: 'Hourly', monthly: 'Monthly', yearly: 'Yearly' },
|
||||
perCycle: { hourly: 'per hour', monthly: 'per month', yearly: 'per year' },
|
||||
discountBadge: '{p}% off',
|
||||
free: 'Free',
|
||||
resources: {
|
||||
base_fee: 'Base fee',
|
||||
cpu_per_core: 'CPU (per core)',
|
||||
memory_per_gb: 'Memory (per GB)',
|
||||
storage_per_gb: 'Storage (per GB)',
|
||||
database_addon: 'Managed database',
|
||||
custom_domain_addon: 'Custom domain + SSL',
|
||||
},
|
||||
currency: 'Toman',
|
||||
note: 'Your final bill is based on actual usage; these are the per-unit rates for each resource.',
|
||||
loadError: 'Could not load pricing.',
|
||||
},
|
||||
estimator: {
|
||||
title: 'Estimate your package',
|
||||
subtitle: 'Pick the resources you need and see an instant cost estimate.',
|
||||
runtime: 'Application type',
|
||||
cpu: 'CPU',
|
||||
memory: 'Memory (RAM)',
|
||||
storage: 'Storage',
|
||||
replicas: 'Replicas',
|
||||
database: 'Database',
|
||||
databaseType: 'Database type',
|
||||
options: 'Add-on services',
|
||||
redis: 'Redis',
|
||||
rabbitmq: 'RabbitMQ',
|
||||
elasticsearch: 'Elasticsearch (logging)',
|
||||
customDomain: 'Custom domain + SSL',
|
||||
cores: '{n} cores',
|
||||
gb: '{n} GB',
|
||||
none: 'None',
|
||||
estimatedTitle: 'Estimated cost',
|
||||
perCycle: { hourly: 'per hour', monthly: 'per month', yearly: 'per year' },
|
||||
cycle: { hourly: 'Hourly', monthly: 'Monthly', yearly: 'Yearly' },
|
||||
youSave: '{p}% discount applied',
|
||||
calculating: 'Calculating…',
|
||||
cta: 'Build this package',
|
||||
currency: 'Toman',
|
||||
error: 'Could not calculate cost.',
|
||||
},
|
||||
finalCta: {
|
||||
titleLead: 'The sky is clear; it’s time to ',
|
||||
titleHighlight: 'go live',
|
||||
@@ -1103,6 +1176,19 @@ const en: Dictionary = {
|
||||
saveFailedShort: 'Failed to save',
|
||||
hours: 'hours',
|
||||
days: 'days',
|
||||
globalDiscount: {
|
||||
title: 'Platform-wide discount',
|
||||
subtitle: 'A single percentage applied to every price and real invoice. 0 means no discount.',
|
||||
label: 'Global discount %',
|
||||
hint: 'A number between 0 and 100',
|
||||
save: 'Save discount',
|
||||
saving: 'Saving…',
|
||||
saved: 'Global discount saved',
|
||||
edit: 'Edit',
|
||||
cancel: 'Cancel',
|
||||
none: 'No platform-wide discount is currently active.',
|
||||
active: 'A {p}% discount is active on all services.',
|
||||
},
|
||||
discounts: {
|
||||
title: 'Discount codes',
|
||||
subtitle: 'Percentage discounts on different services — public or for specific users.',
|
||||
|
||||
@@ -65,6 +65,32 @@ const fa = {
|
||||
ctaSecondary: 'ورود',
|
||||
scrollHint: 'به دلِ ابرها بزن',
|
||||
},
|
||||
services: {
|
||||
title: 'سرویسهای پلتفرم',
|
||||
subtitle: 'هرچه برای ساخت و اجرای محصولت لازم داری، یکجا.',
|
||||
comingSoon: 'بهزودی',
|
||||
explore: 'برای مشاهده کلیک کن',
|
||||
paas: {
|
||||
label: 'PaaS',
|
||||
name: 'پلتفرم بهعنوان سرویس',
|
||||
desc: 'اپلیکیشنت را مستقیم از کد روی کوبرنتیز منتشر کن — بیلد و انتشار خودکار.',
|
||||
},
|
||||
dbaas: {
|
||||
label: 'DBaaS',
|
||||
name: 'دیتابیس بهعنوان سرویس',
|
||||
desc: 'دیتابیسهای مدیریتشده و پایدار، آماده در چند ثانیه.',
|
||||
},
|
||||
kaas: {
|
||||
label: 'KaaS',
|
||||
name: 'کوبرنتیز بهعنوان سرویس',
|
||||
desc: 'کلاستر کوبرنتیز اختصاصی و مدیریتشده برای تیم تو.',
|
||||
},
|
||||
laas: {
|
||||
label: 'LaaS',
|
||||
name: 'لاگ بهعنوان سرویس',
|
||||
desc: 'جمعآوری، نگهداری و تحلیل متمرکز لاگها.',
|
||||
},
|
||||
},
|
||||
value: {
|
||||
lead: 'یک پلتفرمِ ',
|
||||
highlight: 'PaaS خودسرویس',
|
||||
@@ -102,6 +128,53 @@ const fa = {
|
||||
{ kpi: 'بومیِ ایران', label: 'بهینه برای کاربر ایرانی' },
|
||||
],
|
||||
},
|
||||
pricing: {
|
||||
title: 'قیمتگذاری شفاف',
|
||||
subtitle: 'فقط بابت منابعی که مصرف میکنی پرداخت کن. این نرخها مستقیماً از پلنهای فعال پلتفرم خوانده میشوند.',
|
||||
runtimeLabel: 'نوع اپلیکیشن',
|
||||
cycle: { hourly: 'ساعتی', monthly: 'ماهانه', yearly: 'سالانه' },
|
||||
perCycle: { hourly: 'هر ساعت', monthly: 'هر ماه', yearly: 'هر سال' },
|
||||
discountBadge: '{p}٪ تخفیف',
|
||||
free: 'رایگان',
|
||||
resources: {
|
||||
base_fee: 'هزینهٔ پایه',
|
||||
cpu_per_core: 'پردازنده (هر هسته)',
|
||||
memory_per_gb: 'حافظه (هر گیگابایت)',
|
||||
storage_per_gb: 'فضای ذخیره (هر گیگابایت)',
|
||||
database_addon: 'دیتابیس مدیریتشده',
|
||||
custom_domain_addon: 'دامنهٔ اختصاصی + SSL',
|
||||
},
|
||||
currency: 'تومان',
|
||||
note: 'هزینهٔ نهایی بر اساس مصرف واقعی شما محاسبه میشود؛ این اعداد نرخ واحد هر منبع هستند.',
|
||||
loadError: 'دریافت قیمتها ممکن نشد.',
|
||||
},
|
||||
estimator: {
|
||||
title: 'پکیج خودت را تخمین بزن',
|
||||
subtitle: 'منابع موردنیازت را انتخاب کن تا همین حالا برآورد هزینه را ببینی.',
|
||||
runtime: 'نوع اپلیکیشن',
|
||||
cpu: 'پردازنده (CPU)',
|
||||
memory: 'حافظه (RAM)',
|
||||
storage: 'فضای ذخیرهسازی',
|
||||
replicas: 'تعداد نمونه',
|
||||
database: 'دیتابیس',
|
||||
databaseType: 'نوع دیتابیس',
|
||||
options: 'سرویسهای جانبی',
|
||||
redis: 'Redis',
|
||||
rabbitmq: 'RabbitMQ',
|
||||
elasticsearch: 'Elasticsearch (لاگ)',
|
||||
customDomain: 'دامنهٔ اختصاصی + SSL',
|
||||
cores: '{n} هسته',
|
||||
gb: '{n} گیگابایت',
|
||||
none: 'بدون',
|
||||
estimatedTitle: 'برآورد هزینه',
|
||||
perCycle: { hourly: 'در ساعت', monthly: 'در ماه', yearly: 'در سال' },
|
||||
cycle: { hourly: 'ساعتی', monthly: 'ماهانه', yearly: 'سالانه' },
|
||||
youSave: '{p}٪ تخفیف اعمال شد',
|
||||
calculating: 'در حال محاسبه…',
|
||||
cta: 'ساخت این پکیج',
|
||||
currency: 'تومان',
|
||||
error: 'محاسبهٔ هزینه ممکن نشد.',
|
||||
},
|
||||
finalCta: {
|
||||
titleLead: 'آسمان صاف است؛ وقتِ ',
|
||||
titleHighlight: 'زنده',
|
||||
@@ -1102,6 +1175,19 @@ const fa = {
|
||||
saveFailedShort: 'ذخیره ناموفق بود',
|
||||
hours: 'ساعت',
|
||||
days: 'روز',
|
||||
globalDiscount: {
|
||||
title: 'تخفیف کلی پلتفرم',
|
||||
subtitle: 'یک درصد تخفیف که روی همهٔ قیمتها و صورتحسابهای واقعی اعمال میشود. ۰ یعنی بدون تخفیف.',
|
||||
label: 'درصد تخفیف کلی',
|
||||
hint: 'عددی بین ۰ تا ۱۰۰',
|
||||
save: 'ذخیرهٔ تخفیف',
|
||||
saving: 'در حال ذخیره…',
|
||||
saved: 'تخفیف کلی ذخیره شد',
|
||||
edit: 'ویرایش',
|
||||
cancel: 'انصراف',
|
||||
none: 'در حال حاضر تخفیف کلی فعال نیست.',
|
||||
active: 'تخفیف {p}٪ روی همهٔ سرویسها فعال است.',
|
||||
},
|
||||
discounts: {
|
||||
title: 'کدهای تخفیف',
|
||||
subtitle: 'تخفیف درصدی روی سرویسهای مختلف؛ عمومی یا مخصوص کاربران خاص.',
|
||||
|
||||
Reference in New Issue
Block a user