From 19792e456c6871d8a27f520d3b8fcf449539a017 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 16:02:56 +0200 Subject: [PATCH 01/17] feat(credits): share build minute pricing --- messages/de.json | 12 ++-- messages/en.json | 12 ++-- messages/es.json | 12 ++-- messages/fr.json | 12 ++-- messages/hi.json | 12 ++-- messages/id.json | 12 ++-- messages/it.json | 12 ++-- messages/ja.json | 12 ++-- messages/ko.json | 12 ++-- messages/pl.json | 12 ++-- messages/pt-br.json | 12 ++-- messages/ru.json | 12 ++-- messages/tr.json | 12 ++-- messages/vi.json | 12 ++-- messages/zh-cn.json | 12 ++-- src/pages/settings/organization/Usage.vue | 61 +++++-------------- src/services/supabase.ts | 48 +++++++++++++++ .../functions/_backend/private/credits.ts | 11 +++- ...34842_adjust_build_time_credit_pricing.sql | 23 +++++++ supabase/seed.sql | 12 ++-- supabase/tests/32_test_usage_credits.sql | 23 ++++++- tests/credits-pricing.test.ts | 56 +++++++++++++++++ 22 files changed, 270 insertions(+), 144 deletions(-) create mode 100644 supabase/migrations/20260408134842_adjust_build_time_credit_pricing.sql create mode 100644 tests/credits-pricing.test.ts diff --git a/messages/de.json b/messages/de.json index 036030671e..d3cd279812 100644 --- a/messages/de.json +++ b/messages/de.json @@ -609,17 +609,17 @@ "credits-pricing-bandwidth-title": "Bandbreite (GiB)", "credits-pricing-build-subtitle": "Pro Build-Minute, die über das in deinem Tarif enthaltene hinausgeht.", "credits-pricing-build-tier-first-100": "Erste 100 Minuten", - "credits-pricing-build-tier-first-100-price": "$0.50 pro Minute", + "credits-pricing-build-tier-first-100-price": "$0.16 pro Minute", "credits-pricing-build-tier-next-400": "Nächste 400 Minuten", - "credits-pricing-build-tier-next-400-price": "$0.45 pro Minute", + "credits-pricing-build-tier-next-400-price": "$0.14 pro Minute", "credits-pricing-build-tier-next-4000": "Nächste 4.000 Minuten", - "credits-pricing-build-tier-next-4000-price": "$0.35 pro Minute", + "credits-pricing-build-tier-next-4000-price": "$0.10 pro Minute", "credits-pricing-build-tier-next-500": "Nächste 500 Minuten", - "credits-pricing-build-tier-next-500-price": "$0.40 pro Minute", + "credits-pricing-build-tier-next-500-price": "$0.12 pro Minute", "credits-pricing-build-tier-next-5000": "Nächste 5.000 Minuten", - "credits-pricing-build-tier-next-5000-price": "$0.30 pro Minute", + "credits-pricing-build-tier-next-5000-price": "$0.09 pro Minute", "credits-pricing-build-tier-over-10000": "Über 10.000 Minuten", - "credits-pricing-build-tier-over-10000-price": "$0.25 pro Minute", + "credits-pricing-build-tier-over-10000-price": "$0.08 pro Minute", "credits-pricing-build-title": "Build-Zeit (Minuten)", "credits-pricing-description": "Credits decken die Nutzung über die Grenzen deines Tarifs hinaus ab. Nutze diese Stufen, um abzuschätzen, wie viele du kaufen solltest.", "credits-pricing-disclaimer": "Credits decken die Nutzung über die im Tarif enthaltenen Grenzen hinaus ab. Credits werden im Voraus bezahlt und sind 12 Monate gültig.", diff --git a/messages/en.json b/messages/en.json index 5deb1cebbe..6b846f07f8 100644 --- a/messages/en.json +++ b/messages/en.json @@ -654,17 +654,17 @@ "credits-pricing-bandwidth-title": "Bandwidth (GiB)", "credits-pricing-build-subtitle": "Per minute spent building beyond what is included with your plan.", "credits-pricing-build-tier-first-100": "First 100 minutes", - "credits-pricing-build-tier-first-100-price": "$0.50 per minute", + "credits-pricing-build-tier-first-100-price": "$0.16 per minute", "credits-pricing-build-tier-next-400": "Next 400 minutes", - "credits-pricing-build-tier-next-400-price": "$0.45 per minute", + "credits-pricing-build-tier-next-400-price": "$0.14 per minute", "credits-pricing-build-tier-next-4000": "Next 4,000 minutes", - "credits-pricing-build-tier-next-4000-price": "$0.35 per minute", + "credits-pricing-build-tier-next-4000-price": "$0.10 per minute", "credits-pricing-build-tier-next-500": "Next 500 minutes", - "credits-pricing-build-tier-next-500-price": "$0.40 per minute", + "credits-pricing-build-tier-next-500-price": "$0.12 per minute", "credits-pricing-build-tier-next-5000": "Next 5,000 minutes", - "credits-pricing-build-tier-next-5000-price": "$0.30 per minute", + "credits-pricing-build-tier-next-5000-price": "$0.09 per minute", "credits-pricing-build-tier-over-10000": "Over 10,000 minutes", - "credits-pricing-build-tier-over-10000-price": "$0.25 per minute", + "credits-pricing-build-tier-over-10000-price": "$0.08 per minute", "credits-pricing-build-title": "Build time (minutes)", "credits-pricing-description": "Credits cover usage beyond your plan limits. Use these tiers to estimate how many to purchase.", "credits-pricing-disclaimer": "Credits cover usage beyond included plan limits. Credits are prepaid and remain valid for 12 months.", diff --git a/messages/es.json b/messages/es.json index 24014e403e..3e1cc1dde5 100644 --- a/messages/es.json +++ b/messages/es.json @@ -609,17 +609,17 @@ "credits-pricing-bandwidth-title": "Ancho de banda (GiB)", "credits-pricing-build-subtitle": "Por minuto dedicado a compilar más allá de lo incluido en tu plan.", "credits-pricing-build-tier-first-100": "Primeros 100 minutos", - "credits-pricing-build-tier-first-100-price": "$0.50 por minuto", + "credits-pricing-build-tier-first-100-price": "$0.16 por minuto", "credits-pricing-build-tier-next-400": "Siguientes 400 minutos", - "credits-pricing-build-tier-next-400-price": "$0.45 por minuto", + "credits-pricing-build-tier-next-400-price": "$0.14 por minuto", "credits-pricing-build-tier-next-4000": "Siguientes 4.000 minutos", - "credits-pricing-build-tier-next-4000-price": "$0.35 por minuto", + "credits-pricing-build-tier-next-4000-price": "$0.10 por minuto", "credits-pricing-build-tier-next-500": "Siguientes 500 minutos", - "credits-pricing-build-tier-next-500-price": "$0.40 por minuto", + "credits-pricing-build-tier-next-500-price": "$0.12 por minuto", "credits-pricing-build-tier-next-5000": "Siguientes 5.000 minutos", - "credits-pricing-build-tier-next-5000-price": "$0.30 por minuto", + "credits-pricing-build-tier-next-5000-price": "$0.09 por minuto", "credits-pricing-build-tier-over-10000": "Más de 10.000 minutos", - "credits-pricing-build-tier-over-10000-price": "$0.25 por minuto", + "credits-pricing-build-tier-over-10000-price": "$0.08 por minuto", "credits-pricing-build-title": "Tiempo de compilación (minutos)", "credits-pricing-description": "Los créditos cubren el uso que supera los límites de tu plan. Usa estos niveles para estimar cuántos comprar.", "credits-pricing-disclaimer": "Los créditos cubren el uso que excede los límites incluidos en el plan. Los créditos se prepagan y son válidos durante 12 meses.", diff --git a/messages/fr.json b/messages/fr.json index 5ad59c8ef4..d47b511191 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -609,17 +609,17 @@ "credits-pricing-bandwidth-title": "Bande passante (GiB)", "credits-pricing-build-subtitle": "Par minute de build dépassant ce qui est inclus dans votre offre.", "credits-pricing-build-tier-first-100": "Premières 100 minutes", - "credits-pricing-build-tier-first-100-price": "$0.50 par minute", + "credits-pricing-build-tier-first-100-price": "$0.16 par minute", "credits-pricing-build-tier-next-400": "Prochaines 400 minutes", - "credits-pricing-build-tier-next-400-price": "$0.45 par minute", + "credits-pricing-build-tier-next-400-price": "$0.14 par minute", "credits-pricing-build-tier-next-4000": "Prochaines 4 000 minutes", - "credits-pricing-build-tier-next-4000-price": "$0.35 par minute", + "credits-pricing-build-tier-next-4000-price": "$0.10 par minute", "credits-pricing-build-tier-next-500": "Prochaines 500 minutes", - "credits-pricing-build-tier-next-500-price": "$0.40 par minute", + "credits-pricing-build-tier-next-500-price": "$0.12 par minute", "credits-pricing-build-tier-next-5000": "Prochaines 5 000 minutes", - "credits-pricing-build-tier-next-5000-price": "$0.30 par minute", + "credits-pricing-build-tier-next-5000-price": "$0.09 par minute", "credits-pricing-build-tier-over-10000": "Au-delà de 10 000 minutes", - "credits-pricing-build-tier-over-10000-price": "$0.25 par minute", + "credits-pricing-build-tier-over-10000-price": "$0.08 par minute", "credits-pricing-build-title": "Temps de build (minutes)", "credits-pricing-description": "Les crédits couvrent l'utilisation au-delà des limites de votre offre. Utilisez ces paliers pour estimer combien en acheter.", "credits-pricing-disclaimer": "Les crédits couvrent l'utilisation au-delà des limites incluses dans l'offre. Les crédits sont prépayés et restent valables pendant 12 mois.", diff --git a/messages/hi.json b/messages/hi.json index 76a1678243..4a707ff325 100644 --- a/messages/hi.json +++ b/messages/hi.json @@ -609,17 +609,17 @@ "credits-pricing-bandwidth-title": "बैंडविड्थ (GiB)", "credits-pricing-build-subtitle": "आपकी योजना में शामिल सीमा से परे किए गए हर बिल्ड मिनट पर।", "credits-pricing-build-tier-first-100": "पहले 100 मिनट", - "credits-pricing-build-tier-first-100-price": "$0.50 प्रति मिनट", + "credits-pricing-build-tier-first-100-price": "$0.16 प्रति मिनट", "credits-pricing-build-tier-next-400": "अगले 400 मिनट", - "credits-pricing-build-tier-next-400-price": "$0.45 प्रति मिनट", + "credits-pricing-build-tier-next-400-price": "$0.14 प्रति मिनट", "credits-pricing-build-tier-next-4000": "अगले 4,000 मिनट", - "credits-pricing-build-tier-next-4000-price": "$0.35 प्रति मिनट", + "credits-pricing-build-tier-next-4000-price": "$0.10 प्रति मिनट", "credits-pricing-build-tier-next-500": "अगले 500 मिनट", - "credits-pricing-build-tier-next-500-price": "$0.40 प्रति मिनट", + "credits-pricing-build-tier-next-500-price": "$0.12 प्रति मिनट", "credits-pricing-build-tier-next-5000": "अगले 5,000 मिनट", - "credits-pricing-build-tier-next-5000-price": "$0.30 प्रति मिनट", + "credits-pricing-build-tier-next-5000-price": "$0.09 प्रति मिनट", "credits-pricing-build-tier-over-10000": "10,000 मिनट से अधिक", - "credits-pricing-build-tier-over-10000-price": "$0.25 प्रति मिनट", + "credits-pricing-build-tier-over-10000-price": "$0.08 प्रति मिनट", "credits-pricing-build-title": "बिल्ड समय (मिनट)", "credits-pricing-description": "क्रेडिट आपके प्लान की सीमा से अधिक उपयोग को कवर करते हैं। कितने क्रेडिट खरीदने हैं इसका अनुमान लगाने के लिए इन स्तरों का उपयोग करें।", "credits-pricing-disclaimer": "क्रेडिट आपके प्लान में शामिल सीमाओं से अधिक उपयोग को कवर करते हैं। क्रेडिट अग्रिम भुगतान किए जाते हैं और 12 महीनों तक मान्य रहते हैं।", diff --git a/messages/id.json b/messages/id.json index 104682555c..b289d2a153 100644 --- a/messages/id.json +++ b/messages/id.json @@ -609,17 +609,17 @@ "credits-pricing-bandwidth-title": "Bandwidth (GiB)", "credits-pricing-build-subtitle": "Per menit waktu build di luar yang sudah termasuk dalam paket Anda.", "credits-pricing-build-tier-first-100": "100 menit pertama", - "credits-pricing-build-tier-first-100-price": "$0.50 per menit", + "credits-pricing-build-tier-first-100-price": "$0.16 per menit", "credits-pricing-build-tier-next-400": "400 menit berikutnya", - "credits-pricing-build-tier-next-400-price": "$0.45 per menit", + "credits-pricing-build-tier-next-400-price": "$0.14 per menit", "credits-pricing-build-tier-next-4000": "4.000 menit berikutnya", - "credits-pricing-build-tier-next-4000-price": "$0.35 per menit", + "credits-pricing-build-tier-next-4000-price": "$0.10 per menit", "credits-pricing-build-tier-next-500": "500 menit berikutnya", - "credits-pricing-build-tier-next-500-price": "$0.40 per menit", + "credits-pricing-build-tier-next-500-price": "$0.12 per menit", "credits-pricing-build-tier-next-5000": "5.000 menit berikutnya", - "credits-pricing-build-tier-next-5000-price": "$0.30 per menit", + "credits-pricing-build-tier-next-5000-price": "$0.09 per menit", "credits-pricing-build-tier-over-10000": "Di atas 10.000 menit", - "credits-pricing-build-tier-over-10000-price": "$0.25 per menit", + "credits-pricing-build-tier-over-10000-price": "$0.08 per menit", "credits-pricing-build-title": "Waktu build (menit)", "credits-pricing-description": "Kredit menutup penggunaan yang melampaui batas paket Anda. Gunakan tingkatan ini untuk memperkirakan jumlah yang perlu dibeli.", "credits-pricing-disclaimer": "Kredit menutup penggunaan di luar batas paket yang disertakan. Kredit dibayar di muka dan berlaku selama 12 bulan.", diff --git a/messages/it.json b/messages/it.json index 12f365c860..cb78a29615 100644 --- a/messages/it.json +++ b/messages/it.json @@ -609,17 +609,17 @@ "credits-pricing-bandwidth-title": "Banda (GiB)", "credits-pricing-build-subtitle": "Per ogni minuto di build oltre quanto incluso nel tuo piano.", "credits-pricing-build-tier-first-100": "Primi 100 minuti", - "credits-pricing-build-tier-first-100-price": "$0.50 per minuto", + "credits-pricing-build-tier-first-100-price": "$0.16 per minuto", "credits-pricing-build-tier-next-400": "Prossimi 400 minuti", - "credits-pricing-build-tier-next-400-price": "$0.45 per minuto", + "credits-pricing-build-tier-next-400-price": "$0.14 per minuto", "credits-pricing-build-tier-next-4000": "Prossimi 4.000 minuti", - "credits-pricing-build-tier-next-4000-price": "$0.35 per minuto", + "credits-pricing-build-tier-next-4000-price": "$0.10 per minuto", "credits-pricing-build-tier-next-500": "Prossimi 500 minuti", - "credits-pricing-build-tier-next-500-price": "$0.40 per minuto", + "credits-pricing-build-tier-next-500-price": "$0.12 per minuto", "credits-pricing-build-tier-next-5000": "Prossimi 5.000 minuti", - "credits-pricing-build-tier-next-5000-price": "$0.30 per minuto", + "credits-pricing-build-tier-next-5000-price": "$0.09 per minuto", "credits-pricing-build-tier-over-10000": "Oltre 10.000 minuti", - "credits-pricing-build-tier-over-10000-price": "$0.25 per minuto", + "credits-pricing-build-tier-over-10000-price": "$0.08 per minuto", "credits-pricing-build-title": "Tempo di build (minuti)", "credits-pricing-description": "I crediti coprono l'utilizzo oltre i limiti del tuo piano. Usa questi livelli per stimare quanti acquistarne.", "credits-pricing-disclaimer": "I crediti coprono l'utilizzo oltre i limiti inclusi nel piano. I crediti sono prepagati e restano validi per 12 mesi.", diff --git a/messages/ja.json b/messages/ja.json index 0e2fc79390..45095ae83e 100644 --- a/messages/ja.json +++ b/messages/ja.json @@ -609,17 +609,17 @@ "credits-pricing-bandwidth-title": "帯域幅 (GiB)", "credits-pricing-build-subtitle": "プランに含まれる分を超えてビルドした時間の1分ごとに。", "credits-pricing-build-tier-first-100": "最初の100分", - "credits-pricing-build-tier-first-100-price": "$0.50/分", + "credits-pricing-build-tier-first-100-price": "$0.16/分", "credits-pricing-build-tier-next-400": "次の400分", - "credits-pricing-build-tier-next-400-price": "$0.45/分", + "credits-pricing-build-tier-next-400-price": "$0.14/分", "credits-pricing-build-tier-next-4000": "次の4,000分", - "credits-pricing-build-tier-next-4000-price": "$0.35/分", + "credits-pricing-build-tier-next-4000-price": "$0.10/分", "credits-pricing-build-tier-next-500": "次の500分", - "credits-pricing-build-tier-next-500-price": "$0.40/分", + "credits-pricing-build-tier-next-500-price": "$0.12/分", "credits-pricing-build-tier-next-5000": "次の5,000分", - "credits-pricing-build-tier-next-5000-price": "$0.30/分", + "credits-pricing-build-tier-next-5000-price": "$0.09/分", "credits-pricing-build-tier-over-10000": "10,000分以上", - "credits-pricing-build-tier-over-10000-price": "$0.25/分", + "credits-pricing-build-tier-over-10000-price": "$0.08/分", "credits-pricing-build-title": "ビルド時間 (分)", "credits-pricing-description": "クレジットはプラン上限を超えた利用分をカバーします。必要な購入数の目安として以下の階層をご利用ください。", "credits-pricing-disclaimer": "クレジットはプランに含まれる上限を超えた利用を補います。クレジットは前払いで、12か月間有効です。", diff --git a/messages/ko.json b/messages/ko.json index 9866d8661f..d4871c7103 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -609,17 +609,17 @@ "credits-pricing-bandwidth-title": "대역폭 (GiB)", "credits-pricing-build-subtitle": "요금제에 포함된 분량을 넘는 빌드 시간의 분당 요금입니다.", "credits-pricing-build-tier-first-100": "처음 100분", - "credits-pricing-build-tier-first-100-price": "$0.50 (분당)", + "credits-pricing-build-tier-first-100-price": "$0.16 (분당)", "credits-pricing-build-tier-next-400": "다음 400분", - "credits-pricing-build-tier-next-400-price": "$0.45 (분당)", + "credits-pricing-build-tier-next-400-price": "$0.14 (분당)", "credits-pricing-build-tier-next-4000": "다음 4,000분", - "credits-pricing-build-tier-next-4000-price": "$0.35 (분당)", + "credits-pricing-build-tier-next-4000-price": "$0.10 (분당)", "credits-pricing-build-tier-next-500": "다음 500분", - "credits-pricing-build-tier-next-500-price": "$0.40 (분당)", + "credits-pricing-build-tier-next-500-price": "$0.12 (분당)", "credits-pricing-build-tier-next-5000": "다음 5,000분", - "credits-pricing-build-tier-next-5000-price": "$0.30 (분당)", + "credits-pricing-build-tier-next-5000-price": "$0.09 (분당)", "credits-pricing-build-tier-over-10000": "10,000분 초과", - "credits-pricing-build-tier-over-10000-price": "$0.25 (분당)", + "credits-pricing-build-tier-over-10000-price": "$0.08 (분당)", "credits-pricing-build-title": "빌드 시간 (분)", "credits-pricing-description": "크레딧은 요금제 한도를 초과한 사용량을 보완합니다. 아래 구간을 참고해 구매할 수량을 가늠하세요.", "credits-pricing-disclaimer": "크레딧은 요금제에 포함된 한도를 초과한 사용량을 보완합니다. 크레딧은 선불이며 12개월 동안 유효합니다.", diff --git a/messages/pl.json b/messages/pl.json index 8c36159879..0fdbd67d7e 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -609,17 +609,17 @@ "credits-pricing-bandwidth-title": "Przepustowość (GiB)", "credits-pricing-build-subtitle": "Za każdą minutę budowania poza tym, co obejmuje Twój plan.", "credits-pricing-build-tier-first-100": "Pierwsze 100 minut", - "credits-pricing-build-tier-first-100-price": "$0.50 za minutę", + "credits-pricing-build-tier-first-100-price": "$0.16 za minutę", "credits-pricing-build-tier-next-400": "Kolejne 400 minut", - "credits-pricing-build-tier-next-400-price": "$0.45 za minutę", + "credits-pricing-build-tier-next-400-price": "$0.14 za minutę", "credits-pricing-build-tier-next-4000": "Kolejne 4 000 minut", - "credits-pricing-build-tier-next-4000-price": "$0.35 za minutę", + "credits-pricing-build-tier-next-4000-price": "$0.10 za minutę", "credits-pricing-build-tier-next-500": "Kolejne 500 minut", - "credits-pricing-build-tier-next-500-price": "$0.40 za minutę", + "credits-pricing-build-tier-next-500-price": "$0.12 za minutę", "credits-pricing-build-tier-next-5000": "Kolejne 5 000 minut", - "credits-pricing-build-tier-next-5000-price": "$0.30 za minutę", + "credits-pricing-build-tier-next-5000-price": "$0.09 za minutę", "credits-pricing-build-tier-over-10000": "Powyżej 10 000 minut", - "credits-pricing-build-tier-over-10000-price": "$0.25 za minutę", + "credits-pricing-build-tier-over-10000-price": "$0.08 za minutę", "credits-pricing-build-title": "Czas budowania (minuty)", "credits-pricing-description": "Kredyty pokrywają zużycie przekraczające limity planu. Skorzystaj z poniższych progów, aby oszacować, ile należy kupić.", "credits-pricing-disclaimer": "Kredyty pokrywają zużycie poza limitami zawartymi w planie. Kredyty są opłacane z góry i ważne przez 12 miesięcy.", diff --git a/messages/pt-br.json b/messages/pt-br.json index 586c69a7b5..02d7aacb91 100644 --- a/messages/pt-br.json +++ b/messages/pt-br.json @@ -609,17 +609,17 @@ "credits-pricing-bandwidth-title": "Largura de banda (GiB)", "credits-pricing-build-subtitle": "Por minuto gasto em build além do que está incluído no seu plano.", "credits-pricing-build-tier-first-100": "Primeiros 100 minutos", - "credits-pricing-build-tier-first-100-price": "$0.50 por minuto", + "credits-pricing-build-tier-first-100-price": "$0.16 por minuto", "credits-pricing-build-tier-next-400": "Próximos 400 minutos", - "credits-pricing-build-tier-next-400-price": "$0.45 por minuto", + "credits-pricing-build-tier-next-400-price": "$0.14 por minuto", "credits-pricing-build-tier-next-4000": "Próximos 4.000 minutos", - "credits-pricing-build-tier-next-4000-price": "$0.35 por minuto", + "credits-pricing-build-tier-next-4000-price": "$0.10 por minuto", "credits-pricing-build-tier-next-500": "Próximos 500 minutos", - "credits-pricing-build-tier-next-500-price": "$0.40 por minuto", + "credits-pricing-build-tier-next-500-price": "$0.12 por minuto", "credits-pricing-build-tier-next-5000": "Próximos 5.000 minutos", - "credits-pricing-build-tier-next-5000-price": "$0.30 por minuto", + "credits-pricing-build-tier-next-5000-price": "$0.09 por minuto", "credits-pricing-build-tier-over-10000": "Acima de 10.000 minutos", - "credits-pricing-build-tier-over-10000-price": "$0.25 por minuto", + "credits-pricing-build-tier-over-10000-price": "$0.08 por minuto", "credits-pricing-build-title": "Tempo de build (minutos)", "credits-pricing-description": "Os créditos cobrem o uso além dos limites do seu plano. Use estes níveis para estimar quantos comprar.", "credits-pricing-disclaimer": "Os créditos cobrem o uso além dos limites incluídos no plano. Os créditos são pré-pagos e permanecem válidos por 12 meses.", diff --git a/messages/ru.json b/messages/ru.json index d4bea8b0f1..5335ee11e6 100644 --- a/messages/ru.json +++ b/messages/ru.json @@ -609,17 +609,17 @@ "credits-pricing-bandwidth-title": "Пропускная способность (GiB)", "credits-pricing-build-subtitle": "За каждую минуту сборки сверх того, что включено в ваш тариф.", "credits-pricing-build-tier-first-100": "Первые 100 минут", - "credits-pricing-build-tier-first-100-price": "$0.50 за минуту", + "credits-pricing-build-tier-first-100-price": "$0.16 за минуту", "credits-pricing-build-tier-next-400": "Следующие 400 минут", - "credits-pricing-build-tier-next-400-price": "$0.45 за минуту", + "credits-pricing-build-tier-next-400-price": "$0.14 за минуту", "credits-pricing-build-tier-next-4000": "Следующие 4 000 минут", - "credits-pricing-build-tier-next-4000-price": "$0.35 за минуту", + "credits-pricing-build-tier-next-4000-price": "$0.10 за минуту", "credits-pricing-build-tier-next-500": "Следующие 500 минут", - "credits-pricing-build-tier-next-500-price": "$0.40 за минуту", + "credits-pricing-build-tier-next-500-price": "$0.12 за минуту", "credits-pricing-build-tier-next-5000": "Следующие 5 000 минут", - "credits-pricing-build-tier-next-5000-price": "$0.30 за минуту", + "credits-pricing-build-tier-next-5000-price": "$0.09 за минуту", "credits-pricing-build-tier-over-10000": "Свыше 10 000 минут", - "credits-pricing-build-tier-over-10000-price": "$0.25 за минуту", + "credits-pricing-build-tier-over-10000-price": "$0.08 за минуту", "credits-pricing-build-title": "Время сборки (минуты)", "credits-pricing-description": "Кредиты покрывают использование сверх лимитов вашего тарифа. Используйте эти уровни, чтобы оценить, сколько необходимо приобрести.", "credits-pricing-disclaimer": "Кредиты покрывают использование сверх лимитов, включенных в тариф. Кредиты оплачиваются заранее и действуют 12 месяцев.", diff --git a/messages/tr.json b/messages/tr.json index 695566dfb1..b48648e290 100644 --- a/messages/tr.json +++ b/messages/tr.json @@ -609,17 +609,17 @@ "credits-pricing-bandwidth-title": "Bant genişliği (GiB)", "credits-pricing-build-subtitle": "Planınıza dahil olanın ötesinde yapılan build süresi için dakika başına.", "credits-pricing-build-tier-first-100": "İlk 100 dakika", - "credits-pricing-build-tier-first-100-price": "$0.50 dakika başına", + "credits-pricing-build-tier-first-100-price": "$0.16 dakika başına", "credits-pricing-build-tier-next-400": "Sonraki 400 dakika", - "credits-pricing-build-tier-next-400-price": "$0.45 dakika başına", + "credits-pricing-build-tier-next-400-price": "$0.14 dakika başına", "credits-pricing-build-tier-next-4000": "Sonraki 4.000 dakika", - "credits-pricing-build-tier-next-4000-price": "$0.35 dakika başına", + "credits-pricing-build-tier-next-4000-price": "$0.10 dakika başına", "credits-pricing-build-tier-next-500": "Sonraki 500 dakika", - "credits-pricing-build-tier-next-500-price": "$0.40 dakika başına", + "credits-pricing-build-tier-next-500-price": "$0.12 dakika başına", "credits-pricing-build-tier-next-5000": "Sonraki 5.000 dakika", - "credits-pricing-build-tier-next-5000-price": "$0.30 dakika başına", + "credits-pricing-build-tier-next-5000-price": "$0.09 dakika başına", "credits-pricing-build-tier-over-10000": "10.000 dakikanın üzerinde", - "credits-pricing-build-tier-over-10000-price": "$0.25 dakika başına", + "credits-pricing-build-tier-over-10000-price": "$0.08 dakika başına", "credits-pricing-build-title": "Build süresi (dakika)", "credits-pricing-description": "Krediler, plan limitinizi aşan kullanımı karşılar. Kaç adet almanız gerektiğini tahmin etmek için bu kademeleri kullanın.", "credits-pricing-disclaimer": "Krediler, plana dahil edilen limitlerin üzerindeki kullanımı karşılar. Krediler peşin ödenir ve 12 ay boyunca geçerlidir.", diff --git a/messages/vi.json b/messages/vi.json index bca617e6ff..272407c016 100644 --- a/messages/vi.json +++ b/messages/vi.json @@ -609,17 +609,17 @@ "credits-pricing-bandwidth-title": "Băng thông (GiB)", "credits-pricing-build-subtitle": "Tính cho mỗi phút build vượt quá những gì gói của bạn bao gồm.", "credits-pricing-build-tier-first-100": "100 phút đầu tiên", - "credits-pricing-build-tier-first-100-price": "$0.50 mỗi phút", + "credits-pricing-build-tier-first-100-price": "$0.16 mỗi phút", "credits-pricing-build-tier-next-400": "400 phút tiếp theo", - "credits-pricing-build-tier-next-400-price": "$0.45 mỗi phút", + "credits-pricing-build-tier-next-400-price": "$0.14 mỗi phút", "credits-pricing-build-tier-next-4000": "4.000 phút tiếp theo", - "credits-pricing-build-tier-next-4000-price": "$0.35 mỗi phút", + "credits-pricing-build-tier-next-4000-price": "$0.10 mỗi phút", "credits-pricing-build-tier-next-500": "500 phút tiếp theo", - "credits-pricing-build-tier-next-500-price": "$0.40 mỗi phút", + "credits-pricing-build-tier-next-500-price": "$0.12 mỗi phút", "credits-pricing-build-tier-next-5000": "5.000 phút tiếp theo", - "credits-pricing-build-tier-next-5000-price": "$0.30 mỗi phút", + "credits-pricing-build-tier-next-5000-price": "$0.09 mỗi phút", "credits-pricing-build-tier-over-10000": "Trên 10.000 phút", - "credits-pricing-build-tier-over-10000-price": "$0.25 mỗi phút", + "credits-pricing-build-tier-over-10000-price": "$0.08 mỗi phút", "credits-pricing-build-title": "Thời gian build (phút)", "credits-pricing-description": "Tín dụng dùng để chi trả phần sử dụng vượt giới hạn gói. Hãy dùng các bậc dưới đây để ước lượng số lượng cần mua.", "credits-pricing-disclaimer": "Tín dụng chi trả cho phần sử dụng vượt giới hạn gói. Tín dụng được trả trước và có hiệu lực trong 12 tháng.", diff --git a/messages/zh-cn.json b/messages/zh-cn.json index 5ff46f364e..3b788d65c6 100644 --- a/messages/zh-cn.json +++ b/messages/zh-cn.json @@ -609,17 +609,17 @@ "credits-pricing-bandwidth-title": "带宽 (GiB)", "credits-pricing-build-subtitle": "超出套餐包含部分的构建时间按分钟计费。", "credits-pricing-build-tier-first-100": "前 100 分钟", - "credits-pricing-build-tier-first-100-price": "$0.50 每分钟", + "credits-pricing-build-tier-first-100-price": "$0.16 每分钟", "credits-pricing-build-tier-next-400": "接下来的 400 分钟", - "credits-pricing-build-tier-next-400-price": "$0.45 每分钟", + "credits-pricing-build-tier-next-400-price": "$0.14 每分钟", "credits-pricing-build-tier-next-4000": "接下来的 4,000 分钟", - "credits-pricing-build-tier-next-4000-price": "$0.35 每分钟", + "credits-pricing-build-tier-next-4000-price": "$0.10 每分钟", "credits-pricing-build-tier-next-500": "接下来的 500 分钟", - "credits-pricing-build-tier-next-500-price": "$0.40 每分钟", + "credits-pricing-build-tier-next-500-price": "$0.12 每分钟", "credits-pricing-build-tier-next-5000": "接下来的 5,000 分钟", - "credits-pricing-build-tier-next-5000-price": "$0.30 每分钟", + "credits-pricing-build-tier-next-5000-price": "$0.09 每分钟", "credits-pricing-build-tier-over-10000": "超过 10,000 分钟", - "credits-pricing-build-tier-over-10000-price": "$0.25 每分钟", + "credits-pricing-build-tier-over-10000-price": "$0.08 每分钟", "credits-pricing-build-title": "构建时间(分钟)", "credits-pricing-description": "積分可覆盖超出套餐限制的用量。使用以下分级来估算需要购买的数量。", "credits-pricing-disclaimer": "積分用于覆盖超出套餐包含限制的用量。積分需预付并在 12 个月内有效。", diff --git a/src/pages/settings/organization/Usage.vue b/src/pages/settings/organization/Usage.vue index 950db7ed57..fc056334c4 100644 --- a/src/pages/settings/organization/Usage.vue +++ b/src/pages/settings/organization/Usage.vue @@ -10,7 +10,7 @@ import { toast } from 'vue-sonner' import CreditsCta from '~/components/CreditsCta.vue' import Spinner from '~/components/Spinner.vue' import { bytesToGb } from '~/services/conversion' -import { getCreditUnitPricing, getCurrentPlanNameOrg, getPlans, getPlanUsagePercent, getTotalStorage, getUsageCreditDeductions } from '~/services/supabase' +import { calculateCreditCost, getCurrentPlanNameOrg, getPlans, getPlanUsagePercent, getTotalStorage, getUsageCreditDeductions } from '~/services/supabase' import { sendEvent } from '~/services/tracking' import { useDialogV2Store } from '~/stores/dialogv2' import { useMainStore } from '~/stores/main' @@ -18,7 +18,6 @@ import { useMainStore } from '~/stores/main' const { t } = useI18n() const plans = ref([]) -const creditUnitPrices = ref>>({}) const isLoading = ref(false) const initialLoad = ref(true) @@ -73,20 +72,6 @@ async function getUsage(orgId: string) { } detailPlanUsage = roundUsagePercents(detailPlanUsage) - const enterprise_base = { - mau: currentPlan?.mau ?? 0, - storage: currentPlan?.storage ?? 0, - bandwidth: currentPlan?.bandwidth ?? 0, - build_time: currentPlan?.build_time_unit ?? 0, - } - - const enterprise_units = { - mau: creditUnitPrices.value.mau ?? 0, - storage: creditUnitPrices.value.storage ?? 0, - bandwidth: creditUnitPrices.value.bandwidth ?? 0, - build_time: creditUnitPrices.value?.build_time ?? 0, - } - const creditDeductions = await getUsageCreditDeductions(orgId) const nowEndOfDay = dayjs().endOf('day') @@ -151,31 +136,23 @@ async function getUsage(orgId: string) { }) const basePrice = currentPlan?.price_m ?? 0 + let estimatedUsagePrice = 0 - const calculatePrice = (total: number, base: number, unit: number) => { - if (unit <= 0) - return 0 - return total <= base ? 0 : (total - base) * unit + try { + const overageCost = await calculateCreditCost({ + mau: Math.max(totalMau - (currentPlan?.mau ?? 0), 0), + bandwidth: Math.max(totalBandwidthBytes - Math.round((currentPlan?.bandwidth ?? 0) * 1073741824), 0), + storage: Math.max(totalStorageBytes - Math.round((currentPlan?.storage ?? 0) * 1073741824), 0), + build_time: Math.max(totalBuildTime - (currentPlan?.build_time_unit ?? 0), 0), + }) + estimatedUsagePrice = roundNumber(overageCost.total_cost) + } + catch (err) { + console.error('Error estimating credit overage cost:', err) } - const estimatedUsagePrice = computed(() => { - const mauPrice = calculatePrice(totalMau, enterprise_base.mau, enterprise_units.mau) - const storagePrice = calculatePrice(totalStorage, enterprise_base.storage, enterprise_units.storage) - const bandwidthPrice = calculatePrice(totalBandwidth, enterprise_base.bandwidth, enterprise_units.bandwidth) - const buildTimePrice = calculatePrice(totalBuildTime, enterprise_base.build_time, enterprise_units.build_time) - const sum = mauPrice + storagePrice + bandwidthPrice + buildTimePrice - return roundNumber(sum) - }) - - const totalUsagePrice = computed(() => { - if (creditDeductions.length > 0) - return roundNumber(totalCreditDeductions) - return estimatedUsagePrice.value - }) - - const totalPrice = computed(() => { - return roundNumber(basePrice + totalUsagePrice.value) - }) + const totalUsagePrice = roundNumber(creditDeductions.length > 0 ? totalCreditDeductions : estimatedUsagePrice) + const totalPrice = roundNumber(basePrice + totalUsagePrice) return { currentPlan, @@ -185,7 +162,6 @@ async function getUsage(orgId: string) { totalBandwidth, totalStorage, totalBuildTime, - enterprise_units, detailPlanUsage, cycle: { subscription_anchor_start: dayjs(organizationStore.currentOrganization?.subscription_start).format('YYYY/MM/D'), @@ -308,16 +284,11 @@ async function loadData() { isLoading.value = true if (initialLoad.value) { - const [pls, pricing] = await Promise.all([ + const [pls] = await Promise.all([ getPlans(), - getCreditUnitPricing(gid || undefined), ]) plans.value.length = 0 plans.value.push(...pls) - creditUnitPrices.value = pricing - } - else if (!Object.keys(creditUnitPrices.value).length) { - creditUnitPrices.value = await getCreditUnitPricing(gid || undefined) } const usageDetails = await getUsage(gid) diff --git a/src/services/supabase.ts b/src/services/supabase.ts index d6151f33f1..9121882c72 100644 --- a/src/services/supabase.ts +++ b/src/services/supabase.ts @@ -387,6 +387,40 @@ export async function getPlans(): Promise> export type UsageCreditLedgerRow = Database['public']['Views']['usage_credit_ledger']['Row'] +export type CreditMetricType = Database['public']['Enums']['credit_metric_type'] + +export interface CreditTierUsage { + tier_id: number + step_min: number + step_max: number + unit_factor: number + units_used: number + price_per_unit: number + cost: number +} + +export interface CreditMetricBreakdown { + cost: number + tiers: CreditTierUsage[] +} + +export interface CreditCostCalculationRequest { + mau: number + bandwidth: number + storage: number + build_time?: number +} + +export interface CreditCostCalculationResponse { + total_cost: number + breakdown: Record + usage: { + mau: number + bandwidth: number + storage: number + build_time: number + } +} export async function getCreditUnitPricing(orgId?: string): Promise { try { @@ -447,6 +481,20 @@ export async function getUsageCreditDeductions(orgId: string): Promise { + const response = await useSupabase().functions.invoke('private/credits', { + body: { + ...request, + build_time: request.build_time ?? 0, + }, + }) + + if (response.error) + throw new Error(response.error.message) + + return response.data as CreditCostCalculationResponse +} + interface PlanUsage { total_percent: number mau_percent: number diff --git a/supabase/functions/_backend/private/credits.ts b/supabase/functions/_backend/private/credits.ts index c835451459..42649b94a6 100644 --- a/supabase/functions/_backend/private/credits.ts +++ b/supabase/functions/_backend/private/credits.ts @@ -26,6 +26,7 @@ interface CostCalculationRequest { mau: number bandwidth: number // in bytes storage: number // in bytes + build_time?: number // in seconds } interface TierUsage { @@ -33,7 +34,7 @@ interface TierUsage { step_min: number step_max: number unit_factor: number - units_used: number // billing units (GB for bandwidth/storage, count for MAU) + units_used: number // billing units (GiB/minutes/count) price_per_unit: number // Price per billing unit cost: number } @@ -49,11 +50,13 @@ interface CostCalculationResponse { mau: MetricBreakdown bandwidth: MetricBreakdown storage: MetricBreakdown + build_time: MetricBreakdown } usage: { mau: number bandwidth: number storage: number + build_time: number } } @@ -283,6 +286,7 @@ app.get('/', async (c) => { app.post('/', async (c) => { const body = await parseBody(c) + const buildTime = Number(body.build_time ?? 0) const { mau, bandwidth, storage } = body // Validate inputs @@ -376,8 +380,9 @@ app.post('/', async (c) => { const mauResult = calculateMetricCost(mau, 'mau') const bandwidthResult = calculateMetricCost(bandwidth, 'bandwidth') const storageResult = calculateMetricCost(storage, 'storage') + const buildTimeResult = calculateMetricCost(buildTime, 'build_time') - const totalCost = mauResult.cost + bandwidthResult.cost + storageResult.cost + const totalCost = mauResult.cost + bandwidthResult.cost + storageResult.cost + buildTimeResult.cost const response: CostCalculationResponse = { total_cost: totalCost, @@ -385,11 +390,13 @@ app.post('/', async (c) => { mau: mauResult, bandwidth: bandwidthResult, storage: storageResult, + build_time: buildTimeResult, }, usage: { mau, bandwidth, storage, + build_time: buildTime, }, } diff --git a/supabase/migrations/20260408134842_adjust_build_time_credit_pricing.sql b/supabase/migrations/20260408134842_adjust_build_time_credit_pricing.sql new file mode 100644 index 0000000000..c6344db330 --- /dev/null +++ b/supabase/migrations/20260408134842_adjust_build_time_credit_pricing.sql @@ -0,0 +1,23 @@ +-- Move build minutes onto the same shared usage-credit ladder used for the other +-- overage metrics while lowering the effective build-minute pricing. +-- Keep the existing ranges and unit_factor so the generic credit engine can +-- continue pricing build_time without any special-case logic. + +DELETE FROM public.capgo_credits_steps +WHERE type = 'build_time'; + +INSERT INTO public.capgo_credits_steps ( + type, + step_min, + step_max, + price_per_unit, + unit_factor, + org_id +) +VALUES + ('build_time', 0, 6000, 0.16, 60, NULL), + ('build_time', 6000, 30000, 0.14, 60, NULL), + ('build_time', 30000, 60000, 0.12, 60, NULL), + ('build_time', 60000, 300000, 0.10, 60, NULL), + ('build_time', 300000, 600000, 0.09, 60, NULL), + ('build_time', 600000, 9223372036854775807, 0.08, 60, NULL); diff --git a/supabase/seed.sql b/supabase/seed.sql index 0fe8dc7429..29cd021b1f 100644 --- a/supabase/seed.sql +++ b/supabase/seed.sql @@ -217,12 +217,12 @@ BEGIN 1073741824, NULL ), -- 1280+ GiB - ('build_time', 0, 6000, 0.5, 60, NULL), -- 0-100 minutes (in seconds, displayed as minutes) - ('build_time', 6000, 30000, 0.45, 60, NULL), -- 100-500 minutes (in seconds, displayed as minutes) - ('build_time', 30000, 60000, 0.40, 60, NULL), -- 500-1000 minutes (in seconds, displayed as minutes) - ('build_time', 60000, 300000, 0.35, 60, NULL), -- 1000-5000 minutes (in seconds, displayed as minutes) - ('build_time', 300000, 600000, 0.30, 60, NULL), -- 5000-10000 minutes (in seconds, displayed as minutes) - ('build_time', 600000, 9223372036854775807, 0.25, 60, NULL); -- 10000+ minutes (in seconds, displayed as minutes) + ('build_time', 0, 6000, 0.16, 60, NULL), -- 0-100 minutes (in seconds, displayed as minutes) + ('build_time', 6000, 30000, 0.14, 60, NULL), -- 100-500 minutes (in seconds, displayed as minutes) + ('build_time', 30000, 60000, 0.12, 60, NULL), -- 500-1000 minutes (in seconds, displayed as minutes) + ('build_time', 60000, 300000, 0.10, 60, NULL), -- 1000-5000 minutes (in seconds, displayed as minutes) + ('build_time', 300000, 600000, 0.09, 60, NULL), -- 5000-10000 minutes (in seconds, displayed as minutes) + ('build_time', 600000, 9223372036854775807, 0.08, 60, NULL); -- 10000+ minutes (in seconds, displayed as minutes) INSERT INTO "storage"."buckets" ("id", "name", "owner", "created_at", "updated_at", "public") VALUES ('capgo', 'capgo', NULL, NOW(), NOW(), 't'), diff --git a/supabase/tests/32_test_usage_credits.sql b/supabase/tests/32_test_usage_credits.sql index 3c60f60624..12d98ba16b 100644 --- a/supabase/tests/32_test_usage_credits.sql +++ b/supabase/tests/32_test_usage_credits.sql @@ -1,6 +1,6 @@ BEGIN; -SELECT plan(19); +SELECT plan(22); DO $$ BEGIN @@ -41,6 +41,27 @@ SELECT 'top_up_usage_credits qualifies source_ref lookups to avoid ambiguity' ); +SELECT + results_eq( + $$SELECT price_per_unit FROM public.capgo_credits_steps WHERE type = 'build_time' AND step_min = 0$$, + $$VALUES (0.16::double precision)$$, + 'build_time credit pricing starts at $0.16 per minute' + ); + +SELECT + results_eq( + $$SELECT price_per_unit FROM public.capgo_credits_steps WHERE type = 'build_time' ORDER BY step_max DESC LIMIT 1$$, + $$VALUES (0.08::double precision)$$, + 'build_time credit pricing floors at $0.08 per minute' + ); + +SELECT + results_eq( + $$SELECT credits_required FROM public.calculate_credit_cost('build_time', 6000)$$, + $$VALUES (16.0::numeric)$$, + 'calculate_credit_cost prices build_time through the shared credit ladder' + ); + CREATE TEMP TABLE test_credit_context ( org_id uuid, grant_id uuid, diff --git a/tests/credits-pricing.test.ts b/tests/credits-pricing.test.ts new file mode 100644 index 0000000000..88f34ce8d3 --- /dev/null +++ b/tests/credits-pricing.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, it } from 'vitest' +import { fetchWithRetry, getEndpointUrl } from './test-utils' + +interface CreditStep { + type: string + step_min: number + price_per_unit: number +} + +describe('credits pricing API', () => { + it('returns the updated build_time tiers from the shared pricing table', async () => { + const response = await fetchWithRetry(getEndpointUrl('/private/credits')) + + expect(response.status).toBe(200) + + const data = await response.json() as CreditStep[] + const buildSteps = data + .filter(step => step.type === 'build_time') + .sort((a, b) => a.step_min - b.step_min) + + expect(buildSteps.map(step => step.price_per_unit)).toEqual([0.16, 0.14, 0.12, 0.10, 0.09, 0.08]) + }) + + it('prices build_time overage through the shared calculator endpoint', async () => { + const response = await fetchWithRetry(getEndpointUrl('/private/credits'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + mau: 0, + bandwidth: 0, + storage: 0, + build_time: 6000, + }), + }) + + expect(response.status).toBe(200) + + const data = await response.json() as { + total_cost: number + breakdown: { + build_time: { + cost: number + } + } + usage: { + build_time: number + } + } + + expect(data.usage.build_time).toBe(6000) + expect(data.breakdown.build_time.cost).toBe(16) + expect(data.total_cost).toBe(16) + }) +}) From 6920ee1e7aa5836cdff05884980113749e899ee5 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 16:28:22 +0200 Subject: [PATCH 02/17] fix(credits): preserve org pricing overrides --- src/pages/settings/organization/Usage.vue | 1 + src/services/supabase.ts | 1 + .../functions/_backend/private/credits.ts | 93 +++++++++++++++---- ...34842_adjust_build_time_credit_pricing.sql | 3 +- tests/credits-pricing.test.ts | 46 ++++++++- 5 files changed, 124 insertions(+), 20 deletions(-) diff --git a/src/pages/settings/organization/Usage.vue b/src/pages/settings/organization/Usage.vue index fc056334c4..cae61f3649 100644 --- a/src/pages/settings/organization/Usage.vue +++ b/src/pages/settings/organization/Usage.vue @@ -140,6 +140,7 @@ async function getUsage(orgId: string) { try { const overageCost = await calculateCreditCost({ + org_id: orgId, mau: Math.max(totalMau - (currentPlan?.mau ?? 0), 0), bandwidth: Math.max(totalBandwidthBytes - Math.round((currentPlan?.bandwidth ?? 0) * 1073741824), 0), storage: Math.max(totalStorageBytes - Math.round((currentPlan?.storage ?? 0) * 1073741824), 0), diff --git a/src/services/supabase.ts b/src/services/supabase.ts index 9121882c72..db86792bbe 100644 --- a/src/services/supabase.ts +++ b/src/services/supabase.ts @@ -409,6 +409,7 @@ export interface CreditCostCalculationRequest { bandwidth: number storage: number build_time?: number + org_id?: string } export interface CreditCostCalculationResponse { diff --git a/supabase/functions/_backend/private/credits.ts b/supabase/functions/_backend/private/credits.ts index 42649b94a6..f33d756b49 100644 --- a/supabase/functions/_backend/private/credits.ts +++ b/supabase/functions/_backend/private/credits.ts @@ -27,6 +27,7 @@ interface CostCalculationRequest { bandwidth: number // in bytes storage: number // in bytes build_time?: number // in seconds + org_id?: string } interface TierUsage { @@ -75,6 +76,75 @@ const MAX_TOP_UP_QUANTITY = 100000 type AppContext = Context +function preferScopedCreditSteps(steps: CreditStep[], orgId?: string): CreditStep[] { + const sortedSteps = [...steps].sort((a, b) => { + if (a.type !== b.type) + return a.type.localeCompare(b.type) + + if (a.step_min !== b.step_min) + return a.step_min - b.step_min + + if (a.step_max !== b.step_max) + return a.step_max - b.step_max + + const aOrgPriority = orgId && a.org_id === orgId ? 0 : 1 + const bOrgPriority = orgId && b.org_id === orgId ? 0 : 1 + return aOrgPriority - bOrgPriority + }) + + const seenStepKeys = new Set() + + return sortedSteps.filter((step) => { + const key = `${step.type}:${step.step_min}:${step.step_max}` + if (seenStepKeys.has(key)) + return false + + seenStepKeys.add(key) + return true + }) +} + +async function getScopedCreditSteps(c: AppContext, orgId?: string): Promise { + const authorization = c.req.header('authorization') + ?? c.req.header('Authorization') + ?? c.get('authorization') + + const pricingClient = orgId + ? authorization + ? supabaseClient(c, authorization) + : undefined + : supabaseAdmin(c) + + if (!pricingClient) + throw simpleError('not_authorized', 'Not authorized') + + const scopedPricingClient = pricingClient + + const [globalCreditsResult, orgCreditsResult] = await Promise.all([ + scopedPricingClient + .from('capgo_credits_steps') + .select() + .is('org_id', null), + orgId + ? scopedPricingClient + .from('capgo_credits_steps') + .select() + .eq('org_id', orgId) + : Promise.resolve({ data: [] as CreditStep[], error: null }), + ]) + + const { data: globalCredits, error: globalCreditsError } = globalCreditsResult + const { data: orgCredits, error: orgCreditsError } = orgCreditsResult + + if (globalCreditsError || orgCreditsError) + throw simpleError('failed_to_fetch_pricing_data', 'Failed to fetch pricing data') + + return preferScopedCreditSteps([ + ...((globalCredits ?? []) as CreditStep[]), + ...((orgCredits ?? []) as CreditStep[]), + ], orgId) +} + async function getCreditTopUpProductId(c: AppContext, customerId: string, token: string): Promise<{ productId: string }> { const supabase = supabaseClient(c, token) const { data: stripeInfo, error: stripeInfoError } = await supabase @@ -273,11 +343,9 @@ app.use('*', useCors) app.get('/', async (c) => { try { - const { data: credits } = await supabaseAdmin(c) - .from('capgo_credits_steps') - .select() - .order('price_per_unit') - return c.json(credits ?? []) + const orgId = c.req.query('org_id') ?? undefined + const credits = await getScopedCreditSteps(c as AppContext, orgId) + return c.json(credits) } catch (e) { throw simpleError('failed_to_fetch_pricing_data', 'Failed to fetch pricing data', {}, e) @@ -287,25 +355,14 @@ app.get('/', async (c) => { app.post('/', async (c) => { const body = await parseBody(c) const buildTime = Number(body.build_time ?? 0) - const { mau, bandwidth, storage } = body + const { mau, bandwidth, org_id: orgId, storage } = body // Validate inputs if (mau === undefined || bandwidth === undefined || storage === undefined) { throw simpleError('missing_required_fields', 'Missing required fields: mau, bandwidth, storage') } - // Get pricing steps from database - const { data: credits, error } = await supabaseAdmin(c) - .from('capgo_credits_steps') - .select() - .order('type, step_min') - - if (error || !credits) { - throw simpleError('failed_to_fetch_pricing_data', 'Failed to fetch pricing data') - } - - // Type assertion for credits - const typedCredits = credits as CreditStep[] + const typedCredits = await getScopedCreditSteps(c as AppContext, orgId) // Calculate cost for each metric type with tier breakdown const calculateMetricCost = (value: number, type: string): MetricBreakdown => { diff --git a/supabase/migrations/20260408134842_adjust_build_time_credit_pricing.sql b/supabase/migrations/20260408134842_adjust_build_time_credit_pricing.sql index c6344db330..04969272dd 100644 --- a/supabase/migrations/20260408134842_adjust_build_time_credit_pricing.sql +++ b/supabase/migrations/20260408134842_adjust_build_time_credit_pricing.sql @@ -4,7 +4,8 @@ -- continue pricing build_time without any special-case logic. DELETE FROM public.capgo_credits_steps -WHERE type = 'build_time'; +WHERE type = 'build_time' + AND org_id IS NULL; INSERT INTO public.capgo_credits_steps ( type, diff --git a/tests/credits-pricing.test.ts b/tests/credits-pricing.test.ts index 88f34ce8d3..b89cc19035 100644 --- a/tests/credits-pricing.test.ts +++ b/tests/credits-pricing.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { fetchWithRetry, getEndpointUrl } from './test-utils' +import { executeSQL, fetchWithRetry, getAuthHeaders, getEndpointUrl, ORG_ID } from './test-utils' interface CreditStep { type: string @@ -53,4 +53,48 @@ describe('credits pricing API', () => { expect(data.breakdown.build_time.cost).toBe(16) expect(data.total_cost).toBe(16) }) + + it('uses org-scoped build_time tiers when an authorized org_id is supplied', async () => { + await executeSQL('DELETE FROM public.capgo_credits_steps WHERE org_id = $1 AND type = $2', [ORG_ID, 'build_time']) + + await executeSQL(` + INSERT INTO public.capgo_credits_steps (type, step_min, step_max, price_per_unit, unit_factor, org_id) + VALUES ($1, $2, $3, $4, $5, $6) + `, ['build_time', 0, 6000, 0.05, 60, ORG_ID]) + + try { + const response = await fetchWithRetry(getEndpointUrl('/private/credits'), { + method: 'POST', + headers: await getAuthHeaders(), + body: JSON.stringify({ + org_id: ORG_ID, + mau: 0, + bandwidth: 0, + storage: 0, + build_time: 6000, + }), + }) + + expect(response.status).toBe(200) + + const data = await response.json() as { + total_cost: number + breakdown: { + build_time: { + cost: number + tiers: { + price_per_unit: number + }[] + } + } + } + + expect(data.breakdown.build_time.tiers[0]?.price_per_unit).toBe(0.05) + expect(data.breakdown.build_time.cost).toBe(5) + expect(data.total_cost).toBe(5) + } + finally { + await executeSQL('DELETE FROM public.capgo_credits_steps WHERE org_id = $1 AND type = $2', [ORG_ID, 'build_time']) + } + }) }) From a921f91ab51ca925ae484d3f148cfb9a7c112981 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 16:36:43 +0200 Subject: [PATCH 03/17] fix(credits): harden build minute pricing --- src/pages/settings/organization/Usage.vue | 58 +++++++++++++------ .../functions/_backend/private/credits.ts | 2 + supabase/tests/32_test_usage_credits.sql | 15 ++++- tests/credits-pricing.test.ts | 27 ++++++++- 4 files changed, 79 insertions(+), 23 deletions(-) diff --git a/src/pages/settings/organization/Usage.vue b/src/pages/settings/organization/Usage.vue index cae61f3649..4793be9f26 100644 --- a/src/pages/settings/organization/Usage.vue +++ b/src/pages/settings/organization/Usage.vue @@ -136,24 +136,30 @@ async function getUsage(orgId: string) { }) const basePrice = currentPlan?.price_m ?? 0 - let estimatedUsagePrice = 0 - - try { - const overageCost = await calculateCreditCost({ - org_id: orgId, - mau: Math.max(totalMau - (currentPlan?.mau ?? 0), 0), - bandwidth: Math.max(totalBandwidthBytes - Math.round((currentPlan?.bandwidth ?? 0) * 1073741824), 0), - storage: Math.max(totalStorageBytes - Math.round((currentPlan?.storage ?? 0) * 1073741824), 0), - build_time: Math.max(totalBuildTime - (currentPlan?.build_time_unit ?? 0), 0), - }) - estimatedUsagePrice = roundNumber(overageCost.total_cost) - } - catch (err) { - console.error('Error estimating credit overage cost:', err) + let estimatedUsagePrice: number | null = null + + if (currentPlan) { + try { + const overageCost = await calculateCreditCost({ + org_id: orgId, + mau: Math.max(totalMau - currentPlan.mau, 0), + bandwidth: Math.max(totalBandwidthBytes - Math.round(currentPlan.bandwidth * 1073741824), 0), + storage: Math.max(totalStorageBytes - Math.round(currentPlan.storage * 1073741824), 0), + build_time: Math.max(totalBuildTime - currentPlan.build_time_unit, 0), + }) + estimatedUsagePrice = roundNumber(overageCost.total_cost) + } + catch (err) { + console.error('Error estimating credit overage cost:', err) + } } - const totalUsagePrice = roundNumber(creditDeductions.length > 0 ? totalCreditDeductions : estimatedUsagePrice) - const totalPrice = roundNumber(basePrice + totalUsagePrice) + const totalUsagePrice = creditDeductions.length > 0 + ? roundNumber(totalCreditDeductions) + : estimatedUsagePrice + const totalPrice = totalUsagePrice !== null && currentPlan + ? roundNumber(basePrice + totalUsagePrice) + : null return { currentPlan, @@ -182,6 +188,20 @@ function roundNumber(number: number) { return Math.round(number * 100) / 100 } +function formatCurrency(value?: number | null) { + if (typeof value !== 'number' || !Number.isFinite(value)) + return t('unknown') + + return `$${value.toLocaleString()}` +} + +function formatMonthlyPrice(value?: number | null) { + if (typeof value !== 'number' || !Number.isFinite(value)) + return t('unknown') + + return `$${value}/${t('mo')}` +} + function percent(usage: number, limit: number) { if (!Number.isFinite(usage) || !Number.isFinite(limit) || limit <= 0) return 0 @@ -364,7 +384,7 @@ function nextRunDate() { {{ t('base') }}
- ${{ currentPlan?.price_m }}/{{ t('mo') }} + {{ formatMonthlyPrice(currentPlan?.price_m) }}
@@ -372,7 +392,7 @@ function nextRunDate() { {{ t('credits-used-in-period') }}
- ${{ planUsage?.totalUsagePrice.toLocaleString() }} + {{ formatCurrency(planUsage?.totalUsagePrice) }}
@@ -381,7 +401,7 @@ function nextRunDate() { {{ t('total') }}
- ${{ planUsage?.totalPrice.toLocaleString() }} + {{ formatCurrency(planUsage?.totalPrice) }}
diff --git a/supabase/functions/_backend/private/credits.ts b/supabase/functions/_backend/private/credits.ts index f33d756b49..adad4c3c86 100644 --- a/supabase/functions/_backend/private/credits.ts +++ b/supabase/functions/_backend/private/credits.ts @@ -361,6 +361,8 @@ app.post('/', async (c) => { if (mau === undefined || bandwidth === undefined || storage === undefined) { throw simpleError('missing_required_fields', 'Missing required fields: mau, bandwidth, storage') } + if (!Number.isFinite(buildTime) || buildTime < 0) + throw simpleError('invalid_build_time', 'build_time must be a non-negative number') const typedCredits = await getScopedCreditSteps(c as AppContext, orgId) diff --git a/supabase/tests/32_test_usage_credits.sql b/supabase/tests/32_test_usage_credits.sql index 12d98ba16b..bd1d8ac269 100644 --- a/supabase/tests/32_test_usage_credits.sql +++ b/supabase/tests/32_test_usage_credits.sql @@ -43,14 +43,25 @@ SELECT SELECT results_eq( - $$SELECT price_per_unit FROM public.capgo_credits_steps WHERE type = 'build_time' AND step_min = 0$$, + $$SELECT price_per_unit + FROM public.capgo_credits_steps + WHERE type = 'build_time' + AND org_id IS NULL + AND step_min = 0 + ORDER BY step_max ASC + LIMIT 1$$, $$VALUES (0.16::double precision)$$, 'build_time credit pricing starts at $0.16 per minute' ); SELECT results_eq( - $$SELECT price_per_unit FROM public.capgo_credits_steps WHERE type = 'build_time' ORDER BY step_max DESC LIMIT 1$$, + $$SELECT price_per_unit + FROM public.capgo_credits_steps + WHERE type = 'build_time' + AND org_id IS NULL + ORDER BY step_max DESC, step_min DESC + LIMIT 1$$, $$VALUES (0.08::double precision)$$, 'build_time credit pricing floors at $0.08 per minute' ); diff --git a/tests/credits-pricing.test.ts b/tests/credits-pricing.test.ts index b89cc19035..f586aa05fe 100644 --- a/tests/credits-pricing.test.ts +++ b/tests/credits-pricing.test.ts @@ -8,7 +8,7 @@ interface CreditStep { } describe('credits pricing API', () => { - it('returns the updated build_time tiers from the shared pricing table', async () => { + it.concurrent('returns the updated build_time tiers from the shared pricing table', async () => { const response = await fetchWithRetry(getEndpointUrl('/private/credits')) expect(response.status).toBe(200) @@ -21,7 +21,7 @@ describe('credits pricing API', () => { expect(buildSteps.map(step => step.price_per_unit)).toEqual([0.16, 0.14, 0.12, 0.10, 0.09, 0.08]) }) - it('prices build_time overage through the shared calculator endpoint', async () => { + it.concurrent('prices build_time overage through the shared calculator endpoint', async () => { const response = await fetchWithRetry(getEndpointUrl('/private/credits'), { method: 'POST', headers: { @@ -54,6 +54,29 @@ describe('credits pricing API', () => { expect(data.total_cost).toBe(16) }) + it.concurrent('rejects negative build_time input', async () => { + const response = await fetchWithRetry(getEndpointUrl('/private/credits'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + mau: 0, + bandwidth: 0, + storage: 0, + build_time: -60, + }), + }) + + expect(response.status).toBe(400) + + const data = await response.json() as { + error: string + } + + expect(data.error).toBe('invalid_build_time') + }) + it('uses org-scoped build_time tiers when an authorized org_id is supplied', async () => { await executeSQL('DELETE FROM public.capgo_credits_steps WHERE org_id = $1 AND type = $2', [ORG_ID, 'build_time']) From fe0a4498aa73bf7977e5ed6f8a9012a640b7f043 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 16:37:52 +0200 Subject: [PATCH 04/17] fix(frontend): scope usage deductions to active cycle --- src/pages/settings/organization/Usage.vue | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/pages/settings/organization/Usage.vue b/src/pages/settings/organization/Usage.vue index 4793be9f26..814790de4d 100644 --- a/src/pages/settings/organization/Usage.vue +++ b/src/pages/settings/organization/Usage.vue @@ -94,9 +94,9 @@ async function getUsage(orgId: string) { const relevantUsage = usageInCycle.length > 0 ? usageInCycle : usage - const totalCreditDeductions = creditDeductions.reduce((acc, entry) => { + const creditDeductionsInCycle = creditDeductions.filter((entry) => { if (entry.amount === null) - return acc + return false const entryStart = entry.billing_cycle_start ? dayjs(entry.billing_cycle_start).startOf('day') @@ -111,13 +111,14 @@ async function getUsage(orgId: string) { : null if (billingStart && entryEnd && entryEnd.isBefore(billingStart)) - return acc + return false if (billingEnd && entryStart && entryStart.isAfter(billingEnd)) - return acc + return false - return acc + Math.abs(entry.amount) - }, 0) + return true + }) + const totalCreditDeductions = creditDeductionsInCycle.reduce((acc, entry) => acc + Math.abs(entry.amount ?? 0), 0) const totalMau = relevantUsage.reduce((acc, entry) => acc + (entry.mau ?? 0), 0) const totalBandwidthBytes = relevantUsage.reduce((acc, entry) => acc + (entry.bandwidth ?? 0), 0) @@ -154,7 +155,7 @@ async function getUsage(orgId: string) { } } - const totalUsagePrice = creditDeductions.length > 0 + const totalUsagePrice = creditDeductionsInCycle.length > 0 ? roundNumber(totalCreditDeductions) : estimatedUsagePrice const totalPrice = totalUsagePrice !== null && currentPlan From bc88606f928eaa254749d412d72d396ca5ee9259 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 16:47:20 +0200 Subject: [PATCH 05/17] fix(credits): preserve overage step history --- ...34842_adjust_build_time_credit_pricing.sql | 51 +++++++++---- supabase/tests/32_test_usage_credits.sql | 72 ++++++++++++++++++- 2 files changed, 109 insertions(+), 14 deletions(-) diff --git a/supabase/migrations/20260408134842_adjust_build_time_credit_pricing.sql b/supabase/migrations/20260408134842_adjust_build_time_credit_pricing.sql index 04969272dd..131845a61a 100644 --- a/supabase/migrations/20260408134842_adjust_build_time_credit_pricing.sql +++ b/supabase/migrations/20260408134842_adjust_build_time_credit_pricing.sql @@ -1,12 +1,30 @@ -- Move build minutes onto the same shared usage-credit ladder used for the other -- overage metrics while lowering the effective build-minute pricing. --- Keep the existing ranges and unit_factor so the generic credit engine can --- continue pricing build_time without any special-case logic. - -DELETE FROM public.capgo_credits_steps -WHERE type = 'build_time' - AND org_id IS NULL; +-- Keep the existing ranges and update rows in place so historical +-- usage_overage_events.credit_step_id links remain attached to their original +-- pricing tiers. +WITH desired_steps (step_min, step_max, price_per_unit, unit_factor) AS ( + VALUES + (0::bigint, 6000::bigint, 0.16::double precision, 60::bigint), + (6000::bigint, 30000::bigint, 0.14::double precision, 60::bigint), + (30000::bigint, 60000::bigint, 0.12::double precision, 60::bigint), + (60000::bigint, 300000::bigint, 0.10::double precision, 60::bigint), + (300000::bigint, 600000::bigint, 0.09::double precision, 60::bigint), + (600000::bigint, 9223372036854775807::bigint, 0.08::double precision, 60::bigint) +), +updated_steps AS ( + UPDATE public.capgo_credits_steps AS existing + SET + price_per_unit = desired_steps.price_per_unit, + unit_factor = desired_steps.unit_factor + FROM desired_steps + WHERE existing.type = 'build_time' + AND existing.org_id IS NULL + AND existing.step_min = desired_steps.step_min + AND existing.step_max = desired_steps.step_max + RETURNING existing.step_min, existing.step_max +) INSERT INTO public.capgo_credits_steps ( type, step_min, @@ -15,10 +33,17 @@ INSERT INTO public.capgo_credits_steps ( unit_factor, org_id ) -VALUES - ('build_time', 0, 6000, 0.16, 60, NULL), - ('build_time', 6000, 30000, 0.14, 60, NULL), - ('build_time', 30000, 60000, 0.12, 60, NULL), - ('build_time', 60000, 300000, 0.10, 60, NULL), - ('build_time', 300000, 600000, 0.09, 60, NULL), - ('build_time', 600000, 9223372036854775807, 0.08, 60, NULL); +SELECT + 'build_time', + desired_steps.step_min, + desired_steps.step_max, + desired_steps.price_per_unit, + desired_steps.unit_factor, + NULL +FROM desired_steps +WHERE NOT EXISTS ( + SELECT 1 + FROM updated_steps + WHERE updated_steps.step_min = desired_steps.step_min + AND updated_steps.step_max = desired_steps.step_max +); diff --git a/supabase/tests/32_test_usage_credits.sql b/supabase/tests/32_test_usage_credits.sql index bd1d8ac269..e6f6c40b2d 100644 --- a/supabase/tests/32_test_usage_credits.sql +++ b/supabase/tests/32_test_usage_credits.sql @@ -1,6 +1,6 @@ BEGIN; -SELECT plan(22); +SELECT plan(23); DO $$ BEGIN @@ -184,6 +184,76 @@ FROM grant_insert, step_insert; +CREATE TEMP TABLE test_repricing_context ( + credit_step_id bigint, + overage_event_id uuid +) ON COMMIT DROP; + +WITH repricing_step AS ( + INSERT INTO public.capgo_credits_steps ( + type, + step_min, + step_max, + price_per_unit, + unit_factor, + org_id + ) + VALUES ( + 'build_time', + 900000000, + 900006000, + 0.5, + 60, + NULL + ) + RETURNING id +), +repricing_overage AS ( + INSERT INTO public.usage_overage_events ( + org_id, + metric, + overage_amount, + credits_estimated, + credits_debited, + credit_step_id, + billing_cycle_start, + billing_cycle_end, + details + ) + SELECT + (SELECT org_id FROM test_credit_context), + 'build_time'::public.credit_metric_type, + 6000, + 50, + 0, + repricing_step.id, + current_date, + current_date, + '{}'::jsonb + FROM repricing_step + RETURNING id, credit_step_id +) +INSERT INTO test_repricing_context (credit_step_id, overage_event_id) +SELECT credit_step_id, id +FROM repricing_overage; + +UPDATE public.capgo_credits_steps +SET + price_per_unit = 0.16, + unit_factor = 60 +WHERE id = (SELECT credit_step_id FROM test_repricing_context); + +SELECT + is( + ( + SELECT credit_step_id + FROM public.usage_overage_events + WHERE id = (SELECT overage_event_id FROM test_repricing_context) + ), + (SELECT credit_step_id FROM test_repricing_context), + 'repricing build_time tiers in place preserves usage_overage_events credit_step_id links' + ); + SELECT throws_ok( $sql$ From 0051ade87f3db79e74415e49795b5abc6a2993fc Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 17:08:51 +0200 Subject: [PATCH 06/17] fix(credits): normalize org-scoped pricing tiers --- .../functions/_backend/private/credits.ts | 80 +++++++++++++++---- tests/credits-pricing.test.ts | 54 +++++++++++++ 2 files changed, 118 insertions(+), 16 deletions(-) diff --git a/supabase/functions/_backend/private/credits.ts b/supabase/functions/_backend/private/credits.ts index adad4c3c86..a2235a5d0b 100644 --- a/supabase/functions/_backend/private/credits.ts +++ b/supabase/functions/_backend/private/credits.ts @@ -76,32 +76,80 @@ const MAX_TOP_UP_QUANTITY = 100000 type AppContext = Context -function preferScopedCreditSteps(steps: CreditStep[], orgId?: string): CreditStep[] { - const sortedSteps = [...steps].sort((a, b) => { +function sortCreditSteps(steps: CreditStep[]): CreditStep[] { + return [...steps].sort((a, b) => { if (a.type !== b.type) return a.type.localeCompare(b.type) if (a.step_min !== b.step_min) return a.step_min - b.step_min - if (a.step_max !== b.step_max) - return a.step_max - b.step_max - - const aOrgPriority = orgId && a.org_id === orgId ? 0 : 1 - const bOrgPriority = orgId && b.org_id === orgId ? 0 : 1 - return aOrgPriority - bOrgPriority + return a.step_max - b.step_max }) +} - const seenStepKeys = new Set() +function subtractScopedRange(baseStep: CreditStep, scopedStep: CreditStep): CreditStep[] { + const overlapStart = Math.max(baseStep.step_min, scopedStep.step_min) + const overlapEnd = Math.min(baseStep.step_max, scopedStep.step_max) - return sortedSteps.filter((step) => { - const key = `${step.type}:${step.step_min}:${step.step_max}` - if (seenStepKeys.has(key)) - return false + if (overlapStart >= overlapEnd) + return [baseStep] - seenStepKeys.add(key) - return true - }) + const remainingSteps: CreditStep[] = [] + + if (baseStep.step_min < overlapStart) { + remainingSteps.push({ + ...baseStep, + step_min: baseStep.step_min, + step_max: overlapStart, + }) + } + + if (overlapEnd < baseStep.step_max) { + remainingSteps.push({ + ...baseStep, + step_min: overlapEnd, + step_max: baseStep.step_max, + }) + } + + return remainingSteps +} + +function preferScopedCreditSteps(steps: CreditStep[], orgId?: string): CreditStep[] { + if (!orgId) + return sortCreditSteps(steps) + + const stepGroups = new Map() + + for (const step of steps) { + const currentGroup = stepGroups.get(step.type) ?? { global: [], scoped: [] } + + if (step.org_id === orgId) + currentGroup.scoped.push(step) + else + currentGroup.global.push(step) + + stepGroups.set(step.type, currentGroup) + } + + const normalizedSteps: CreditStep[] = [] + + for (const [type, group] of stepGroups.entries()) { + const scopedSteps = sortCreditSteps(group.scoped.filter(step => step.type === type)) + if (scopedSteps.length === 0) { + normalizedSteps.push(...sortCreditSteps(group.global)) + continue + } + + let remainingGlobalSteps = sortCreditSteps(group.global) + for (const scopedStep of scopedSteps) + remainingGlobalSteps = remainingGlobalSteps.flatMap(globalStep => subtractScopedRange(globalStep, scopedStep)) + + normalizedSteps.push(...sortCreditSteps([...remainingGlobalSteps, ...scopedSteps])) + } + + return sortCreditSteps(normalizedSteps) } async function getScopedCreditSteps(c: AppContext, orgId?: string): Promise { diff --git a/tests/credits-pricing.test.ts b/tests/credits-pricing.test.ts index f586aa05fe..a0d0227914 100644 --- a/tests/credits-pricing.test.ts +++ b/tests/credits-pricing.test.ts @@ -120,4 +120,58 @@ describe('credits pricing API', () => { await executeSQL('DELETE FROM public.capgo_credits_steps WHERE org_id = $1 AND type = $2', [ORG_ID, 'build_time']) } }) + + it('falls back to the correct global tiers after a partial org-scoped override', async () => { + await executeSQL('DELETE FROM public.capgo_credits_steps WHERE org_id = $1 AND type = $2', [ORG_ID, 'build_time']) + + await executeSQL(` + INSERT INTO public.capgo_credits_steps (type, step_min, step_max, price_per_unit, unit_factor, org_id) + VALUES ($1, $2, $3, $4, $5, $6) + `, ['build_time', 0, 5000, 0.05, 60, ORG_ID]) + + try { + const response = await fetchWithRetry(getEndpointUrl('/private/credits'), { + method: 'POST', + headers: await getAuthHeaders(), + body: JSON.stringify({ + org_id: ORG_ID, + mau: 0, + bandwidth: 0, + storage: 0, + build_time: 8000, + }), + }) + + expect(response.status).toBe(200) + + const data = await response.json() as { + total_cost: number + breakdown: { + build_time: { + cost: number + tiers: { + step_min: number + step_max: number + price_per_unit: number + }[] + } + } + } + + expect(data.breakdown.build_time.tiers.map(tier => ({ + step_min: tier.step_min, + step_max: tier.step_max, + price_per_unit: tier.price_per_unit, + }))).toEqual([ + { step_min: 0, step_max: 5000, price_per_unit: 0.05 }, + { step_min: 5000, step_max: 6000, price_per_unit: 0.16 }, + { step_min: 6000, step_max: 30000, price_per_unit: 0.14 }, + ]) + expect(data.breakdown.build_time.cost).toBeCloseTo(11.68, 5) + expect(data.total_cost).toBeCloseTo(11.68, 5) + } + finally { + await executeSQL('DELETE FROM public.capgo_credits_steps WHERE org_id = $1 AND type = $2', [ORG_ID, 'build_time']) + } + }) }) From e03496f852758ea54383d616bd50e1610fe1a311 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 17:14:45 +0200 Subject: [PATCH 07/17] fix(credits): preserve auth errors on pricing fetch --- supabase/functions/_backend/private/credits.ts | 11 +++-------- tests/credits-pricing.test.ts | 12 ++++++++++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/supabase/functions/_backend/private/credits.ts b/supabase/functions/_backend/private/credits.ts index a2235a5d0b..03ecfeb01e 100644 --- a/supabase/functions/_backend/private/credits.ts +++ b/supabase/functions/_backend/private/credits.ts @@ -390,14 +390,9 @@ export const app = new Hono() app.use('*', useCors) app.get('/', async (c) => { - try { - const orgId = c.req.query('org_id') ?? undefined - const credits = await getScopedCreditSteps(c as AppContext, orgId) - return c.json(credits) - } - catch (e) { - throw simpleError('failed_to_fetch_pricing_data', 'Failed to fetch pricing data', {}, e) - } + const orgId = c.req.query('org_id') ?? undefined + const credits = await getScopedCreditSteps(c as AppContext, orgId) + return c.json(credits) }) app.post('/', async (c) => { diff --git a/tests/credits-pricing.test.ts b/tests/credits-pricing.test.ts index a0d0227914..2247c99157 100644 --- a/tests/credits-pricing.test.ts +++ b/tests/credits-pricing.test.ts @@ -21,6 +21,18 @@ describe('credits pricing API', () => { expect(buildSteps.map(step => step.price_per_unit)).toEqual([0.16, 0.14, 0.12, 0.10, 0.09, 0.08]) }) + it.concurrent('preserves not_authorized for org-scoped pricing queries without auth', async () => { + const response = await fetchWithRetry(getEndpointUrl(`/private/credits?org_id=${ORG_ID}`)) + + expect(response.status).toBe(400) + + const data = await response.json() as { + error: string + } + + expect(data.error).toBe('not_authorized') + }) + it.concurrent('prices build_time overage through the shared calculator endpoint', async () => { const response = await fetchWithRetry(getEndpointUrl('/private/credits'), { method: 'POST', From c1b2745f6048e50564a768ea1315a4b63e6c79d3 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 18:24:59 +0200 Subject: [PATCH 08/17] fix(pricing): show live credit overage tiers --- messages/en.json | 8 + src/pages/settings/organization/Credits.vue | 215 ++++---------------- src/pages/settings/organization/Plans.vue | 37 ++-- src/services/creditPricing.ts | 192 +++++++++++++++++ src/services/supabase.ts | 57 +++--- tests/credit-pricing-ui.unit.test.ts | 82 ++++++++ 6 files changed, 375 insertions(+), 216 deletions(-) create mode 100644 src/services/creditPricing.ts create mode 100644 tests/credit-pricing-ui.unit.test.ts diff --git a/messages/en.json b/messages/en.json index 6b846f07f8..97f231a770 100644 --- a/messages/en.json +++ b/messages/en.json @@ -634,6 +634,8 @@ "created-channel-within-7-days": "Created Channel (within 7 days)", "demo-apps-created": "Demo Apps Created", "credits-pagination-label": "Page {current} / {total}", + "credits-plan-overage": "{included}, then {price}", + "credits-pricing-price": "{price} {unit}", "credits-pricing-bandwidth-subtitle": "Per GiB delivered beyond what is included with your plan.", "credits-pricing-bandwidth-tier-first": "First 1 TB", "credits-pricing-bandwidth-tier-first-price": "$0.12 per GiB", @@ -705,7 +707,13 @@ "credits-pricing-storage-tier-over-1tb": "Over 1 TB", "credits-pricing-storage-tier-over-1tb-price": "$0.021 per GiB", "credits-pricing-storage-title": "Storage (GiB)", + "credits-pricing-tier-first": "First {amount}", + "credits-pricing-tier-next": "Next {amount}", + "credits-pricing-tier-over": "Over {amount}", "credits-pricing-title": "Credit pricing", + "credits-pricing-unit-per-gib": "per GiB", + "credits-pricing-unit-per-mau": "per MAU", + "credits-pricing-unit-per-minute": "per minute", "credits-top-up-quantity-help": "You can adjust the quantity directly in Stripe during checkout.", "credits-top-up-quantity-invalid": "Enter a valid credit amount before continuing.", "credits-top-up-quantity-label": "Credits to purchase", diff --git a/src/pages/settings/organization/Credits.vue b/src/pages/settings/organization/Credits.vue index 8709a1310b..2b6ed53d0b 100644 --- a/src/pages/settings/organization/Credits.vue +++ b/src/pages/settings/organization/Credits.vue @@ -1,4 +1,5 @@ diff --git a/src/pages/settings/organization/Plans.vue b/src/pages/settings/organization/Plans.vue index 3db3b52266..7046057d64 100644 --- a/src/pages/settings/organization/Plans.vue +++ b/src/pages/settings/organization/Plans.vue @@ -8,6 +8,7 @@ import { useRoute, useRouter } from 'vue-router' import { toast } from 'vue-sonner' import AdminOnlyModal from '~/components/AdminOnlyModal.vue' import CreditsCta from '~/components/CreditsCta.vue' +import { formatCreditPricingPrice, formatIncludedThenPrice } from '~/services/creditPricing' import { checkPermissions } from '~/services/permissions' import { openCheckout } from '~/services/stripe' import { getCreditUnitPricing, getCurrentPlanNameOrg, useSupabase } from '~/services/supabase' @@ -62,24 +63,32 @@ function planFeatures(plan: Database['public']['Tables']['plans']['Row']) { } } - const features = [ - `${plan.mau.toLocaleString()} ${t('mau')}`, - `${plan.storage.toLocaleString()} ${t('plan-storage')}`, - `${plan.bandwidth.toLocaleString()} ${t('plan-bandwidth')}`, - buildTimeDisplay, // Will be empty string if 0, filtered out below - ] + const mauFeature = creditUnitPrices.value.mau !== undefined + ? `${plan.mau.toLocaleString()} ${t('mau')} · ${formatIncludedThenPrice('mau', creditUnitPrices.value.mau, t)}` + : `${plan.mau.toLocaleString()} ${t('mau')}` - if (creditUnitPrices.value.mau) - features[0] += ` included, then $${creditUnitPrices.value.mau}/user` + const storageFeature = creditUnitPrices.value.storage !== undefined + ? `${plan.storage.toLocaleString()} ${t('plan-storage')} · ${formatIncludedThenPrice('storage', creditUnitPrices.value.storage, t)}` + : `${plan.storage.toLocaleString()} ${t('plan-storage')}` - if (creditUnitPrices.value.storage) - features[1] += ` included, then $${creditUnitPrices.value.storage} per GB` + const bandwidthFeature = creditUnitPrices.value.bandwidth !== undefined + ? `${plan.bandwidth.toLocaleString()} ${t('plan-bandwidth')} · ${formatIncludedThenPrice('bandwidth', creditUnitPrices.value.bandwidth, t)}` + : `${plan.bandwidth.toLocaleString()} ${t('plan-bandwidth')}` - if (creditUnitPrices.value.bandwidth) - features[2] += ` included, then $${creditUnitPrices.value.bandwidth} per GB` + const buildTimeFeature = buildTimeDisplay + ? creditUnitPrices.value.build_time !== undefined + ? `${buildTimeDisplay} · ${formatIncludedThenPrice('build_time', creditUnitPrices.value.build_time, t)}` + : buildTimeDisplay + : creditUnitPrices.value.build_time !== undefined + ? `${t('build-time')} · ${formatCreditPricingPrice('build_time', creditUnitPrices.value.build_time, t)}` + : '' - if (creditUnitPrices.value.build_time) - features[3] += ` included, then $${creditUnitPrices.value.build_time} per minute` + const features = [ + mauFeature, + storageFeature, + bandwidthFeature, + buildTimeFeature, + ] const planName = plan.name?.toLowerCase() ?? '' if (planName === 'solo') { diff --git a/src/services/creditPricing.ts b/src/services/creditPricing.ts new file mode 100644 index 0000000000..973a67f7e6 --- /dev/null +++ b/src/services/creditPricing.ts @@ -0,0 +1,192 @@ +import type { Database } from '~/types/supabase.types' + +export type CreditMetricType = Database['public']['Enums']['credit_metric_type'] + +export interface CreditPricingStep { + type: CreditMetricType + step_min: number + step_max: number + price_per_unit: number + unit_factor: number + org_id?: string | null +} + +type Translate = (key: string, values?: Record) => string + +export const creditPricingMetricOrder: CreditMetricType[] = ['mau', 'bandwidth', 'storage', 'build_time'] + +const creditPricingTierLabelKeys: Partial>> = { + mau: { + '0:1000000': 'credits-pricing-mau-tier-first', + '1000000:3000000': 'credits-pricing-mau-tier-next-2m', + '3000000:10000000': 'credits-pricing-mau-tier-next-7m', + '10000000:15000000': 'credits-pricing-mau-tier-next-5m', + '15000000:25000000': 'credits-pricing-mau-tier-next-10m', + '25000000:40000000': 'credits-pricing-mau-tier-next-15m', + '40000000:100000000': 'credits-pricing-mau-tier-next-60m', + '100000000:open': 'credits-pricing-mau-tier-over-100m', + }, + bandwidth: { + '0:1024': 'credits-pricing-bandwidth-tier-first', + '1024:2048': 'credits-pricing-bandwidth-tier-next-1tb', + '2048:6144': 'credits-pricing-bandwidth-tier-next-4tb', + '6144:12288': 'credits-pricing-bandwidth-tier-next-6tb', + '12288:25600': 'credits-pricing-bandwidth-tier-next-13tb', + '25600:64512': 'credits-pricing-bandwidth-tier-next-38tb', + '64512:130048': 'credits-pricing-bandwidth-tier-next-64tb', + '130048:open': 'credits-pricing-bandwidth-tier-over-128tb', + }, + storage: { + '0:1': 'credits-pricing-storage-tier-first', + '1:6': 'credits-pricing-storage-tier-next-5gib', + '6:25': 'credits-pricing-storage-tier-next-19gib', + '25:63': 'credits-pricing-storage-tier-next-38gib', + '63:250': 'credits-pricing-storage-tier-next-187gib', + '250:640': 'credits-pricing-storage-tier-next-390gib', + '640:1280': 'credits-pricing-storage-tier-next-640gib', + '1280:open': 'credits-pricing-storage-tier-over-1tb', + }, + build_time: { + '0:100': 'credits-pricing-build-tier-first-100', + '100:500': 'credits-pricing-build-tier-next-400', + '500:1000': 'credits-pricing-build-tier-next-500', + '1000:5000': 'credits-pricing-build-tier-next-4000', + '5000:10000': 'credits-pricing-build-tier-next-5000', + '10000:open': 'credits-pricing-build-tier-over-10000', + }, +} + +const creditPricingUnitLabelKeys: Record = { + mau: 'credits-pricing-unit-per-mau', + bandwidth: 'credits-pricing-unit-per-gib', + storage: 'credits-pricing-unit-per-gib', + build_time: 'credits-pricing-unit-per-minute', +} + +function getMetricOrder(metric: CreditMetricType) { + const index = creditPricingMetricOrder.indexOf(metric) + return index === -1 ? Number.MAX_SAFE_INTEGER : index +} + +function isOpenEndedTier(step: Pick) { + return !Number.isFinite(step.step_max) || step.step_max >= Number.MAX_SAFE_INTEGER +} + +function toBilledUnits(step: Pick, rawValue: number) { + const factor = step.unit_factor || 1 + return Math.ceil(rawValue / factor) +} + +function getTierLookupKey(step: Pick) { + const minUnits = toBilledUnits(step, step.step_min) + if (isOpenEndedTier(step)) + return `${minUnits}:open` + + const maxUnits = toBilledUnits(step, step.step_max) + return `${minUnits}:${maxUnits}` +} + +function formatCreditTierAmount(metric: CreditMetricType, billedUnits: number, locale?: string) { + const formatter = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 }) + + if (metric === 'mau' && billedUnits >= 1_000_000 && billedUnits % 1_000_000 === 0) + return `${formatter.format(billedUnits / 1_000_000)}M` + + if ((metric === 'bandwidth' || metric === 'storage') && billedUnits >= 1024 && billedUnits % 1024 === 0) + return `${formatter.format(billedUnits / 1024)} TB` + + if (metric === 'bandwidth' || metric === 'storage') + return `${formatter.format(billedUnits)} GiB` + + if (metric === 'build_time') + return `${formatter.format(billedUnits)} ${billedUnits === 1 ? 'minute' : 'minutes'}` + + return formatter.format(billedUnits) +} + +export function sortCreditPricingSteps(steps: CreditPricingStep[]) { + return [...steps].sort((left, right) => { + const metricOrderDiff = getMetricOrder(left.type) - getMetricOrder(right.type) + if (metricOrderDiff !== 0) + return metricOrderDiff + + if (left.step_min !== right.step_min) + return left.step_min - right.step_min + + return left.step_max - right.step_max + }) +} + +export function getFirstTierCreditUnitPricing(steps: CreditPricingStep[]) { + return sortCreditPricingSteps(steps).reduce>>((pricing, step) => { + if (pricing[step.type] === undefined) + pricing[step.type] = step.price_per_unit + + return pricing + }, {}) +} + +export function getCreditPricingTierLabelKey(step: Pick) { + return creditPricingTierLabelKeys[step.type]?.[getTierLookupKey(step)] ?? null +} + +export function formatCreditPriceValue(pricePerUnit: number, locale?: string) { + return new Intl.NumberFormat(locale, { + style: 'currency', + currency: 'USD', + minimumFractionDigits: 2, + maximumFractionDigits: 4, + }).format(pricePerUnit) +} + +export function formatCreditPricingPrice( + metric: CreditMetricType, + pricePerUnit: number, + t: Translate, + locale?: string, +) { + return t('credits-pricing-price', { + price: formatCreditPriceValue(pricePerUnit, locale), + unit: t(creditPricingUnitLabelKeys[metric]), + }) +} + +export function formatCreditPricingTierLabel( + step: Pick, + t: Translate, + locale?: string, +) { + const translatedTierKey = getCreditPricingTierLabelKey(step) + if (translatedTierKey) { + const translated = t(translatedTierKey) + if (translated !== translatedTierKey) + return translated + } + + const minUnits = toBilledUnits(step, step.step_min) + const maxUnits = toBilledUnits(step, step.step_max) + const openEnded = isOpenEndedTier(step) + + if (step.step_min === 0) { + return t('credits-pricing-tier-first', { + amount: formatCreditTierAmount(step.type, maxUnits, locale), + }) + } + + if (openEnded) { + return t('credits-pricing-tier-over', { + amount: formatCreditTierAmount(step.type, minUnits, locale), + }) + } + + return t('credits-pricing-tier-next', { + amount: formatCreditTierAmount(step.type, maxUnits - minUnits, locale), + }) +} + +export function formatIncludedThenPrice(metric: CreditMetricType, pricePerUnit: number, t: Translate, locale?: string) { + return t('credits-plan-overage', { + included: t('included-in-plan'), + price: formatCreditPricingPrice(metric, pricePerUnit, t, locale), + }) +} diff --git a/src/services/supabase.ts b/src/services/supabase.ts index db86792bbe..1bc573a408 100644 --- a/src/services/supabase.ts +++ b/src/services/supabase.ts @@ -1,10 +1,12 @@ import type { SupabaseClient } from '@supabase/supabase-js' import type { RouteLocationNormalizedLoaded } from 'vue-router' +import type { CreditMetricType, CreditPricingStep } from './creditPricing' import type { Database } from '~/types/supabase.types' import { format, parse } from '@std/semver' import { createClient } from '@supabase/supabase-js' import subset from 'semver/ranges/subset' import { ref } from 'vue' +import { getFirstTierCreditUnitPricing, sortCreditPricingSteps } from './creditPricing' let supaClient: SupabaseClient = null as any @@ -385,9 +387,8 @@ export async function getPlans(): Promise> +export type CreditUnitPricing = Partial> export type UsageCreditLedgerRow = Database['public']['Views']['usage_credit_ledger']['Row'] -export type CreditMetricType = Database['public']['Enums']['credit_metric_type'] export interface CreditTierUsage { tier_id: number @@ -423,35 +424,39 @@ export interface CreditCostCalculationResponse { } } -export async function getCreditUnitPricing(orgId?: string): Promise { +export async function getCreditPricingSteps(orgId?: string): Promise { try { - const { data, error } = await useSupabase() - .from('capgo_credits_steps') - .select('type, price_per_unit, step_min, org_id') - .eq('step_min', 0) - .order('step_min', { ascending: true }) - - if (error || !data) - throw new Error(error?.message ?? 'Failed to fetch credit pricing') - - const sortedSteps = [...data].sort((a, b) => { - const aOrgPriority = a.org_id && orgId && a.org_id === orgId ? 0 : 1 - const bOrgPriority = b.org_id && orgId && b.org_id === orgId ? 0 : 1 - - if (aOrgPriority !== bOrgPriority) - return aOrgPriority - bOrgPriority - - return (a.step_min ?? 0) - (b.step_min ?? 0) + const supabase = useSupabase() + const { data: currentSession } = await supabase.auth.getSession() + const endpoint = new URL(`${defaultApiHost}/private/credits`) + + if (orgId) + endpoint.searchParams.set('org_id', orgId) + + const response = await fetch(endpoint.toString(), { + headers: currentSession.session?.access_token + ? { + Authorization: `Bearer ${currentSession.session.access_token}`, + } + : undefined, }) - return sortedSteps.reduce((pricing, step) => { - const metric = step.type as Database['public']['Enums']['credit_metric_type'] + if (!response.ok) + throw new Error(`Failed to fetch credit pricing: HTTP ${response.status}`) - if (pricing[metric] === undefined) - pricing[metric] = step.price_per_unit + const data = await response.json() as CreditPricingStep[] + return sortCreditPricingSteps(data ?? []) + } + catch (err) { + console.error('getCreditPricingSteps error', err) + return [] + } +} - return pricing - }, {}) +export async function getCreditUnitPricing(orgId?: string): Promise { + try { + const steps = await getCreditPricingSteps(orgId) + return getFirstTierCreditUnitPricing(steps) } catch (err) { console.error('getCreditUnitPricing error', err) diff --git a/tests/credit-pricing-ui.unit.test.ts b/tests/credit-pricing-ui.unit.test.ts new file mode 100644 index 0000000000..771ef29abb --- /dev/null +++ b/tests/credit-pricing-ui.unit.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, it } from 'vitest' +import { formatCreditPricingPrice, formatCreditPricingTierLabel, formatIncludedThenPrice, getFirstTierCreditUnitPricing } from '../src/services/creditPricing' + +const messages: Record = { + 'credits-plan-overage': '{included}, then {price}', + 'credits-pricing-build-tier-first-100': 'First 100 minutes', + 'credits-pricing-price': '{price} {unit}', + 'credits-pricing-tier-first': 'First {amount}', + 'credits-pricing-tier-next': 'Next {amount}', + 'credits-pricing-tier-over': 'Over {amount}', + 'credits-pricing-unit-per-gib': 'per GiB', + 'credits-pricing-unit-per-mau': 'per MAU', + 'credits-pricing-unit-per-minute': 'per minute', + 'included-in-plan': 'Included in plan', +} + +function t(key: string, values: Record = {}) { + const template = messages[key] ?? key + return template.replaceAll(/\{(\w+)\}/g, (_match, placeholder) => String(values[placeholder] ?? `{${placeholder}}`)) +} + +describe('credit pricing UI helpers', () => { + it.concurrent('formats known build_time tiers using the existing translated labels', () => { + expect(formatCreditPricingTierLabel({ + type: 'build_time', + step_min: 0, + step_max: 6000, + unit_factor: 60, + }, t)).toBe('First 100 minutes') + + expect(formatCreditPricingPrice('build_time', 0.16, t)).toBe('$0.16 per minute') + }) + + it.concurrent('falls back to generic tier copy for custom org-scoped ranges', () => { + expect(formatCreditPricingTierLabel({ + type: 'build_time', + step_min: 3000, + step_max: 9000, + unit_factor: 60, + }, t)).toBe('Next 100 minutes') + + expect(formatCreditPricingTierLabel({ + type: 'build_time', + step_min: 9000, + step_max: Number.MAX_SAFE_INTEGER, + unit_factor: 60, + }, t)).toBe('Over 150 minutes') + }) + + it.concurrent('derives the visible first-tier pricing from the shared step list', () => { + expect(getFirstTierCreditUnitPricing([ + { + type: 'build_time', + step_min: 6000, + step_max: 30000, + price_per_unit: 0.14, + unit_factor: 60, + }, + { + type: 'bandwidth', + step_min: 0, + step_max: 1099511627776, + price_per_unit: 0.12, + unit_factor: 1073741824, + }, + { + type: 'build_time', + step_min: 0, + step_max: 6000, + price_per_unit: 0.16, + unit_factor: 60, + }, + ])).toEqual({ + bandwidth: 0.12, + build_time: 0.16, + }) + }) + + it.concurrent('formats plan overage copy from the shared price formatter', () => { + expect(formatIncludedThenPrice('build_time', 0.08, t)).toBe('Included in plan, then $0.08 per minute') + }) +}) From d3cbd6325bc216365bbddfe674f80a305237247a Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 18:43:55 +0200 Subject: [PATCH 09/17] fix(i18n): preload fallback locale messages --- src/modules/i18n.ts | 22 ++++++++++++++---- tests/i18n-fallback.unit.test.ts | 39 ++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 tests/i18n-fallback.unit.test.ts diff --git a/src/modules/i18n.ts b/src/modules/i18n.ts index 46fae89757..8b6b25a44d 100644 --- a/src/modules/i18n.ts +++ b/src/modules/i18n.ts @@ -2,13 +2,15 @@ import type { Locale } from 'vue-i18n' import type { UserModule } from '~/types' import { createI18n } from 'vue-i18n' +const FALLBACK_LOCALE = 'en' as const + // Import i18n resources // https://vitejs.dev/guide/features.html#glob-import // // Don't need this? Try vitesse-lite: https://github.com/antfu/vitesse-lite export const i18n = createI18n({ legacy: false, - fallbackLocale: 'en', + fallbackLocale: FALLBACK_LOCALE, locale: '', messages: {}, }) @@ -39,6 +41,15 @@ export const languages = { const loadedLanguages: string[] = [] +async function ensureLanguageLoaded(lang: Locale) { + if (loadedLanguages.includes(lang)) + return + + const messages = await localesMap[lang]() + i18n.global.setLocaleMessage(lang, messages.default) + loadedLanguages.push(lang) +} + function setI18nLanguage(lang: Locale) { i18n.global.locale.value = lang as any localStorage.setItem('lang', lang) @@ -48,8 +59,11 @@ function setI18nLanguage(lang: Locale) { } export async function loadLanguageAsync(lang: string): Promise { + if (lang !== FALLBACK_LOCALE) + await ensureLanguageLoaded(FALLBACK_LOCALE) + // If the same language - if (i18n.global.locale.value === lang) + if (i18n.global.locale.value === lang && loadedLanguages.includes(lang)) return setI18nLanguage(lang) // If the language was already loaded @@ -57,9 +71,7 @@ export async function loadLanguageAsync(lang: string): Promise { return setI18nLanguage(lang) // If the language hasn't been loaded yet - const messages = await localesMap[lang]() - i18n.global.setLocaleMessage(lang, messages.default) - loadedLanguages.push(lang) + await ensureLanguageLoaded(lang as Locale) return setI18nLanguage(lang) } diff --git a/tests/i18n-fallback.unit.test.ts b/tests/i18n-fallback.unit.test.ts new file mode 100644 index 0000000000..e20a85718c --- /dev/null +++ b/tests/i18n-fallback.unit.test.ts @@ -0,0 +1,39 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +describe('i18n fallback loading', () => { + beforeEach(() => { + vi.resetModules() + vi.stubGlobal('localStorage', { + clear: vi.fn(), + getItem: vi.fn(() => null), + removeItem: vi.fn(), + setItem: vi.fn(), + }) + vi.stubGlobal('document', { + createElement: vi.fn(() => ({ + innerHTML: '', + })), + querySelector: vi.fn(() => ({ + setAttribute: vi.fn(), + })), + }) + }) + + afterEach(() => { + vi.unstubAllGlobals() + vi.restoreAllMocks() + }) + + it.concurrent('loads the English fallback bundle before switching to another locale', async () => { + const { i18n, loadLanguageAsync } = await import('../src/modules/i18n.ts') + + await loadLanguageAsync('fr') + + expect(i18n.global.availableLocales).toEqual(expect.arrayContaining(['en', 'fr'])) + expect(i18n.global.locale.value).toBe('fr') + expect(i18n.global.t('credits-plan-overage', { + included: 'Included in plan', + price: '$0.08 per minute', + })).toBe('Included in plan, then $0.08 per minute') + }) +}) From 3ac7bb455ae46438a9e89de8699ecc0ca4281a95 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 21:39:03 +0200 Subject: [PATCH 10/17] docs: add credits UI screenshot --- docs/pr/credits-ui.png | Bin 0 -> 229784 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/pr/credits-ui.png diff --git a/docs/pr/credits-ui.png b/docs/pr/credits-ui.png new file mode 100644 index 0000000000000000000000000000000000000000..e886669c094f1dca2bce756cfa8541b56b54e385 GIT binary patch literal 229784 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� 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%0AFIKz#keyfyqrh``(2; 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 Date: Wed, 8 Apr 2026 21:49:53 +0200 Subject: [PATCH 11/17] fix(pricing): collapse tier translations --- messages/de.json | 60 --------------------- messages/en.json | 60 --------------------- messages/es.json | 60 --------------------- messages/fr.json | 60 --------------------- messages/hi.json | 60 --------------------- messages/id.json | 60 --------------------- messages/it.json | 60 --------------------- messages/ja.json | 60 --------------------- messages/ko.json | 60 --------------------- messages/pl.json | 60 --------------------- messages/pt-br.json | 60 --------------------- messages/ru.json | 60 --------------------- messages/tr.json | 60 --------------------- messages/vi.json | 60 --------------------- messages/zh-cn.json | 60 --------------------- src/services/creditPricing.ts | 81 +++++----------------------- tests/credit-pricing-ui.unit.test.ts | 10 ++-- tests/webhooks.test.ts | 26 ++++----- 18 files changed, 28 insertions(+), 989 deletions(-) diff --git a/messages/de.json b/messages/de.json index d3cd279812..92bc8eb783 100644 --- a/messages/de.json +++ b/messages/de.json @@ -590,75 +590,15 @@ "credits-only-info-title": "Kredite statt eines Plans verwenden", "credits-pagination-label": "Seite {current} / {total}", "credits-pricing-bandwidth-subtitle": "Pro GiB, das zusätzlich zu deinem Tarif ausgeliefert wird.", - "credits-pricing-bandwidth-tier-first": "Erste 1 TB", - "credits-pricing-bandwidth-tier-first-price": "$0.12 pro GiB", - "credits-pricing-bandwidth-tier-next-13tb": "Nächste 13 TB", - "credits-pricing-bandwidth-tier-next-13tb-price": "$0.055 pro GiB", - "credits-pricing-bandwidth-tier-next-1tb": "Nächste 1 TB", - "credits-pricing-bandwidth-tier-next-1tb-price": "$0.10 pro GiB", - "credits-pricing-bandwidth-tier-next-38tb": "Nächste 38 TB", - "credits-pricing-bandwidth-tier-next-38tb-price": "$0.04 pro GiB", - "credits-pricing-bandwidth-tier-next-4tb": "Nächste 4 TB", - "credits-pricing-bandwidth-tier-next-4tb-price": "$0.085 pro GiB", - "credits-pricing-bandwidth-tier-next-64tb": "Nächste 64 TB", - "credits-pricing-bandwidth-tier-next-64tb-price": "$0.03 pro GiB", - "credits-pricing-bandwidth-tier-next-6tb": "Nächste 6 TB", - "credits-pricing-bandwidth-tier-next-6tb-price": "$0.07 pro GiB", - "credits-pricing-bandwidth-tier-over-128tb": "Über 128 TB", - "credits-pricing-bandwidth-tier-over-128tb-price": "$0.02 pro GiB", "credits-pricing-bandwidth-title": "Bandbreite (GiB)", "credits-pricing-build-subtitle": "Pro Build-Minute, die über das in deinem Tarif enthaltene hinausgeht.", - "credits-pricing-build-tier-first-100": "Erste 100 Minuten", - "credits-pricing-build-tier-first-100-price": "$0.16 pro Minute", - "credits-pricing-build-tier-next-400": "Nächste 400 Minuten", - "credits-pricing-build-tier-next-400-price": "$0.14 pro Minute", - "credits-pricing-build-tier-next-4000": "Nächste 4.000 Minuten", - "credits-pricing-build-tier-next-4000-price": "$0.10 pro Minute", - "credits-pricing-build-tier-next-500": "Nächste 500 Minuten", - "credits-pricing-build-tier-next-500-price": "$0.12 pro Minute", - "credits-pricing-build-tier-next-5000": "Nächste 5.000 Minuten", - "credits-pricing-build-tier-next-5000-price": "$0.09 pro Minute", - "credits-pricing-build-tier-over-10000": "Über 10.000 Minuten", - "credits-pricing-build-tier-over-10000-price": "$0.08 pro Minute", "credits-pricing-build-title": "Build-Zeit (Minuten)", "credits-pricing-description": "Credits decken die Nutzung über die Grenzen deines Tarifs hinaus ab. Nutze diese Stufen, um abzuschätzen, wie viele du kaufen solltest.", "credits-pricing-disclaimer": "Credits decken die Nutzung über die im Tarif enthaltenen Grenzen hinaus ab. Credits werden im Voraus bezahlt und sind 12 Monate gültig.", "credits-pricing-footnote": "* Speicher wird pro GiB und Stunde berechnet.", "credits-pricing-mau-subtitle": "Pro Gerät, das sich mindestens einmal im Monat meldet.", - "credits-pricing-mau-tier-first": "Erste 1 Mio.", - "credits-pricing-mau-tier-first-price": "$0.003 pro MAU", - "credits-pricing-mau-tier-next-10m": "Nächste 10 Mio.", - "credits-pricing-mau-tier-next-10m-price": "$0.0011 pro MAU", - "credits-pricing-mau-tier-next-15m": "Nächste 15 Mio.", - "credits-pricing-mau-tier-next-15m-price": "$0.001 pro MAU", - "credits-pricing-mau-tier-next-2m": "Nächste 2 Mio.", - "credits-pricing-mau-tier-next-2m-price": "$0.0022 pro MAU", - "credits-pricing-mau-tier-next-5m": "Nächste 5 Mio.", - "credits-pricing-mau-tier-next-5m-price": "$0.0014 pro MAU", - "credits-pricing-mau-tier-next-60m": "Nächste 60 Mio.", - "credits-pricing-mau-tier-next-60m-price": "$0.0009 pro MAU", - "credits-pricing-mau-tier-next-7m": "Nächste 7 Mio.", - "credits-pricing-mau-tier-next-7m-price": "$0.0016 pro MAU", - "credits-pricing-mau-tier-over-100m": "Über 100 Mio.", - "credits-pricing-mau-tier-over-100m-price": "$0.0007 pro MAU", "credits-pricing-mau-title": "Monatlich aktive Nutzer (MAU)", "credits-pricing-storage-subtitle": "Pro GiB, das Speicherplatz für deine Releases belegt.", - "credits-pricing-storage-tier-first": "Erste 1 GiB", - "credits-pricing-storage-tier-first-price": "$0.09 pro GiB", - "credits-pricing-storage-tier-next-187gib": "Nächste 187 GiB", - "credits-pricing-storage-tier-next-187gib-price": "$0.04 pro GiB", - "credits-pricing-storage-tier-next-19gib": "Nächste 19 GiB", - "credits-pricing-storage-tier-next-19gib-price": "$0.065 pro GiB", - "credits-pricing-storage-tier-next-38gib": "Nächste 38 GiB", - "credits-pricing-storage-tier-next-38gib-price": "$0.05 pro GiB", - "credits-pricing-storage-tier-next-390gib": "Nächste 390 GiB", - "credits-pricing-storage-tier-next-390gib-price": "$0.03 pro GiB", - "credits-pricing-storage-tier-next-5gib": "Nächste 5 GiB", - "credits-pricing-storage-tier-next-5gib-price": "$0.08 pro GiB", - "credits-pricing-storage-tier-next-640gib": "Nächste 640 GiB", - "credits-pricing-storage-tier-next-640gib-price": "$0.025 pro GiB", - "credits-pricing-storage-tier-over-1tb": "Über 1 TB", - "credits-pricing-storage-tier-over-1tb-price": "$0.021 pro GiB", "credits-pricing-storage-title": "Speicher (GiB)", "credits-pricing-title": "Credit-Preise", "credits-top-up-quantity-help": "Du kannst die Menge während des Stripe-Checkouts direkt anpassen.", diff --git a/messages/en.json b/messages/en.json index 97f231a770..73bbc96b83 100644 --- a/messages/en.json +++ b/messages/en.json @@ -637,75 +637,15 @@ "credits-plan-overage": "{included}, then {price}", "credits-pricing-price": "{price} {unit}", "credits-pricing-bandwidth-subtitle": "Per GiB delivered beyond what is included with your plan.", - "credits-pricing-bandwidth-tier-first": "First 1 TB", - "credits-pricing-bandwidth-tier-first-price": "$0.12 per GiB", - "credits-pricing-bandwidth-tier-next-13tb": "Next 13 TB", - "credits-pricing-bandwidth-tier-next-13tb-price": "$0.055 per GiB", - "credits-pricing-bandwidth-tier-next-1tb": "Next 1 TB", - "credits-pricing-bandwidth-tier-next-1tb-price": "$0.10 per GiB", - "credits-pricing-bandwidth-tier-next-38tb": "Next 38 TB", - "credits-pricing-bandwidth-tier-next-38tb-price": "$0.04 per GiB", - "credits-pricing-bandwidth-tier-next-4tb": "Next 4 TB", - "credits-pricing-bandwidth-tier-next-4tb-price": "$0.085 per GiB", - "credits-pricing-bandwidth-tier-next-64tb": "Next 64 TB", - "credits-pricing-bandwidth-tier-next-64tb-price": "$0.03 per GiB", - "credits-pricing-bandwidth-tier-next-6tb": "Next 6 TB", - "credits-pricing-bandwidth-tier-next-6tb-price": "$0.07 per GiB", - "credits-pricing-bandwidth-tier-over-128tb": "Over 128 TB", - "credits-pricing-bandwidth-tier-over-128tb-price": "$0.02 per GiB", "credits-pricing-bandwidth-title": "Bandwidth (GiB)", "credits-pricing-build-subtitle": "Per minute spent building beyond what is included with your plan.", - "credits-pricing-build-tier-first-100": "First 100 minutes", - "credits-pricing-build-tier-first-100-price": "$0.16 per minute", - "credits-pricing-build-tier-next-400": "Next 400 minutes", - "credits-pricing-build-tier-next-400-price": "$0.14 per minute", - "credits-pricing-build-tier-next-4000": "Next 4,000 minutes", - "credits-pricing-build-tier-next-4000-price": "$0.10 per minute", - "credits-pricing-build-tier-next-500": "Next 500 minutes", - "credits-pricing-build-tier-next-500-price": "$0.12 per minute", - "credits-pricing-build-tier-next-5000": "Next 5,000 minutes", - "credits-pricing-build-tier-next-5000-price": "$0.09 per minute", - "credits-pricing-build-tier-over-10000": "Over 10,000 minutes", - "credits-pricing-build-tier-over-10000-price": "$0.08 per minute", "credits-pricing-build-title": "Build time (minutes)", "credits-pricing-description": "Credits cover usage beyond your plan limits. Use these tiers to estimate how many to purchase.", "credits-pricing-disclaimer": "Credits cover usage beyond included plan limits. Credits are prepaid and remain valid for 12 months.", "credits-pricing-footnote": "* Storage is calculated per GiB per hour.", "credits-pricing-mau-subtitle": "Per device checking in at least once during the month.", - "credits-pricing-mau-tier-first": "First 1M", - "credits-pricing-mau-tier-first-price": "$0.003 per MAU", - "credits-pricing-mau-tier-next-10m": "Next 10M", - "credits-pricing-mau-tier-next-10m-price": "$0.0011 per MAU", - "credits-pricing-mau-tier-next-15m": "Next 15M", - "credits-pricing-mau-tier-next-15m-price": "$0.001 per MAU", - "credits-pricing-mau-tier-next-2m": "Next 2M", - "credits-pricing-mau-tier-next-2m-price": "$0.0022 per MAU", - "credits-pricing-mau-tier-next-5m": "Next 5M", - "credits-pricing-mau-tier-next-5m-price": "$0.0014 per MAU", - "credits-pricing-mau-tier-next-60m": "Next 60M", - "credits-pricing-mau-tier-next-60m-price": "$0.0009 per MAU", - "credits-pricing-mau-tier-next-7m": "Next 7M", - "credits-pricing-mau-tier-next-7m-price": "$0.0016 per MAU", - "credits-pricing-mau-tier-over-100m": "Over 100M", - "credits-pricing-mau-tier-over-100m-price": "$0.0007 per MAU", "credits-pricing-mau-title": "Monthly Active Users (MAU)", "credits-pricing-storage-subtitle": "Per GiB occupying storage for your releases.", - "credits-pricing-storage-tier-first": "First 1 GiB", - "credits-pricing-storage-tier-first-price": "$0.09 per GiB", - "credits-pricing-storage-tier-next-187gib": "Next 187 GiB", - "credits-pricing-storage-tier-next-187gib-price": "$0.04 per GiB", - "credits-pricing-storage-tier-next-19gib": "Next 19 GiB", - "credits-pricing-storage-tier-next-19gib-price": "$0.065 per GiB", - "credits-pricing-storage-tier-next-38gib": "Next 38 GiB", - "credits-pricing-storage-tier-next-38gib-price": "$0.05 per GiB", - "credits-pricing-storage-tier-next-390gib": "Next 390 GiB", - "credits-pricing-storage-tier-next-390gib-price": "$0.03 per GiB", - "credits-pricing-storage-tier-next-5gib": "Next 5 GiB", - "credits-pricing-storage-tier-next-5gib-price": "$0.08 per GiB", - "credits-pricing-storage-tier-next-640gib": "Next 640 GiB", - "credits-pricing-storage-tier-next-640gib-price": "$0.025 per GiB", - "credits-pricing-storage-tier-over-1tb": "Over 1 TB", - "credits-pricing-storage-tier-over-1tb-price": "$0.021 per GiB", "credits-pricing-storage-title": "Storage (GiB)", "credits-pricing-tier-first": "First {amount}", "credits-pricing-tier-next": "Next {amount}", diff --git a/messages/es.json b/messages/es.json index 3e1cc1dde5..5c76d9e40f 100644 --- a/messages/es.json +++ b/messages/es.json @@ -590,75 +590,15 @@ "credits-only-info-title": "Utilizar créditos en lugar de un plan", "credits-pagination-label": "Página {current} / {total}", "credits-pricing-bandwidth-subtitle": "Por GiB entregado más allá de lo incluido en tu plan.", - "credits-pricing-bandwidth-tier-first": "Primer 1 TB", - "credits-pricing-bandwidth-tier-first-price": "$0.12 por GiB", - "credits-pricing-bandwidth-tier-next-13tb": "Siguientes 13 TB", - "credits-pricing-bandwidth-tier-next-13tb-price": "$0.055 por GiB", - "credits-pricing-bandwidth-tier-next-1tb": "Siguiente 1 TB", - "credits-pricing-bandwidth-tier-next-1tb-price": "$0.10 por GiB", - "credits-pricing-bandwidth-tier-next-38tb": "Siguientes 38 TB", - "credits-pricing-bandwidth-tier-next-38tb-price": "$0.04 por GiB", - "credits-pricing-bandwidth-tier-next-4tb": "Siguientes 4 TB", - "credits-pricing-bandwidth-tier-next-4tb-price": "$0.085 por GiB", - "credits-pricing-bandwidth-tier-next-64tb": "Siguientes 64 TB", - "credits-pricing-bandwidth-tier-next-64tb-price": "$0.03 por GiB", - "credits-pricing-bandwidth-tier-next-6tb": "Siguientes 6 TB", - "credits-pricing-bandwidth-tier-next-6tb-price": "$0.07 por GiB", - "credits-pricing-bandwidth-tier-over-128tb": "Más de 128 TB", - "credits-pricing-bandwidth-tier-over-128tb-price": "$0.02 por GiB", "credits-pricing-bandwidth-title": "Ancho de banda (GiB)", "credits-pricing-build-subtitle": "Por minuto dedicado a compilar más allá de lo incluido en tu plan.", - "credits-pricing-build-tier-first-100": "Primeros 100 minutos", - "credits-pricing-build-tier-first-100-price": "$0.16 por minuto", - "credits-pricing-build-tier-next-400": "Siguientes 400 minutos", - "credits-pricing-build-tier-next-400-price": "$0.14 por minuto", - "credits-pricing-build-tier-next-4000": "Siguientes 4.000 minutos", - "credits-pricing-build-tier-next-4000-price": "$0.10 por minuto", - "credits-pricing-build-tier-next-500": "Siguientes 500 minutos", - "credits-pricing-build-tier-next-500-price": "$0.12 por minuto", - "credits-pricing-build-tier-next-5000": "Siguientes 5.000 minutos", - "credits-pricing-build-tier-next-5000-price": "$0.09 por minuto", - "credits-pricing-build-tier-over-10000": "Más de 10.000 minutos", - "credits-pricing-build-tier-over-10000-price": "$0.08 por minuto", "credits-pricing-build-title": "Tiempo de compilación (minutos)", "credits-pricing-description": "Los créditos cubren el uso que supera los límites de tu plan. Usa estos niveles para estimar cuántos comprar.", "credits-pricing-disclaimer": "Los créditos cubren el uso que excede los límites incluidos en el plan. Los créditos se prepagan y son válidos durante 12 meses.", "credits-pricing-footnote": "* El almacenamiento se calcula por GiB por hora.", "credits-pricing-mau-subtitle": "Por dispositivo que se conecta al menos una vez durante el mes.", - "credits-pricing-mau-tier-first": "Primeros 1 M", - "credits-pricing-mau-tier-first-price": "$0.003 por MAU", - "credits-pricing-mau-tier-next-10m": "Siguientes 10 M", - "credits-pricing-mau-tier-next-10m-price": "$0.0011 por MAU", - "credits-pricing-mau-tier-next-15m": "Siguientes 15 M", - "credits-pricing-mau-tier-next-15m-price": "$0.001 por MAU", - "credits-pricing-mau-tier-next-2m": "Siguientes 2 M", - "credits-pricing-mau-tier-next-2m-price": "$0.0022 por MAU", - "credits-pricing-mau-tier-next-5m": "Siguientes 5 M", - "credits-pricing-mau-tier-next-5m-price": "$0.0014 por MAU", - "credits-pricing-mau-tier-next-60m": "Siguientes 60 M", - "credits-pricing-mau-tier-next-60m-price": "$0.0009 por MAU", - "credits-pricing-mau-tier-next-7m": "Siguientes 7 M", - "credits-pricing-mau-tier-next-7m-price": "$0.0016 por MAU", - "credits-pricing-mau-tier-over-100m": "Más de 100 M", - "credits-pricing-mau-tier-over-100m-price": "$0.0007 por MAU", "credits-pricing-mau-title": "Usuarios activos mensuales (MAU)", "credits-pricing-storage-subtitle": "Por GiB que ocupa almacenamiento para tus publicaciones.", - "credits-pricing-storage-tier-first": "Primer 1 GiB", - "credits-pricing-storage-tier-first-price": "$0.09 por GiB", - "credits-pricing-storage-tier-next-187gib": "Siguientes 187 GiB", - "credits-pricing-storage-tier-next-187gib-price": "$0.04 por GiB", - "credits-pricing-storage-tier-next-19gib": "Siguientes 19 GiB", - "credits-pricing-storage-tier-next-19gib-price": "$0.065 por GiB", - "credits-pricing-storage-tier-next-38gib": "Siguientes 38 GiB", - "credits-pricing-storage-tier-next-38gib-price": "$0.05 por GiB", - "credits-pricing-storage-tier-next-390gib": "Siguientes 390 GiB", - "credits-pricing-storage-tier-next-390gib-price": "$0.03 por GiB", - "credits-pricing-storage-tier-next-5gib": "Siguientes 5 GiB", - "credits-pricing-storage-tier-next-5gib-price": "$0.08 por GiB", - "credits-pricing-storage-tier-next-640gib": "Siguientes 640 GiB", - "credits-pricing-storage-tier-next-640gib-price": "$0.025 por GiB", - "credits-pricing-storage-tier-over-1tb": "Más de 1 TB", - "credits-pricing-storage-tier-over-1tb-price": "$0.021 por GiB", "credits-pricing-storage-title": "Almacenamiento (GiB)", "credits-pricing-title": "Precios de créditos", "credits-top-up-quantity-help": "Puedes ajustar la cantidad directamente en Stripe durante el pago.", diff --git a/messages/fr.json b/messages/fr.json index d47b511191..0446c134ce 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -590,75 +590,15 @@ "credits-only-info-title": "Utiliser des crédits au lieu d'un plan", "credits-pagination-label": "Page {current} / {total}", "credits-pricing-bandwidth-subtitle": "Par GiB livré au-delà de ce qui est inclus dans votre offre.", - "credits-pricing-bandwidth-tier-first": "Premiers 1 To", - "credits-pricing-bandwidth-tier-first-price": "$0.12 par GiB", - "credits-pricing-bandwidth-tier-next-13tb": "Prochains 13 To", - "credits-pricing-bandwidth-tier-next-13tb-price": "$0.055 par GiB", - "credits-pricing-bandwidth-tier-next-1tb": "Prochains 1 To", - "credits-pricing-bandwidth-tier-next-1tb-price": "$0.10 par GiB", - "credits-pricing-bandwidth-tier-next-38tb": "Prochains 38 To", - "credits-pricing-bandwidth-tier-next-38tb-price": "$0.04 par GiB", - "credits-pricing-bandwidth-tier-next-4tb": "Prochains 4 To", - "credits-pricing-bandwidth-tier-next-4tb-price": "$0.085 par GiB", - "credits-pricing-bandwidth-tier-next-64tb": "Prochains 64 To", - "credits-pricing-bandwidth-tier-next-64tb-price": "$0.03 par GiB", - "credits-pricing-bandwidth-tier-next-6tb": "Prochains 6 To", - "credits-pricing-bandwidth-tier-next-6tb-price": "$0.07 par GiB", - "credits-pricing-bandwidth-tier-over-128tb": "Au-delà de 128 To", - "credits-pricing-bandwidth-tier-over-128tb-price": "$0.02 par GiB", "credits-pricing-bandwidth-title": "Bande passante (GiB)", "credits-pricing-build-subtitle": "Par minute de build dépassant ce qui est inclus dans votre offre.", - "credits-pricing-build-tier-first-100": "Premières 100 minutes", - "credits-pricing-build-tier-first-100-price": "$0.16 par minute", - "credits-pricing-build-tier-next-400": "Prochaines 400 minutes", - "credits-pricing-build-tier-next-400-price": "$0.14 par minute", - "credits-pricing-build-tier-next-4000": "Prochaines 4 000 minutes", - "credits-pricing-build-tier-next-4000-price": "$0.10 par minute", - "credits-pricing-build-tier-next-500": "Prochaines 500 minutes", - "credits-pricing-build-tier-next-500-price": "$0.12 par minute", - "credits-pricing-build-tier-next-5000": "Prochaines 5 000 minutes", - "credits-pricing-build-tier-next-5000-price": "$0.09 par minute", - "credits-pricing-build-tier-over-10000": "Au-delà de 10 000 minutes", - "credits-pricing-build-tier-over-10000-price": "$0.08 par minute", "credits-pricing-build-title": "Temps de build (minutes)", "credits-pricing-description": "Les crédits couvrent l'utilisation au-delà des limites de votre offre. Utilisez ces paliers pour estimer combien en acheter.", "credits-pricing-disclaimer": "Les crédits couvrent l'utilisation au-delà des limites incluses dans l'offre. Les crédits sont prépayés et restent valables pendant 12 mois.", "credits-pricing-footnote": "* Le stockage est calculé par GiB et par heure.", "credits-pricing-mau-subtitle": "Par appareil se connectant au moins une fois durant le mois.", - "credits-pricing-mau-tier-first": "Premiers 1 M", - "credits-pricing-mau-tier-first-price": "$0.003 par MAU", - "credits-pricing-mau-tier-next-10m": "Prochains 10 M", - "credits-pricing-mau-tier-next-10m-price": "$0.0011 par MAU", - "credits-pricing-mau-tier-next-15m": "Prochains 15 M", - "credits-pricing-mau-tier-next-15m-price": "$0.001 par MAU", - "credits-pricing-mau-tier-next-2m": "Prochains 2 M", - "credits-pricing-mau-tier-next-2m-price": "$0.0022 par MAU", - "credits-pricing-mau-tier-next-5m": "Prochains 5 M", - "credits-pricing-mau-tier-next-5m-price": "$0.0014 par MAU", - "credits-pricing-mau-tier-next-60m": "Prochains 60 M", - "credits-pricing-mau-tier-next-60m-price": "$0.0009 par MAU", - "credits-pricing-mau-tier-next-7m": "Prochains 7 M", - "credits-pricing-mau-tier-next-7m-price": "$0.0016 par MAU", - "credits-pricing-mau-tier-over-100m": "Au-delà de 100 M", - "credits-pricing-mau-tier-over-100m-price": "$0.0007 par MAU", "credits-pricing-mau-title": "Utilisateurs actifs mensuels (MAU)", "credits-pricing-storage-subtitle": "Par GiB occupant l'espace de stockage de vos mises en production.", - "credits-pricing-storage-tier-first": "Premiers 1 GiB", - "credits-pricing-storage-tier-first-price": "$0.09 par GiB", - "credits-pricing-storage-tier-next-187gib": "Prochains 187 GiB", - "credits-pricing-storage-tier-next-187gib-price": "$0.04 par GiB", - "credits-pricing-storage-tier-next-19gib": "Prochains 19 GiB", - "credits-pricing-storage-tier-next-19gib-price": "$0.065 par GiB", - "credits-pricing-storage-tier-next-38gib": "Prochains 38 GiB", - "credits-pricing-storage-tier-next-38gib-price": "$0.05 par GiB", - "credits-pricing-storage-tier-next-390gib": "Prochains 390 GiB", - "credits-pricing-storage-tier-next-390gib-price": "$0.03 par GiB", - "credits-pricing-storage-tier-next-5gib": "Prochains 5 GiB", - "credits-pricing-storage-tier-next-5gib-price": "$0.08 par GiB", - "credits-pricing-storage-tier-next-640gib": "Prochains 640 GiB", - "credits-pricing-storage-tier-next-640gib-price": "$0.025 par GiB", - "credits-pricing-storage-tier-over-1tb": "Au-delà de 1 To", - "credits-pricing-storage-tier-over-1tb-price": "$0.021 par GiB", "credits-pricing-storage-title": "Stockage (GiB)", "credits-pricing-title": "Tarification des crédits", "credits-top-up-quantity-help": "Vous pourrez ajuster la quantité directement dans Stripe au moment du paiement.", diff --git a/messages/hi.json b/messages/hi.json index 4a707ff325..aa7ec47eb5 100644 --- a/messages/hi.json +++ b/messages/hi.json @@ -590,75 +590,15 @@ "credits-only-info-title": "योजना के बजाय क्रेडिट का उपयोग करना", "credits-pagination-label": "पृष्ठ {current} / {total}", "credits-pricing-bandwidth-subtitle": "आपके प्लान में शामिल सीमा से अधिक डिलीवर किए गए प्रति GiB पर।", - "credits-pricing-bandwidth-tier-first": "पहला 1 TB", - "credits-pricing-bandwidth-tier-first-price": "$0.12 प्रति GiB", - "credits-pricing-bandwidth-tier-next-13tb": "अगले 13 TB", - "credits-pricing-bandwidth-tier-next-13tb-price": "$0.055 प्रति GiB", - "credits-pricing-bandwidth-tier-next-1tb": "अगला 1 TB", - "credits-pricing-bandwidth-tier-next-1tb-price": "$0.10 प्रति GiB", - "credits-pricing-bandwidth-tier-next-38tb": "अगले 38 TB", - "credits-pricing-bandwidth-tier-next-38tb-price": "$0.04 प्रति GiB", - "credits-pricing-bandwidth-tier-next-4tb": "अगले 4 TB", - "credits-pricing-bandwidth-tier-next-4tb-price": "$0.085 प्रति GiB", - "credits-pricing-bandwidth-tier-next-64tb": "अगले 64 TB", - "credits-pricing-bandwidth-tier-next-64tb-price": "$0.03 प्रति GiB", - "credits-pricing-bandwidth-tier-next-6tb": "अगले 6 TB", - "credits-pricing-bandwidth-tier-next-6tb-price": "$0.07 प्रति GiB", - "credits-pricing-bandwidth-tier-over-128tb": "128 TB से अधिक", - "credits-pricing-bandwidth-tier-over-128tb-price": "$0.02 प्रति GiB", "credits-pricing-bandwidth-title": "बैंडविड्थ (GiB)", "credits-pricing-build-subtitle": "आपकी योजना में शामिल सीमा से परे किए गए हर बिल्ड मिनट पर।", - "credits-pricing-build-tier-first-100": "पहले 100 मिनट", - "credits-pricing-build-tier-first-100-price": "$0.16 प्रति मिनट", - "credits-pricing-build-tier-next-400": "अगले 400 मिनट", - "credits-pricing-build-tier-next-400-price": "$0.14 प्रति मिनट", - "credits-pricing-build-tier-next-4000": "अगले 4,000 मिनट", - "credits-pricing-build-tier-next-4000-price": "$0.10 प्रति मिनट", - "credits-pricing-build-tier-next-500": "अगले 500 मिनट", - "credits-pricing-build-tier-next-500-price": "$0.12 प्रति मिनट", - "credits-pricing-build-tier-next-5000": "अगले 5,000 मिनट", - "credits-pricing-build-tier-next-5000-price": "$0.09 प्रति मिनट", - "credits-pricing-build-tier-over-10000": "10,000 मिनट से अधिक", - "credits-pricing-build-tier-over-10000-price": "$0.08 प्रति मिनट", "credits-pricing-build-title": "बिल्ड समय (मिनट)", "credits-pricing-description": "क्रेडिट आपके प्लान की सीमा से अधिक उपयोग को कवर करते हैं। कितने क्रेडिट खरीदने हैं इसका अनुमान लगाने के लिए इन स्तरों का उपयोग करें।", "credits-pricing-disclaimer": "क्रेडिट आपके प्लान में शामिल सीमाओं से अधिक उपयोग को कवर करते हैं। क्रेडिट अग्रिम भुगतान किए जाते हैं और 12 महीनों तक मान्य रहते हैं।", "credits-pricing-footnote": "* स्टोरेज की गणना प्रति GiB प्रति घंटे के आधार पर होती है।", "credits-pricing-mau-subtitle": "प्रत्येक डिवाइस जो महीने में कम से कम एक बार चेक-इन करता है।", - "credits-pricing-mau-tier-first": "पहले 1 मिलियन", - "credits-pricing-mau-tier-first-price": "$0.003 प्रति MAU", - "credits-pricing-mau-tier-next-10m": "अगले 10 मिलियन", - "credits-pricing-mau-tier-next-10m-price": "$0.0011 प्रति MAU", - "credits-pricing-mau-tier-next-15m": "अगले 15 मिलियन", - "credits-pricing-mau-tier-next-15m-price": "$0.001 प्रति MAU", - "credits-pricing-mau-tier-next-2m": "अगले 2 मिलियन", - "credits-pricing-mau-tier-next-2m-price": "$0.0022 प्रति MAU", - "credits-pricing-mau-tier-next-5m": "अगले 5 मिलियन", - "credits-pricing-mau-tier-next-5m-price": "$0.0014 प्रति MAU", - "credits-pricing-mau-tier-next-60m": "अगले 60 मिलियन", - "credits-pricing-mau-tier-next-60m-price": "$0.0009 प्रति MAU", - "credits-pricing-mau-tier-next-7m": "अगले 7 मिलियन", - "credits-pricing-mau-tier-next-7m-price": "$0.0016 प्रति MAU", - "credits-pricing-mau-tier-over-100m": "100 मिलियन से अधिक", - "credits-pricing-mau-tier-over-100m-price": "$0.0007 प्रति MAU", "credits-pricing-mau-title": "मासिक सक्रिय उपयोगकर्ता (MAU)", "credits-pricing-storage-subtitle": "आपकी रिलीज़ के लिए स्टोरेज घेरने वाले प्रति GiB पर।", - "credits-pricing-storage-tier-first": "पहला 1 GiB", - "credits-pricing-storage-tier-first-price": "$0.09 प्रति GiB", - "credits-pricing-storage-tier-next-187gib": "अगले 187 GiB", - "credits-pricing-storage-tier-next-187gib-price": "$0.04 प्रति GiB", - "credits-pricing-storage-tier-next-19gib": "अगले 19 GiB", - "credits-pricing-storage-tier-next-19gib-price": "$0.065 प्रति GiB", - "credits-pricing-storage-tier-next-38gib": "अगले 38 GiB", - "credits-pricing-storage-tier-next-38gib-price": "$0.05 प्रति GiB", - "credits-pricing-storage-tier-next-390gib": "अगले 390 GiB", - "credits-pricing-storage-tier-next-390gib-price": "$0.03 प्रति GiB", - "credits-pricing-storage-tier-next-5gib": "अगले 5 GiB", - "credits-pricing-storage-tier-next-5gib-price": "$0.08 प्रति GiB", - "credits-pricing-storage-tier-next-640gib": "अगले 640 GiB", - "credits-pricing-storage-tier-next-640gib-price": "$0.025 प्रति GiB", - "credits-pricing-storage-tier-over-1tb": "1 TB से अधिक", - "credits-pricing-storage-tier-over-1tb-price": "$0.021 प्रति GiB", "credits-pricing-storage-title": "स्टोरेज (GiB)", "credits-pricing-title": "क्रेडिट मूल्य निर्धारण", "credits-top-up-quantity-help": "आप भुगतान करते समय Stripe में सीधे मात्रा समायोजित कर सकते हैं।", diff --git a/messages/id.json b/messages/id.json index b289d2a153..24e25d03a1 100644 --- a/messages/id.json +++ b/messages/id.json @@ -590,75 +590,15 @@ "credits-only-info-title": "Menggunakan kredit sebagai pengganti paket", "credits-pagination-label": "Halaman {current} / {total}", "credits-pricing-bandwidth-subtitle": "Per GiB yang dikirim melebihi batas paket Anda.", - "credits-pricing-bandwidth-tier-first": "1 TB pertama", - "credits-pricing-bandwidth-tier-first-price": "$0.12 per GiB", - "credits-pricing-bandwidth-tier-next-13tb": "13 TB berikutnya", - "credits-pricing-bandwidth-tier-next-13tb-price": "$0.055 per GiB", - "credits-pricing-bandwidth-tier-next-1tb": "1 TB berikutnya", - "credits-pricing-bandwidth-tier-next-1tb-price": "$0.10 per GiB", - "credits-pricing-bandwidth-tier-next-38tb": "38 TB berikutnya", - "credits-pricing-bandwidth-tier-next-38tb-price": "$0.04 per GiB", - "credits-pricing-bandwidth-tier-next-4tb": "4 TB berikutnya", - "credits-pricing-bandwidth-tier-next-4tb-price": "$0.085 per GiB", - "credits-pricing-bandwidth-tier-next-64tb": "64 TB berikutnya", - "credits-pricing-bandwidth-tier-next-64tb-price": "$0.03 per GiB", - "credits-pricing-bandwidth-tier-next-6tb": "6 TB berikutnya", - "credits-pricing-bandwidth-tier-next-6tb-price": "$0.07 per GiB", - "credits-pricing-bandwidth-tier-over-128tb": "Di atas 128 TB", - "credits-pricing-bandwidth-tier-over-128tb-price": "$0.02 per GiB", "credits-pricing-bandwidth-title": "Bandwidth (GiB)", "credits-pricing-build-subtitle": "Per menit waktu build di luar yang sudah termasuk dalam paket Anda.", - "credits-pricing-build-tier-first-100": "100 menit pertama", - "credits-pricing-build-tier-first-100-price": "$0.16 per menit", - "credits-pricing-build-tier-next-400": "400 menit berikutnya", - "credits-pricing-build-tier-next-400-price": "$0.14 per menit", - "credits-pricing-build-tier-next-4000": "4.000 menit berikutnya", - "credits-pricing-build-tier-next-4000-price": "$0.10 per menit", - "credits-pricing-build-tier-next-500": "500 menit berikutnya", - "credits-pricing-build-tier-next-500-price": "$0.12 per menit", - "credits-pricing-build-tier-next-5000": "5.000 menit berikutnya", - "credits-pricing-build-tier-next-5000-price": "$0.09 per menit", - "credits-pricing-build-tier-over-10000": "Di atas 10.000 menit", - "credits-pricing-build-tier-over-10000-price": "$0.08 per menit", "credits-pricing-build-title": "Waktu build (menit)", "credits-pricing-description": "Kredit menutup penggunaan yang melampaui batas paket Anda. Gunakan tingkatan ini untuk memperkirakan jumlah yang perlu dibeli.", "credits-pricing-disclaimer": "Kredit menutup penggunaan di luar batas paket yang disertakan. Kredit dibayar di muka dan berlaku selama 12 bulan.", "credits-pricing-footnote": "* Penyimpanan dihitung per GiB per jam.", "credits-pricing-mau-subtitle": "Per perangkat yang check-in setidaknya sekali dalam sebulan.", - "credits-pricing-mau-tier-first": "1 M pertama", - "credits-pricing-mau-tier-first-price": "$0.003 per MAU", - "credits-pricing-mau-tier-next-10m": "10 M berikutnya", - "credits-pricing-mau-tier-next-10m-price": "$0.0011 per MAU", - "credits-pricing-mau-tier-next-15m": "15 M berikutnya", - "credits-pricing-mau-tier-next-15m-price": "$0.001 per MAU", - "credits-pricing-mau-tier-next-2m": "2 M berikutnya", - "credits-pricing-mau-tier-next-2m-price": "$0.0022 per MAU", - "credits-pricing-mau-tier-next-5m": "5 M berikutnya", - "credits-pricing-mau-tier-next-5m-price": "$0.0014 per MAU", - "credits-pricing-mau-tier-next-60m": "60 M berikutnya", - "credits-pricing-mau-tier-next-60m-price": "$0.0009 per MAU", - "credits-pricing-mau-tier-next-7m": "7 M berikutnya", - "credits-pricing-mau-tier-next-7m-price": "$0.0016 per MAU", - "credits-pricing-mau-tier-over-100m": "Di atas 100 M", - "credits-pricing-mau-tier-over-100m-price": "$0.0007 per MAU", "credits-pricing-mau-title": "Pengguna aktif bulanan (MAU)", "credits-pricing-storage-subtitle": "Per GiB yang memakai penyimpanan untuk rilis Anda.", - "credits-pricing-storage-tier-first": "1 GiB pertama", - "credits-pricing-storage-tier-first-price": "$0.09 per GiB", - "credits-pricing-storage-tier-next-187gib": "187 GiB berikutnya", - "credits-pricing-storage-tier-next-187gib-price": "$0.04 per GiB", - "credits-pricing-storage-tier-next-19gib": "19 GiB berikutnya", - "credits-pricing-storage-tier-next-19gib-price": "$0.065 per GiB", - "credits-pricing-storage-tier-next-38gib": "38 GiB berikutnya", - "credits-pricing-storage-tier-next-38gib-price": "$0.05 per GiB", - "credits-pricing-storage-tier-next-390gib": "390 GiB berikutnya", - "credits-pricing-storage-tier-next-390gib-price": "$0.03 per GiB", - "credits-pricing-storage-tier-next-5gib": "5 GiB berikutnya", - "credits-pricing-storage-tier-next-5gib-price": "$0.08 per GiB", - "credits-pricing-storage-tier-next-640gib": "640 GiB berikutnya", - "credits-pricing-storage-tier-next-640gib-price": "$0.025 per GiB", - "credits-pricing-storage-tier-over-1tb": "Di atas 1 TB", - "credits-pricing-storage-tier-over-1tb-price": "$0.021 per GiB", "credits-pricing-storage-title": "Penyimpanan (GiB)", "credits-pricing-title": "Harga kredit", "credits-top-up-quantity-help": "Anda dapat menyesuaikan jumlah langsung di Stripe saat checkout.", diff --git a/messages/it.json b/messages/it.json index cb78a29615..4496a4edd5 100644 --- a/messages/it.json +++ b/messages/it.json @@ -590,75 +590,15 @@ "credits-only-info-title": "Utilizzo di crediti invece di un piano", "credits-pagination-label": "Pagina {current} / {total}", "credits-pricing-bandwidth-subtitle": "Per GiB erogato oltre quanto incluso nel tuo piano.", - "credits-pricing-bandwidth-tier-first": "Primo 1 TB", - "credits-pricing-bandwidth-tier-first-price": "$0.12 per GiB", - "credits-pricing-bandwidth-tier-next-13tb": "Successivi 13 TB", - "credits-pricing-bandwidth-tier-next-13tb-price": "$0.055 per GiB", - "credits-pricing-bandwidth-tier-next-1tb": "Successivo 1 TB", - "credits-pricing-bandwidth-tier-next-1tb-price": "$0.10 per GiB", - "credits-pricing-bandwidth-tier-next-38tb": "Successivi 38 TB", - "credits-pricing-bandwidth-tier-next-38tb-price": "$0.04 per GiB", - "credits-pricing-bandwidth-tier-next-4tb": "Successivi 4 TB", - "credits-pricing-bandwidth-tier-next-4tb-price": "$0.085 per GiB", - "credits-pricing-bandwidth-tier-next-64tb": "Successivi 64 TB", - "credits-pricing-bandwidth-tier-next-64tb-price": "$0.03 per GiB", - "credits-pricing-bandwidth-tier-next-6tb": "Successivi 6 TB", - "credits-pricing-bandwidth-tier-next-6tb-price": "$0.07 per GiB", - "credits-pricing-bandwidth-tier-over-128tb": "Oltre 128 TB", - "credits-pricing-bandwidth-tier-over-128tb-price": "$0.02 per GiB", "credits-pricing-bandwidth-title": "Banda (GiB)", "credits-pricing-build-subtitle": "Per ogni minuto di build oltre quanto incluso nel tuo piano.", - "credits-pricing-build-tier-first-100": "Primi 100 minuti", - "credits-pricing-build-tier-first-100-price": "$0.16 per minuto", - "credits-pricing-build-tier-next-400": "Prossimi 400 minuti", - "credits-pricing-build-tier-next-400-price": "$0.14 per minuto", - "credits-pricing-build-tier-next-4000": "Prossimi 4.000 minuti", - "credits-pricing-build-tier-next-4000-price": "$0.10 per minuto", - "credits-pricing-build-tier-next-500": "Prossimi 500 minuti", - "credits-pricing-build-tier-next-500-price": "$0.12 per minuto", - "credits-pricing-build-tier-next-5000": "Prossimi 5.000 minuti", - "credits-pricing-build-tier-next-5000-price": "$0.09 per minuto", - "credits-pricing-build-tier-over-10000": "Oltre 10.000 minuti", - "credits-pricing-build-tier-over-10000-price": "$0.08 per minuto", "credits-pricing-build-title": "Tempo di build (minuti)", "credits-pricing-description": "I crediti coprono l'utilizzo oltre i limiti del tuo piano. Usa questi livelli per stimare quanti acquistarne.", "credits-pricing-disclaimer": "I crediti coprono l'utilizzo oltre i limiti inclusi nel piano. I crediti sono prepagati e restano validi per 12 mesi.", "credits-pricing-footnote": "* L'archiviazione è calcolata per GiB per ora.", "credits-pricing-mau-subtitle": "Per dispositivo che si collega almeno una volta durante il mese.", - "credits-pricing-mau-tier-first": "Primi 1 M", - "credits-pricing-mau-tier-first-price": "$0.003 per MAU", - "credits-pricing-mau-tier-next-10m": "Successivi 10 M", - "credits-pricing-mau-tier-next-10m-price": "$0.0011 per MAU", - "credits-pricing-mau-tier-next-15m": "Successivi 15 M", - "credits-pricing-mau-tier-next-15m-price": "$0.001 per MAU", - "credits-pricing-mau-tier-next-2m": "Successivi 2 M", - "credits-pricing-mau-tier-next-2m-price": "$0.0022 per MAU", - "credits-pricing-mau-tier-next-5m": "Successivi 5 M", - "credits-pricing-mau-tier-next-5m-price": "$0.0014 per MAU", - "credits-pricing-mau-tier-next-60m": "Successivi 60 M", - "credits-pricing-mau-tier-next-60m-price": "$0.0009 per MAU", - "credits-pricing-mau-tier-next-7m": "Successivi 7 M", - "credits-pricing-mau-tier-next-7m-price": "$0.0016 per MAU", - "credits-pricing-mau-tier-over-100m": "Oltre 100 M", - "credits-pricing-mau-tier-over-100m-price": "$0.0007 per MAU", "credits-pricing-mau-title": "Utenti attivi mensili (MAU)", "credits-pricing-storage-subtitle": "Per GiB che occupa lo spazio di archiviazione delle tue release.", - "credits-pricing-storage-tier-first": "Primo 1 GiB", - "credits-pricing-storage-tier-first-price": "$0.09 per GiB", - "credits-pricing-storage-tier-next-187gib": "Successivi 187 GiB", - "credits-pricing-storage-tier-next-187gib-price": "$0.04 per GiB", - "credits-pricing-storage-tier-next-19gib": "Successivi 19 GiB", - "credits-pricing-storage-tier-next-19gib-price": "$0.065 per GiB", - "credits-pricing-storage-tier-next-38gib": "Successivi 38 GiB", - "credits-pricing-storage-tier-next-38gib-price": "$0.05 per GiB", - "credits-pricing-storage-tier-next-390gib": "Successivi 390 GiB", - "credits-pricing-storage-tier-next-390gib-price": "$0.03 per GiB", - "credits-pricing-storage-tier-next-5gib": "Successivi 5 GiB", - "credits-pricing-storage-tier-next-5gib-price": "$0.08 per GiB", - "credits-pricing-storage-tier-next-640gib": "Successivi 640 GiB", - "credits-pricing-storage-tier-next-640gib-price": "$0.025 per GiB", - "credits-pricing-storage-tier-over-1tb": "Oltre 1 TB", - "credits-pricing-storage-tier-over-1tb-price": "$0.021 per GiB", "credits-pricing-storage-title": "Archiviazione (GiB)", "credits-pricing-title": "Prezzi dei crediti", "credits-top-up-quantity-help": "Puoi modificare la quantità direttamente su Stripe durante il checkout.", diff --git a/messages/ja.json b/messages/ja.json index 45095ae83e..d794749546 100644 --- a/messages/ja.json +++ b/messages/ja.json @@ -590,75 +590,15 @@ "credits-only-info-title": "プランの代わりにクレジットを使う", "credits-pagination-label": "ページ {current} / {total}", "credits-pricing-bandwidth-subtitle": "プランに含まれる量を超えて配信されたGiBごと。", - "credits-pricing-bandwidth-tier-first": "最初の1 TB", - "credits-pricing-bandwidth-tier-first-price": "$0.12/GiB", - "credits-pricing-bandwidth-tier-next-13tb": "次の13 TB", - "credits-pricing-bandwidth-tier-next-13tb-price": "$0.055/GiB", - "credits-pricing-bandwidth-tier-next-1tb": "次の1 TB", - "credits-pricing-bandwidth-tier-next-1tb-price": "$0.10/GiB", - "credits-pricing-bandwidth-tier-next-38tb": "次の38 TB", - "credits-pricing-bandwidth-tier-next-38tb-price": "$0.04/GiB", - "credits-pricing-bandwidth-tier-next-4tb": "次の4 TB", - "credits-pricing-bandwidth-tier-next-4tb-price": "$0.085/GiB", - "credits-pricing-bandwidth-tier-next-64tb": "次の64 TB", - "credits-pricing-bandwidth-tier-next-64tb-price": "$0.03/GiB", - "credits-pricing-bandwidth-tier-next-6tb": "次の6 TB", - "credits-pricing-bandwidth-tier-next-6tb-price": "$0.07/GiB", - "credits-pricing-bandwidth-tier-over-128tb": "128 TB超", - "credits-pricing-bandwidth-tier-over-128tb-price": "$0.02/GiB", "credits-pricing-bandwidth-title": "帯域幅 (GiB)", "credits-pricing-build-subtitle": "プランに含まれる分を超えてビルドした時間の1分ごとに。", - "credits-pricing-build-tier-first-100": "最初の100分", - "credits-pricing-build-tier-first-100-price": "$0.16/分", - "credits-pricing-build-tier-next-400": "次の400分", - "credits-pricing-build-tier-next-400-price": "$0.14/分", - "credits-pricing-build-tier-next-4000": "次の4,000分", - "credits-pricing-build-tier-next-4000-price": "$0.10/分", - "credits-pricing-build-tier-next-500": "次の500分", - "credits-pricing-build-tier-next-500-price": "$0.12/分", - "credits-pricing-build-tier-next-5000": "次の5,000分", - "credits-pricing-build-tier-next-5000-price": "$0.09/分", - "credits-pricing-build-tier-over-10000": "10,000分以上", - "credits-pricing-build-tier-over-10000-price": "$0.08/分", "credits-pricing-build-title": "ビルド時間 (分)", "credits-pricing-description": "クレジットはプラン上限を超えた利用分をカバーします。必要な購入数の目安として以下の階層をご利用ください。", "credits-pricing-disclaimer": "クレジットはプランに含まれる上限を超えた利用を補います。クレジットは前払いで、12か月間有効です。", "credits-pricing-footnote": "* ストレージは GiB×時間で計算されます。", "credits-pricing-mau-subtitle": "月内に少なくとも1回チェックインする端末あたり。", - "credits-pricing-mau-tier-first": "最初の100万", - "credits-pricing-mau-tier-first-price": "$0.003/MAU", - "credits-pricing-mau-tier-next-10m": "次の1,000万", - "credits-pricing-mau-tier-next-10m-price": "$0.0011/MAU", - "credits-pricing-mau-tier-next-15m": "次の1,500万", - "credits-pricing-mau-tier-next-15m-price": "$0.001/MAU", - "credits-pricing-mau-tier-next-2m": "次の200万", - "credits-pricing-mau-tier-next-2m-price": "$0.0022/MAU", - "credits-pricing-mau-tier-next-5m": "次の500万", - "credits-pricing-mau-tier-next-5m-price": "$0.0014/MAU", - "credits-pricing-mau-tier-next-60m": "次の6,000万", - "credits-pricing-mau-tier-next-60m-price": "$0.0009/MAU", - "credits-pricing-mau-tier-next-7m": "次の700万", - "credits-pricing-mau-tier-next-7m-price": "$0.0016/MAU", - "credits-pricing-mau-tier-over-100m": "1億超", - "credits-pricing-mau-tier-over-100m-price": "$0.0007/MAU", "credits-pricing-mau-title": "月間アクティブユーザー(MAU)", "credits-pricing-storage-subtitle": "リリースを保存するために使用されるGiBごと。", - "credits-pricing-storage-tier-first": "最初の1 GiB", - "credits-pricing-storage-tier-first-price": "$0.09/GiB", - "credits-pricing-storage-tier-next-187gib": "次の187 GiB", - "credits-pricing-storage-tier-next-187gib-price": "$0.04/GiB", - "credits-pricing-storage-tier-next-19gib": "次の19 GiB", - "credits-pricing-storage-tier-next-19gib-price": "$0.065/GiB", - "credits-pricing-storage-tier-next-38gib": "次の38 GiB", - "credits-pricing-storage-tier-next-38gib-price": "$0.05/GiB", - "credits-pricing-storage-tier-next-390gib": "次の390 GiB", - "credits-pricing-storage-tier-next-390gib-price": "$0.03/GiB", - "credits-pricing-storage-tier-next-5gib": "次の5 GiB", - "credits-pricing-storage-tier-next-5gib-price": "$0.08/GiB", - "credits-pricing-storage-tier-next-640gib": "次の640 GiB", - "credits-pricing-storage-tier-next-640gib-price": "$0.025/GiB", - "credits-pricing-storage-tier-over-1tb": "1 TB超", - "credits-pricing-storage-tier-over-1tb-price": "$0.021/GiB", "credits-pricing-storage-title": "ストレージ (GiB)", "credits-pricing-title": "クレジット料金", "credits-top-up-quantity-help": "決済時にStripe上で数量を直接調整できます。", diff --git a/messages/ko.json b/messages/ko.json index d4871c7103..45ea99f2e2 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -590,75 +590,15 @@ "credits-only-info-title": "요금제 대신 크레딧 사용", "credits-pagination-label": "페이지 {current} / {total}", "credits-pricing-bandwidth-subtitle": "요금제 포함량을 초과해 전송된 GiB 기준입니다.", - "credits-pricing-bandwidth-tier-first": "처음 1 TB", - "credits-pricing-bandwidth-tier-first-price": "$0.12 (GiB당)", - "credits-pricing-bandwidth-tier-next-13tb": "다음 13 TB", - "credits-pricing-bandwidth-tier-next-13tb-price": "$0.055 (GiB당)", - "credits-pricing-bandwidth-tier-next-1tb": "다음 1 TB", - "credits-pricing-bandwidth-tier-next-1tb-price": "$0.10 (GiB당)", - "credits-pricing-bandwidth-tier-next-38tb": "다음 38 TB", - "credits-pricing-bandwidth-tier-next-38tb-price": "$0.04 (GiB당)", - "credits-pricing-bandwidth-tier-next-4tb": "다음 4 TB", - "credits-pricing-bandwidth-tier-next-4tb-price": "$0.085 (GiB당)", - "credits-pricing-bandwidth-tier-next-64tb": "다음 64 TB", - "credits-pricing-bandwidth-tier-next-64tb-price": "$0.03 (GiB당)", - "credits-pricing-bandwidth-tier-next-6tb": "다음 6 TB", - "credits-pricing-bandwidth-tier-next-6tb-price": "$0.07 (GiB당)", - "credits-pricing-bandwidth-tier-over-128tb": "128 TB 초과", - "credits-pricing-bandwidth-tier-over-128tb-price": "$0.02 (GiB당)", "credits-pricing-bandwidth-title": "대역폭 (GiB)", "credits-pricing-build-subtitle": "요금제에 포함된 분량을 넘는 빌드 시간의 분당 요금입니다.", - "credits-pricing-build-tier-first-100": "처음 100분", - "credits-pricing-build-tier-first-100-price": "$0.16 (분당)", - "credits-pricing-build-tier-next-400": "다음 400분", - "credits-pricing-build-tier-next-400-price": "$0.14 (분당)", - "credits-pricing-build-tier-next-4000": "다음 4,000분", - "credits-pricing-build-tier-next-4000-price": "$0.10 (분당)", - "credits-pricing-build-tier-next-500": "다음 500분", - "credits-pricing-build-tier-next-500-price": "$0.12 (분당)", - "credits-pricing-build-tier-next-5000": "다음 5,000분", - "credits-pricing-build-tier-next-5000-price": "$0.09 (분당)", - "credits-pricing-build-tier-over-10000": "10,000분 초과", - "credits-pricing-build-tier-over-10000-price": "$0.08 (분당)", "credits-pricing-build-title": "빌드 시간 (분)", "credits-pricing-description": "크레딧은 요금제 한도를 초과한 사용량을 보완합니다. 아래 구간을 참고해 구매할 수량을 가늠하세요.", "credits-pricing-disclaimer": "크레딧은 요금제에 포함된 한도를 초과한 사용량을 보완합니다. 크레딧은 선불이며 12개월 동안 유효합니다.", "credits-pricing-footnote": "* 스토리지는 GiB·시간 기준으로 계산됩니다.", "credits-pricing-mau-subtitle": "월 동안 최소 한 번 체크인한 기기 기준입니다.", - "credits-pricing-mau-tier-first": "처음 100만", - "credits-pricing-mau-tier-first-price": "$0.003 (MAU당)", - "credits-pricing-mau-tier-next-10m": "다음 1,000만", - "credits-pricing-mau-tier-next-10m-price": "$0.0011 (MAU당)", - "credits-pricing-mau-tier-next-15m": "다음 1,500만", - "credits-pricing-mau-tier-next-15m-price": "$0.001 (MAU당)", - "credits-pricing-mau-tier-next-2m": "다음 200만", - "credits-pricing-mau-tier-next-2m-price": "$0.0022 (MAU당)", - "credits-pricing-mau-tier-next-5m": "다음 500만", - "credits-pricing-mau-tier-next-5m-price": "$0.0014 (MAU당)", - "credits-pricing-mau-tier-next-60m": "다음 6,000만", - "credits-pricing-mau-tier-next-60m-price": "$0.0009 (MAU당)", - "credits-pricing-mau-tier-next-7m": "다음 700만", - "credits-pricing-mau-tier-next-7m-price": "$0.0016 (MAU당)", - "credits-pricing-mau-tier-over-100m": "1억 초과", - "credits-pricing-mau-tier-over-100m-price": "$0.0007 (MAU당)", "credits-pricing-mau-title": "월간 활성 사용자(MAU)", "credits-pricing-storage-subtitle": "배포본이 차지하는 저장소 GiB 기준입니다.", - "credits-pricing-storage-tier-first": "처음 1 GiB", - "credits-pricing-storage-tier-first-price": "$0.09 (GiB당)", - "credits-pricing-storage-tier-next-187gib": "다음 187 GiB", - "credits-pricing-storage-tier-next-187gib-price": "$0.04 (GiB당)", - "credits-pricing-storage-tier-next-19gib": "다음 19 GiB", - "credits-pricing-storage-tier-next-19gib-price": "$0.065 (GiB당)", - "credits-pricing-storage-tier-next-38gib": "다음 38 GiB", - "credits-pricing-storage-tier-next-38gib-price": "$0.05 (GiB당)", - "credits-pricing-storage-tier-next-390gib": "다음 390 GiB", - "credits-pricing-storage-tier-next-390gib-price": "$0.03 (GiB당)", - "credits-pricing-storage-tier-next-5gib": "다음 5 GiB", - "credits-pricing-storage-tier-next-5gib-price": "$0.08 (GiB당)", - "credits-pricing-storage-tier-next-640gib": "다음 640 GiB", - "credits-pricing-storage-tier-next-640gib-price": "$0.025 (GiB당)", - "credits-pricing-storage-tier-over-1tb": "1 TB 초과", - "credits-pricing-storage-tier-over-1tb-price": "$0.021 (GiB당)", "credits-pricing-storage-title": "스토리지 (GiB)", "credits-pricing-title": "크레딧 요금", "credits-top-up-quantity-help": "결제 중 Stripe에서 수량을 바로 조정할 수 있습니다.", diff --git a/messages/pl.json b/messages/pl.json index 0fdbd67d7e..ebc394c138 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -590,75 +590,15 @@ "credits-only-info-title": "Korzystanie z kredytów zamiast planu", "credits-pagination-label": "Strona {current} / {total}", "credits-pricing-bandwidth-subtitle": "Za GiB dostarczony poza limitem planu.", - "credits-pricing-bandwidth-tier-first": "Pierwszy 1 TB", - "credits-pricing-bandwidth-tier-first-price": "$0.12 za GiB", - "credits-pricing-bandwidth-tier-next-13tb": "Kolejne 13 TB", - "credits-pricing-bandwidth-tier-next-13tb-price": "$0.055 za GiB", - "credits-pricing-bandwidth-tier-next-1tb": "Kolejny 1 TB", - "credits-pricing-bandwidth-tier-next-1tb-price": "$0.10 za GiB", - "credits-pricing-bandwidth-tier-next-38tb": "Kolejne 38 TB", - "credits-pricing-bandwidth-tier-next-38tb-price": "$0.04 za GiB", - "credits-pricing-bandwidth-tier-next-4tb": "Kolejne 4 TB", - "credits-pricing-bandwidth-tier-next-4tb-price": "$0.085 za GiB", - "credits-pricing-bandwidth-tier-next-64tb": "Kolejne 64 TB", - "credits-pricing-bandwidth-tier-next-64tb-price": "$0.03 za GiB", - "credits-pricing-bandwidth-tier-next-6tb": "Kolejne 6 TB", - "credits-pricing-bandwidth-tier-next-6tb-price": "$0.07 za GiB", - "credits-pricing-bandwidth-tier-over-128tb": "Powyżej 128 TB", - "credits-pricing-bandwidth-tier-over-128tb-price": "$0.02 za GiB", "credits-pricing-bandwidth-title": "Przepustowość (GiB)", "credits-pricing-build-subtitle": "Za każdą minutę budowania poza tym, co obejmuje Twój plan.", - "credits-pricing-build-tier-first-100": "Pierwsze 100 minut", - "credits-pricing-build-tier-first-100-price": "$0.16 za minutę", - "credits-pricing-build-tier-next-400": "Kolejne 400 minut", - "credits-pricing-build-tier-next-400-price": "$0.14 za minutę", - "credits-pricing-build-tier-next-4000": "Kolejne 4 000 minut", - "credits-pricing-build-tier-next-4000-price": "$0.10 za minutę", - "credits-pricing-build-tier-next-500": "Kolejne 500 minut", - "credits-pricing-build-tier-next-500-price": "$0.12 za minutę", - "credits-pricing-build-tier-next-5000": "Kolejne 5 000 minut", - "credits-pricing-build-tier-next-5000-price": "$0.09 za minutę", - "credits-pricing-build-tier-over-10000": "Powyżej 10 000 minut", - "credits-pricing-build-tier-over-10000-price": "$0.08 za minutę", "credits-pricing-build-title": "Czas budowania (minuty)", "credits-pricing-description": "Kredyty pokrywają zużycie przekraczające limity planu. Skorzystaj z poniższych progów, aby oszacować, ile należy kupić.", "credits-pricing-disclaimer": "Kredyty pokrywają zużycie poza limitami zawartymi w planie. Kredyty są opłacane z góry i ważne przez 12 miesięcy.", "credits-pricing-footnote": "* Przechowywanie liczone jest w GiB na godzinę.", "credits-pricing-mau-subtitle": "Na urządzenie, które zgłasza się co najmniej raz w miesiącu.", - "credits-pricing-mau-tier-first": "Pierwsze 1 mln", - "credits-pricing-mau-tier-first-price": "$0.003 za MAU", - "credits-pricing-mau-tier-next-10m": "Kolejne 10 mln", - "credits-pricing-mau-tier-next-10m-price": "$0.0011 za MAU", - "credits-pricing-mau-tier-next-15m": "Kolejne 15 mln", - "credits-pricing-mau-tier-next-15m-price": "$0.001 za MAU", - "credits-pricing-mau-tier-next-2m": "Kolejne 2 mln", - "credits-pricing-mau-tier-next-2m-price": "$0.0022 za MAU", - "credits-pricing-mau-tier-next-5m": "Kolejne 5 mln", - "credits-pricing-mau-tier-next-5m-price": "$0.0014 za MAU", - "credits-pricing-mau-tier-next-60m": "Kolejne 60 mln", - "credits-pricing-mau-tier-next-60m-price": "$0.0009 za MAU", - "credits-pricing-mau-tier-next-7m": "Kolejne 7 mln", - "credits-pricing-mau-tier-next-7m-price": "$0.0016 za MAU", - "credits-pricing-mau-tier-over-100m": "Powyżej 100 mln", - "credits-pricing-mau-tier-over-100m-price": "$0.0007 za MAU", "credits-pricing-mau-title": "Miesięcznie aktywni użytkownicy (MAU)", "credits-pricing-storage-subtitle": "Za GiB zajmujący miejsce w magazynie wydań.", - "credits-pricing-storage-tier-first": "Pierwszy 1 GiB", - "credits-pricing-storage-tier-first-price": "$0.09 za GiB", - "credits-pricing-storage-tier-next-187gib": "Kolejne 187 GiB", - "credits-pricing-storage-tier-next-187gib-price": "$0.04 za GiB", - "credits-pricing-storage-tier-next-19gib": "Kolejne 19 GiB", - "credits-pricing-storage-tier-next-19gib-price": "$0.065 za GiB", - "credits-pricing-storage-tier-next-38gib": "Kolejne 38 GiB", - "credits-pricing-storage-tier-next-38gib-price": "$0.05 za GiB", - "credits-pricing-storage-tier-next-390gib": "Kolejne 390 GiB", - "credits-pricing-storage-tier-next-390gib-price": "$0.03 za GiB", - "credits-pricing-storage-tier-next-5gib": "Kolejne 5 GiB", - "credits-pricing-storage-tier-next-5gib-price": "$0.08 za GiB", - "credits-pricing-storage-tier-next-640gib": "Kolejne 640 GiB", - "credits-pricing-storage-tier-next-640gib-price": "$0.025 za GiB", - "credits-pricing-storage-tier-over-1tb": "Powyżej 1 TB", - "credits-pricing-storage-tier-over-1tb-price": "$0.021 za GiB", "credits-pricing-storage-title": "Przechowywanie (GiB)", "credits-pricing-title": "Cennik kredytów", "credits-top-up-quantity-help": "Możesz dostosować ilość bezpośrednio w Stripe podczas płatności.", diff --git a/messages/pt-br.json b/messages/pt-br.json index 02d7aacb91..89408c0fe9 100644 --- a/messages/pt-br.json +++ b/messages/pt-br.json @@ -590,75 +590,15 @@ "credits-only-info-title": "Usar créditos em vez de um plano", "credits-pagination-label": "Página {current} / {total}", "credits-pricing-bandwidth-subtitle": "Por GiB entregue além do que está incluído no seu plano.", - "credits-pricing-bandwidth-tier-first": "Primeiro 1 TB", - "credits-pricing-bandwidth-tier-first-price": "$0.12 por GiB", - "credits-pricing-bandwidth-tier-next-13tb": "Próximos 13 TB", - "credits-pricing-bandwidth-tier-next-13tb-price": "$0.055 por GiB", - "credits-pricing-bandwidth-tier-next-1tb": "Próximo 1 TB", - "credits-pricing-bandwidth-tier-next-1tb-price": "$0.10 por GiB", - "credits-pricing-bandwidth-tier-next-38tb": "Próximos 38 TB", - "credits-pricing-bandwidth-tier-next-38tb-price": "$0.04 por GiB", - "credits-pricing-bandwidth-tier-next-4tb": "Próximos 4 TB", - "credits-pricing-bandwidth-tier-next-4tb-price": "$0.085 por GiB", - "credits-pricing-bandwidth-tier-next-64tb": "Próximos 64 TB", - "credits-pricing-bandwidth-tier-next-64tb-price": "$0.03 por GiB", - "credits-pricing-bandwidth-tier-next-6tb": "Próximos 6 TB", - "credits-pricing-bandwidth-tier-next-6tb-price": "$0.07 por GiB", - "credits-pricing-bandwidth-tier-over-128tb": "Acima de 128 TB", - "credits-pricing-bandwidth-tier-over-128tb-price": "$0.02 por GiB", "credits-pricing-bandwidth-title": "Largura de banda (GiB)", "credits-pricing-build-subtitle": "Por minuto gasto em build além do que está incluído no seu plano.", - "credits-pricing-build-tier-first-100": "Primeiros 100 minutos", - "credits-pricing-build-tier-first-100-price": "$0.16 por minuto", - "credits-pricing-build-tier-next-400": "Próximos 400 minutos", - "credits-pricing-build-tier-next-400-price": "$0.14 por minuto", - "credits-pricing-build-tier-next-4000": "Próximos 4.000 minutos", - "credits-pricing-build-tier-next-4000-price": "$0.10 por minuto", - "credits-pricing-build-tier-next-500": "Próximos 500 minutos", - "credits-pricing-build-tier-next-500-price": "$0.12 por minuto", - "credits-pricing-build-tier-next-5000": "Próximos 5.000 minutos", - "credits-pricing-build-tier-next-5000-price": "$0.09 por minuto", - "credits-pricing-build-tier-over-10000": "Acima de 10.000 minutos", - "credits-pricing-build-tier-over-10000-price": "$0.08 por minuto", "credits-pricing-build-title": "Tempo de build (minutos)", "credits-pricing-description": "Os créditos cobrem o uso além dos limites do seu plano. Use estes níveis para estimar quantos comprar.", "credits-pricing-disclaimer": "Os créditos cobrem o uso além dos limites incluídos no plano. Os créditos são pré-pagos e permanecem válidos por 12 meses.", "credits-pricing-footnote": "* O armazenamento é calculado por GiB por hora.", "credits-pricing-mau-subtitle": "Por dispositivo que se conecta pelo menos uma vez durante o mês.", - "credits-pricing-mau-tier-first": "Primeiros 1 M", - "credits-pricing-mau-tier-first-price": "$0.003 por MAU", - "credits-pricing-mau-tier-next-10m": "Próximos 10 M", - "credits-pricing-mau-tier-next-10m-price": "$0.0011 por MAU", - "credits-pricing-mau-tier-next-15m": "Próximos 15 M", - "credits-pricing-mau-tier-next-15m-price": "$0.001 por MAU", - "credits-pricing-mau-tier-next-2m": "Próximos 2 M", - "credits-pricing-mau-tier-next-2m-price": "$0.0022 por MAU", - "credits-pricing-mau-tier-next-5m": "Próximos 5 M", - "credits-pricing-mau-tier-next-5m-price": "$0.0014 por MAU", - "credits-pricing-mau-tier-next-60m": "Próximos 60 M", - "credits-pricing-mau-tier-next-60m-price": "$0.0009 por MAU", - "credits-pricing-mau-tier-next-7m": "Próximos 7 M", - "credits-pricing-mau-tier-next-7m-price": "$0.0016 por MAU", - "credits-pricing-mau-tier-over-100m": "Acima de 100 M", - "credits-pricing-mau-tier-over-100m-price": "$0.0007 por MAU", "credits-pricing-mau-title": "Usuários ativos mensais (MAU)", "credits-pricing-storage-subtitle": "Por GiB ocupando armazenamento para suas publicações.", - "credits-pricing-storage-tier-first": "Primeiro 1 GiB", - "credits-pricing-storage-tier-first-price": "$0.09 por GiB", - "credits-pricing-storage-tier-next-187gib": "Próximos 187 GiB", - "credits-pricing-storage-tier-next-187gib-price": "$0.04 por GiB", - "credits-pricing-storage-tier-next-19gib": "Próximos 19 GiB", - "credits-pricing-storage-tier-next-19gib-price": "$0.065 por GiB", - "credits-pricing-storage-tier-next-38gib": "Próximos 38 GiB", - "credits-pricing-storage-tier-next-38gib-price": "$0.05 por GiB", - "credits-pricing-storage-tier-next-390gib": "Próximos 390 GiB", - "credits-pricing-storage-tier-next-390gib-price": "$0.03 por GiB", - "credits-pricing-storage-tier-next-5gib": "Próximos 5 GiB", - "credits-pricing-storage-tier-next-5gib-price": "$0.08 por GiB", - "credits-pricing-storage-tier-next-640gib": "Próximos 640 GiB", - "credits-pricing-storage-tier-next-640gib-price": "$0.025 por GiB", - "credits-pricing-storage-tier-over-1tb": "Acima de 1 TB", - "credits-pricing-storage-tier-over-1tb-price": "$0.021 por GiB", "credits-pricing-storage-title": "Armazenamento (GiB)", "credits-pricing-title": "Preços de créditos", "credits-top-up-quantity-help": "Você pode ajustar a quantidade diretamente no Stripe durante o checkout.", diff --git a/messages/ru.json b/messages/ru.json index 5335ee11e6..3e6fbfa566 100644 --- a/messages/ru.json +++ b/messages/ru.json @@ -590,75 +590,15 @@ "credits-only-info-title": "Использование кредитов вместо плана", "credits-pagination-label": "Страница {current} / {total}", "credits-pricing-bandwidth-subtitle": "За GiB, переданный сверх объема, включенного в тариф.", - "credits-pricing-bandwidth-tier-first": "Первые 1 ТБ", - "credits-pricing-bandwidth-tier-first-price": "$0.12 за GiB", - "credits-pricing-bandwidth-tier-next-13tb": "Следующие 13 ТБ", - "credits-pricing-bandwidth-tier-next-13tb-price": "$0.055 за GiB", - "credits-pricing-bandwidth-tier-next-1tb": "Следующий 1 ТБ", - "credits-pricing-bandwidth-tier-next-1tb-price": "$0.10 за GiB", - "credits-pricing-bandwidth-tier-next-38tb": "Следующие 38 ТБ", - "credits-pricing-bandwidth-tier-next-38tb-price": "$0.04 за GiB", - "credits-pricing-bandwidth-tier-next-4tb": "Следующие 4 ТБ", - "credits-pricing-bandwidth-tier-next-4tb-price": "$0.085 за GiB", - "credits-pricing-bandwidth-tier-next-64tb": "Следующие 64 ТБ", - "credits-pricing-bandwidth-tier-next-64tb-price": "$0.03 за GiB", - "credits-pricing-bandwidth-tier-next-6tb": "Следующие 6 ТБ", - "credits-pricing-bandwidth-tier-next-6tb-price": "$0.07 за GiB", - "credits-pricing-bandwidth-tier-over-128tb": "Свыше 128 ТБ", - "credits-pricing-bandwidth-tier-over-128tb-price": "$0.02 за GiB", "credits-pricing-bandwidth-title": "Пропускная способность (GiB)", "credits-pricing-build-subtitle": "За каждую минуту сборки сверх того, что включено в ваш тариф.", - "credits-pricing-build-tier-first-100": "Первые 100 минут", - "credits-pricing-build-tier-first-100-price": "$0.16 за минуту", - "credits-pricing-build-tier-next-400": "Следующие 400 минут", - "credits-pricing-build-tier-next-400-price": "$0.14 за минуту", - "credits-pricing-build-tier-next-4000": "Следующие 4 000 минут", - "credits-pricing-build-tier-next-4000-price": "$0.10 за минуту", - "credits-pricing-build-tier-next-500": "Следующие 500 минут", - "credits-pricing-build-tier-next-500-price": "$0.12 за минуту", - "credits-pricing-build-tier-next-5000": "Следующие 5 000 минут", - "credits-pricing-build-tier-next-5000-price": "$0.09 за минуту", - "credits-pricing-build-tier-over-10000": "Свыше 10 000 минут", - "credits-pricing-build-tier-over-10000-price": "$0.08 за минуту", "credits-pricing-build-title": "Время сборки (минуты)", "credits-pricing-description": "Кредиты покрывают использование сверх лимитов вашего тарифа. Используйте эти уровни, чтобы оценить, сколько необходимо приобрести.", "credits-pricing-disclaimer": "Кредиты покрывают использование сверх лимитов, включенных в тариф. Кредиты оплачиваются заранее и действуют 12 месяцев.", "credits-pricing-footnote": "* Хранилище рассчитывается по GiB за час.", "credits-pricing-mau-subtitle": "За устройство, которое подключается хотя бы раз в месяц.", - "credits-pricing-mau-tier-first": "Первые 1 млн", - "credits-pricing-mau-tier-first-price": "$0.003 за MAU", - "credits-pricing-mau-tier-next-10m": "Следующие 10 млн", - "credits-pricing-mau-tier-next-10m-price": "$0.0011 за MAU", - "credits-pricing-mau-tier-next-15m": "Следующие 15 млн", - "credits-pricing-mau-tier-next-15m-price": "$0.001 за MAU", - "credits-pricing-mau-tier-next-2m": "Следующие 2 млн", - "credits-pricing-mau-tier-next-2m-price": "$0.0022 за MAU", - "credits-pricing-mau-tier-next-5m": "Следующие 5 млн", - "credits-pricing-mau-tier-next-5m-price": "$0.0014 за MAU", - "credits-pricing-mau-tier-next-60m": "Следующие 60 млн", - "credits-pricing-mau-tier-next-60m-price": "$0.0009 за MAU", - "credits-pricing-mau-tier-next-7m": "Следующие 7 млн", - "credits-pricing-mau-tier-next-7m-price": "$0.0016 за MAU", - "credits-pricing-mau-tier-over-100m": "Свыше 100 млн", - "credits-pricing-mau-tier-over-100m-price": "$0.0007 за MAU", "credits-pricing-mau-title": "Ежемесячно активные пользователи (MAU)", "credits-pricing-storage-subtitle": "За GiB, занимающий место в хранилище ваших релизов.", - "credits-pricing-storage-tier-first": "Первые 1 GiB", - "credits-pricing-storage-tier-first-price": "$0.09 за GiB", - "credits-pricing-storage-tier-next-187gib": "Следующие 187 GiB", - "credits-pricing-storage-tier-next-187gib-price": "$0.04 за GiB", - "credits-pricing-storage-tier-next-19gib": "Следующие 19 GiB", - "credits-pricing-storage-tier-next-19gib-price": "$0.065 за GiB", - "credits-pricing-storage-tier-next-38gib": "Следующие 38 GiB", - "credits-pricing-storage-tier-next-38gib-price": "$0.05 за GiB", - "credits-pricing-storage-tier-next-390gib": "Следующие 390 GiB", - "credits-pricing-storage-tier-next-390gib-price": "$0.03 за GiB", - "credits-pricing-storage-tier-next-5gib": "Следующие 5 GiB", - "credits-pricing-storage-tier-next-5gib-price": "$0.08 за GiB", - "credits-pricing-storage-tier-next-640gib": "Следующие 640 GiB", - "credits-pricing-storage-tier-next-640gib-price": "$0.025 за GiB", - "credits-pricing-storage-tier-over-1tb": "Свыше 1 ТБ", - "credits-pricing-storage-tier-over-1tb-price": "$0.021 за GiB", "credits-pricing-storage-title": "Хранилище (GiB)", "credits-pricing-title": "Цены на кредиты", "credits-top-up-quantity-help": "Вы можете изменить количество прямо в Stripe во время оплаты.", diff --git a/messages/tr.json b/messages/tr.json index b48648e290..b25251b271 100644 --- a/messages/tr.json +++ b/messages/tr.json @@ -590,75 +590,15 @@ "credits-only-info-title": "Plan yerine kredi kullanma", "credits-pagination-label": "Sayfa {current} / {total}", "credits-pricing-bandwidth-subtitle": "Planınıza dahil olanın ötesinde teslim edilen GiB başına.", - "credits-pricing-bandwidth-tier-first": "İlk 1 TB", - "credits-pricing-bandwidth-tier-first-price": "$0.12 GiB başına", - "credits-pricing-bandwidth-tier-next-13tb": "Sonraki 13 TB", - "credits-pricing-bandwidth-tier-next-13tb-price": "$0.055 GiB başına", - "credits-pricing-bandwidth-tier-next-1tb": "Sonraki 1 TB", - "credits-pricing-bandwidth-tier-next-1tb-price": "$0.10 GiB başına", - "credits-pricing-bandwidth-tier-next-38tb": "Sonraki 38 TB", - "credits-pricing-bandwidth-tier-next-38tb-price": "$0.04 GiB başına", - "credits-pricing-bandwidth-tier-next-4tb": "Sonraki 4 TB", - "credits-pricing-bandwidth-tier-next-4tb-price": "$0.085 GiB başına", - "credits-pricing-bandwidth-tier-next-64tb": "Sonraki 64 TB", - "credits-pricing-bandwidth-tier-next-64tb-price": "$0.03 GiB başına", - "credits-pricing-bandwidth-tier-next-6tb": "Sonraki 6 TB", - "credits-pricing-bandwidth-tier-next-6tb-price": "$0.07 GiB başına", - "credits-pricing-bandwidth-tier-over-128tb": "128 TB üzeri", - "credits-pricing-bandwidth-tier-over-128tb-price": "$0.02 GiB başına", "credits-pricing-bandwidth-title": "Bant genişliği (GiB)", "credits-pricing-build-subtitle": "Planınıza dahil olanın ötesinde yapılan build süresi için dakika başına.", - "credits-pricing-build-tier-first-100": "İlk 100 dakika", - "credits-pricing-build-tier-first-100-price": "$0.16 dakika başına", - "credits-pricing-build-tier-next-400": "Sonraki 400 dakika", - "credits-pricing-build-tier-next-400-price": "$0.14 dakika başına", - "credits-pricing-build-tier-next-4000": "Sonraki 4.000 dakika", - "credits-pricing-build-tier-next-4000-price": "$0.10 dakika başına", - "credits-pricing-build-tier-next-500": "Sonraki 500 dakika", - "credits-pricing-build-tier-next-500-price": "$0.12 dakika başına", - "credits-pricing-build-tier-next-5000": "Sonraki 5.000 dakika", - "credits-pricing-build-tier-next-5000-price": "$0.09 dakika başına", - "credits-pricing-build-tier-over-10000": "10.000 dakikanın üzerinde", - "credits-pricing-build-tier-over-10000-price": "$0.08 dakika başına", "credits-pricing-build-title": "Build süresi (dakika)", "credits-pricing-description": "Krediler, plan limitinizi aşan kullanımı karşılar. Kaç adet almanız gerektiğini tahmin etmek için bu kademeleri kullanın.", "credits-pricing-disclaimer": "Krediler, plana dahil edilen limitlerin üzerindeki kullanımı karşılar. Krediler peşin ödenir ve 12 ay boyunca geçerlidir.", "credits-pricing-footnote": "* Depolama, GiB başına saat olarak hesaplanır.", "credits-pricing-mau-subtitle": "Ay içinde en az bir kez oturum açan cihaz başına.", - "credits-pricing-mau-tier-first": "İlk 1 M", - "credits-pricing-mau-tier-first-price": "$0.003 MAU başına", - "credits-pricing-mau-tier-next-10m": "Sonraki 10 M", - "credits-pricing-mau-tier-next-10m-price": "$0.0011 MAU başına", - "credits-pricing-mau-tier-next-15m": "Sonraki 15 M", - "credits-pricing-mau-tier-next-15m-price": "$0.001 MAU başına", - "credits-pricing-mau-tier-next-2m": "Sonraki 2 M", - "credits-pricing-mau-tier-next-2m-price": "$0.0022 MAU başına", - "credits-pricing-mau-tier-next-5m": "Sonraki 5 M", - "credits-pricing-mau-tier-next-5m-price": "$0.0014 MAU başına", - "credits-pricing-mau-tier-next-60m": "Sonraki 60 M", - "credits-pricing-mau-tier-next-60m-price": "$0.0009 MAU başına", - "credits-pricing-mau-tier-next-7m": "Sonraki 7 M", - "credits-pricing-mau-tier-next-7m-price": "$0.0016 MAU başına", - "credits-pricing-mau-tier-over-100m": "100 M üzeri", - "credits-pricing-mau-tier-over-100m-price": "$0.0007 MAU başına", "credits-pricing-mau-title": "Aylık aktif kullanıcılar (MAU)", "credits-pricing-storage-subtitle": "Yayınlarınız için depolamayı kullanan GiB başına.", - "credits-pricing-storage-tier-first": "İlk 1 GiB", - "credits-pricing-storage-tier-first-price": "$0.09 GiB başına", - "credits-pricing-storage-tier-next-187gib": "Sonraki 187 GiB", - "credits-pricing-storage-tier-next-187gib-price": "$0.04 GiB başına", - "credits-pricing-storage-tier-next-19gib": "Sonraki 19 GiB", - "credits-pricing-storage-tier-next-19gib-price": "$0.065 GiB başına", - "credits-pricing-storage-tier-next-38gib": "Sonraki 38 GiB", - "credits-pricing-storage-tier-next-38gib-price": "$0.05 GiB başına", - "credits-pricing-storage-tier-next-390gib": "Sonraki 390 GiB", - "credits-pricing-storage-tier-next-390gib-price": "$0.03 GiB başına", - "credits-pricing-storage-tier-next-5gib": "Sonraki 5 GiB", - "credits-pricing-storage-tier-next-5gib-price": "$0.08 GiB başına", - "credits-pricing-storage-tier-next-640gib": "Sonraki 640 GiB", - "credits-pricing-storage-tier-next-640gib-price": "$0.025 GiB başına", - "credits-pricing-storage-tier-over-1tb": "1 TB üzeri", - "credits-pricing-storage-tier-over-1tb-price": "$0.021 GiB başına", "credits-pricing-storage-title": "Depolama (GiB)", "credits-pricing-title": "Kredi fiyatlandırması", "credits-top-up-quantity-help": "Ödeme sırasında Stripe üzerinden miktarı doğrudan ayarlayabilirsiniz.", diff --git a/messages/vi.json b/messages/vi.json index 272407c016..3e19f514e9 100644 --- a/messages/vi.json +++ b/messages/vi.json @@ -590,75 +590,15 @@ "credits-only-info-title": "Sử dụng tín dụng thay vì gói cước", "credits-pagination-label": "Trang {current} / {total}", "credits-pricing-bandwidth-subtitle": "Cho mỗi GiB phân phối vượt ngoài gói của bạn.", - "credits-pricing-bandwidth-tier-first": "1 TB đầu tiên", - "credits-pricing-bandwidth-tier-first-price": "$0.12 mỗi GiB", - "credits-pricing-bandwidth-tier-next-13tb": "13 TB tiếp theo", - "credits-pricing-bandwidth-tier-next-13tb-price": "$0.055 mỗi GiB", - "credits-pricing-bandwidth-tier-next-1tb": "1 TB tiếp theo", - "credits-pricing-bandwidth-tier-next-1tb-price": "$0.10 mỗi GiB", - "credits-pricing-bandwidth-tier-next-38tb": "38 TB tiếp theo", - "credits-pricing-bandwidth-tier-next-38tb-price": "$0.04 mỗi GiB", - "credits-pricing-bandwidth-tier-next-4tb": "4 TB tiếp theo", - "credits-pricing-bandwidth-tier-next-4tb-price": "$0.085 mỗi GiB", - "credits-pricing-bandwidth-tier-next-64tb": "64 TB tiếp theo", - "credits-pricing-bandwidth-tier-next-64tb-price": "$0.03 mỗi GiB", - "credits-pricing-bandwidth-tier-next-6tb": "6 TB tiếp theo", - "credits-pricing-bandwidth-tier-next-6tb-price": "$0.07 mỗi GiB", - "credits-pricing-bandwidth-tier-over-128tb": "Trên 128 TB", - "credits-pricing-bandwidth-tier-over-128tb-price": "$0.02 mỗi GiB", "credits-pricing-bandwidth-title": "Băng thông (GiB)", "credits-pricing-build-subtitle": "Tính cho mỗi phút build vượt quá những gì gói của bạn bao gồm.", - "credits-pricing-build-tier-first-100": "100 phút đầu tiên", - "credits-pricing-build-tier-first-100-price": "$0.16 mỗi phút", - "credits-pricing-build-tier-next-400": "400 phút tiếp theo", - "credits-pricing-build-tier-next-400-price": "$0.14 mỗi phút", - "credits-pricing-build-tier-next-4000": "4.000 phút tiếp theo", - "credits-pricing-build-tier-next-4000-price": "$0.10 mỗi phút", - "credits-pricing-build-tier-next-500": "500 phút tiếp theo", - "credits-pricing-build-tier-next-500-price": "$0.12 mỗi phút", - "credits-pricing-build-tier-next-5000": "5.000 phút tiếp theo", - "credits-pricing-build-tier-next-5000-price": "$0.09 mỗi phút", - "credits-pricing-build-tier-over-10000": "Trên 10.000 phút", - "credits-pricing-build-tier-over-10000-price": "$0.08 mỗi phút", "credits-pricing-build-title": "Thời gian build (phút)", "credits-pricing-description": "Tín dụng dùng để chi trả phần sử dụng vượt giới hạn gói. Hãy dùng các bậc dưới đây để ước lượng số lượng cần mua.", "credits-pricing-disclaimer": "Tín dụng chi trả cho phần sử dụng vượt giới hạn gói. Tín dụng được trả trước và có hiệu lực trong 12 tháng.", "credits-pricing-footnote": "* Lưu trữ được tính theo GiB mỗi giờ.", "credits-pricing-mau-subtitle": "Tính cho mỗi thiết bị đăng ký ít nhất một lần trong tháng.", - "credits-pricing-mau-tier-first": "1 triệu đầu tiên", - "credits-pricing-mau-tier-first-price": "$0.003 mỗi MAU", - "credits-pricing-mau-tier-next-10m": "10 triệu tiếp theo", - "credits-pricing-mau-tier-next-10m-price": "$0.0011 mỗi MAU", - "credits-pricing-mau-tier-next-15m": "15 triệu tiếp theo", - "credits-pricing-mau-tier-next-15m-price": "$0.001 mỗi MAU", - "credits-pricing-mau-tier-next-2m": "2 triệu tiếp theo", - "credits-pricing-mau-tier-next-2m-price": "$0.0022 mỗi MAU", - "credits-pricing-mau-tier-next-5m": "5 triệu tiếp theo", - "credits-pricing-mau-tier-next-5m-price": "$0.0014 mỗi MAU", - "credits-pricing-mau-tier-next-60m": "60 triệu tiếp theo", - "credits-pricing-mau-tier-next-60m-price": "$0.0009 mỗi MAU", - "credits-pricing-mau-tier-next-7m": "7 triệu tiếp theo", - "credits-pricing-mau-tier-next-7m-price": "$0.0016 mỗi MAU", - "credits-pricing-mau-tier-over-100m": "Trên 100 triệu", - "credits-pricing-mau-tier-over-100m-price": "$0.0007 mỗi MAU", "credits-pricing-mau-title": "Người dùng hoạt động hàng tháng (MAU)", "credits-pricing-storage-subtitle": "Cho mỗi GiB chiếm dung lượng lưu trữ cho các bản phát hành của bạn.", - "credits-pricing-storage-tier-first": "1 GiB đầu tiên", - "credits-pricing-storage-tier-first-price": "$0.09 mỗi GiB", - "credits-pricing-storage-tier-next-187gib": "187 GiB tiếp theo", - "credits-pricing-storage-tier-next-187gib-price": "$0.04 mỗi GiB", - "credits-pricing-storage-tier-next-19gib": "19 GiB tiếp theo", - "credits-pricing-storage-tier-next-19gib-price": "$0.065 mỗi GiB", - "credits-pricing-storage-tier-next-38gib": "38 GiB tiếp theo", - "credits-pricing-storage-tier-next-38gib-price": "$0.05 mỗi GiB", - "credits-pricing-storage-tier-next-390gib": "390 GiB tiếp theo", - "credits-pricing-storage-tier-next-390gib-price": "$0.03 mỗi GiB", - "credits-pricing-storage-tier-next-5gib": "5 GiB tiếp theo", - "credits-pricing-storage-tier-next-5gib-price": "$0.08 mỗi GiB", - "credits-pricing-storage-tier-next-640gib": "640 GiB tiếp theo", - "credits-pricing-storage-tier-next-640gib-price": "$0.025 mỗi GiB", - "credits-pricing-storage-tier-over-1tb": "Trên 1 TB", - "credits-pricing-storage-tier-over-1tb-price": "$0.021 mỗi GiB", "credits-pricing-storage-title": "Lưu trữ (GiB)", "credits-pricing-title": "Bảng giá tín dụng", "credits-top-up-quantity-help": "Bạn có thể điều chỉnh số lượng trực tiếp trên Stripe khi thanh toán.", diff --git a/messages/zh-cn.json b/messages/zh-cn.json index 3b788d65c6..94f46a2f99 100644 --- a/messages/zh-cn.json +++ b/messages/zh-cn.json @@ -590,75 +590,15 @@ "credits-only-info-title": "用积分代替计划", "credits-pagination-label": "第 {current} / {total} 页", "credits-pricing-bandwidth-subtitle": "对超出套餐包含范围的每 GiB 计费。", - "credits-pricing-bandwidth-tier-first": "前 1 TB", - "credits-pricing-bandwidth-tier-first-price": "$0.12 每 GiB", - "credits-pricing-bandwidth-tier-next-13tb": "接下来的 13 TB", - "credits-pricing-bandwidth-tier-next-13tb-price": "$0.055 每 GiB", - "credits-pricing-bandwidth-tier-next-1tb": "接下来的 1 TB", - "credits-pricing-bandwidth-tier-next-1tb-price": "$0.10 每 GiB", - "credits-pricing-bandwidth-tier-next-38tb": "接下来的 38 TB", - "credits-pricing-bandwidth-tier-next-38tb-price": "$0.04 每 GiB", - "credits-pricing-bandwidth-tier-next-4tb": "接下来的 4 TB", - "credits-pricing-bandwidth-tier-next-4tb-price": "$0.085 每 GiB", - "credits-pricing-bandwidth-tier-next-64tb": "接下来的 64 TB", - "credits-pricing-bandwidth-tier-next-64tb-price": "$0.03 每 GiB", - "credits-pricing-bandwidth-tier-next-6tb": "接下来的 6 TB", - "credits-pricing-bandwidth-tier-next-6tb-price": "$0.07 每 GiB", - "credits-pricing-bandwidth-tier-over-128tb": "超过 128 TB", - "credits-pricing-bandwidth-tier-over-128tb-price": "$0.02 每 GiB", "credits-pricing-bandwidth-title": "带宽 (GiB)", "credits-pricing-build-subtitle": "超出套餐包含部分的构建时间按分钟计费。", - "credits-pricing-build-tier-first-100": "前 100 分钟", - "credits-pricing-build-tier-first-100-price": "$0.16 每分钟", - "credits-pricing-build-tier-next-400": "接下来的 400 分钟", - "credits-pricing-build-tier-next-400-price": "$0.14 每分钟", - "credits-pricing-build-tier-next-4000": "接下来的 4,000 分钟", - "credits-pricing-build-tier-next-4000-price": "$0.10 每分钟", - "credits-pricing-build-tier-next-500": "接下来的 500 分钟", - "credits-pricing-build-tier-next-500-price": "$0.12 每分钟", - "credits-pricing-build-tier-next-5000": "接下来的 5,000 分钟", - "credits-pricing-build-tier-next-5000-price": "$0.09 每分钟", - "credits-pricing-build-tier-over-10000": "超过 10,000 分钟", - "credits-pricing-build-tier-over-10000-price": "$0.08 每分钟", "credits-pricing-build-title": "构建时间(分钟)", "credits-pricing-description": "積分可覆盖超出套餐限制的用量。使用以下分级来估算需要购买的数量。", "credits-pricing-disclaimer": "積分用于覆盖超出套餐包含限制的用量。積分需预付并在 12 个月内有效。", "credits-pricing-footnote": "* 存储按每 GiB 每小时计算。", "credits-pricing-mau-subtitle": "每台设备在当月至少连接一次。", - "credits-pricing-mau-tier-first": "前 100 万", - "credits-pricing-mau-tier-first-price": "$0.003 每 MAU", - "credits-pricing-mau-tier-next-10m": "接下来的 1000 万", - "credits-pricing-mau-tier-next-10m-price": "$0.0011 每 MAU", - "credits-pricing-mau-tier-next-15m": "接下来的 1500 万", - "credits-pricing-mau-tier-next-15m-price": "$0.001 每 MAU", - "credits-pricing-mau-tier-next-2m": "接下来的 200 万", - "credits-pricing-mau-tier-next-2m-price": "$0.0022 每 MAU", - "credits-pricing-mau-tier-next-5m": "接下来的 500 万", - "credits-pricing-mau-tier-next-5m-price": "$0.0014 每 MAU", - "credits-pricing-mau-tier-next-60m": "接下来的 6000 万", - "credits-pricing-mau-tier-next-60m-price": "$0.0009 每 MAU", - "credits-pricing-mau-tier-next-7m": "接下来的 700 万", - "credits-pricing-mau-tier-next-7m-price": "$0.0016 每 MAU", - "credits-pricing-mau-tier-over-100m": "超过 1 亿", - "credits-pricing-mau-tier-over-100m-price": "$0.0007 每 MAU", "credits-pricing-mau-title": "月活跃用户(MAU)", "credits-pricing-storage-subtitle": "对你的发布所占用的每 GiB 存储计费。", - "credits-pricing-storage-tier-first": "前 1 GiB", - "credits-pricing-storage-tier-first-price": "$0.09 每 GiB", - "credits-pricing-storage-tier-next-187gib": "接下来的 187 GiB", - "credits-pricing-storage-tier-next-187gib-price": "$0.04 每 GiB", - "credits-pricing-storage-tier-next-19gib": "接下来的 19 GiB", - "credits-pricing-storage-tier-next-19gib-price": "$0.065 每 GiB", - "credits-pricing-storage-tier-next-38gib": "接下来的 38 GiB", - "credits-pricing-storage-tier-next-38gib-price": "$0.05 每 GiB", - "credits-pricing-storage-tier-next-390gib": "接下来的 390 GiB", - "credits-pricing-storage-tier-next-390gib-price": "$0.03 每 GiB", - "credits-pricing-storage-tier-next-5gib": "接下来的 5 GiB", - "credits-pricing-storage-tier-next-5gib-price": "$0.08 每 GiB", - "credits-pricing-storage-tier-next-640gib": "接下来的 640 GiB", - "credits-pricing-storage-tier-next-640gib-price": "$0.025 每 GiB", - "credits-pricing-storage-tier-over-1tb": "超过 1 TB", - "credits-pricing-storage-tier-over-1tb-price": "$0.021 每 GiB", "credits-pricing-storage-title": "存储 (GiB)", "credits-pricing-title": "积分定价", "credits-top-up-quantity-help": "结账时可在 Stripe 中直接调整数量。", diff --git a/src/services/creditPricing.ts b/src/services/creditPricing.ts index 973a67f7e6..6c6c90dd54 100644 --- a/src/services/creditPricing.ts +++ b/src/services/creditPricing.ts @@ -15,47 +15,6 @@ type Translate = (key: string, values?: Record) => stri export const creditPricingMetricOrder: CreditMetricType[] = ['mau', 'bandwidth', 'storage', 'build_time'] -const creditPricingTierLabelKeys: Partial>> = { - mau: { - '0:1000000': 'credits-pricing-mau-tier-first', - '1000000:3000000': 'credits-pricing-mau-tier-next-2m', - '3000000:10000000': 'credits-pricing-mau-tier-next-7m', - '10000000:15000000': 'credits-pricing-mau-tier-next-5m', - '15000000:25000000': 'credits-pricing-mau-tier-next-10m', - '25000000:40000000': 'credits-pricing-mau-tier-next-15m', - '40000000:100000000': 'credits-pricing-mau-tier-next-60m', - '100000000:open': 'credits-pricing-mau-tier-over-100m', - }, - bandwidth: { - '0:1024': 'credits-pricing-bandwidth-tier-first', - '1024:2048': 'credits-pricing-bandwidth-tier-next-1tb', - '2048:6144': 'credits-pricing-bandwidth-tier-next-4tb', - '6144:12288': 'credits-pricing-bandwidth-tier-next-6tb', - '12288:25600': 'credits-pricing-bandwidth-tier-next-13tb', - '25600:64512': 'credits-pricing-bandwidth-tier-next-38tb', - '64512:130048': 'credits-pricing-bandwidth-tier-next-64tb', - '130048:open': 'credits-pricing-bandwidth-tier-over-128tb', - }, - storage: { - '0:1': 'credits-pricing-storage-tier-first', - '1:6': 'credits-pricing-storage-tier-next-5gib', - '6:25': 'credits-pricing-storage-tier-next-19gib', - '25:63': 'credits-pricing-storage-tier-next-38gib', - '63:250': 'credits-pricing-storage-tier-next-187gib', - '250:640': 'credits-pricing-storage-tier-next-390gib', - '640:1280': 'credits-pricing-storage-tier-next-640gib', - '1280:open': 'credits-pricing-storage-tier-over-1tb', - }, - build_time: { - '0:100': 'credits-pricing-build-tier-first-100', - '100:500': 'credits-pricing-build-tier-next-400', - '500:1000': 'credits-pricing-build-tier-next-500', - '1000:5000': 'credits-pricing-build-tier-next-4000', - '5000:10000': 'credits-pricing-build-tier-next-5000', - '10000:open': 'credits-pricing-build-tier-over-10000', - }, -} - const creditPricingUnitLabelKeys: Record = { mau: 'credits-pricing-unit-per-mau', bandwidth: 'credits-pricing-unit-per-gib', @@ -77,20 +36,15 @@ function toBilledUnits(step: Pick, rawValue: n return Math.ceil(rawValue / factor) } -function getTierLookupKey(step: Pick) { - const minUnits = toBilledUnits(step, step.step_min) - if (isOpenEndedTier(step)) - return `${minUnits}:open` - - const maxUnits = toBilledUnits(step, step.step_max) - return `${minUnits}:${maxUnits}` -} - -function formatCreditTierAmount(metric: CreditMetricType, billedUnits: number, locale?: string) { - const formatter = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 }) +function formatCreditTierAmount(metric: CreditMetricType, billedUnits: number, t: Translate, locale?: string) { + const formatter = new Intl.NumberFormat(locale, { + maximumFractionDigits: 0, + notation: metric === 'mau' ? 'compact' : 'standard', + compactDisplay: 'short', + }) - if (metric === 'mau' && billedUnits >= 1_000_000 && billedUnits % 1_000_000 === 0) - return `${formatter.format(billedUnits / 1_000_000)}M` + if (metric === 'mau') + return formatter.format(billedUnits) if ((metric === 'bandwidth' || metric === 'storage') && billedUnits >= 1024 && billedUnits % 1024 === 0) return `${formatter.format(billedUnits / 1024)} TB` @@ -99,7 +53,7 @@ function formatCreditTierAmount(metric: CreditMetricType, billedUnits: number, l return `${formatter.format(billedUnits)} GiB` if (metric === 'build_time') - return `${formatter.format(billedUnits)} ${billedUnits === 1 ? 'minute' : 'minutes'}` + return t('minutes-short', { minutes: formatter.format(billedUnits) }) return formatter.format(billedUnits) } @@ -126,10 +80,6 @@ export function getFirstTierCreditUnitPricing(steps: CreditPricingStep[]) { }, {}) } -export function getCreditPricingTierLabelKey(step: Pick) { - return creditPricingTierLabelKeys[step.type]?.[getTierLookupKey(step)] ?? null -} - export function formatCreditPriceValue(pricePerUnit: number, locale?: string) { return new Intl.NumberFormat(locale, { style: 'currency', @@ -156,31 +106,24 @@ export function formatCreditPricingTierLabel( t: Translate, locale?: string, ) { - const translatedTierKey = getCreditPricingTierLabelKey(step) - if (translatedTierKey) { - const translated = t(translatedTierKey) - if (translated !== translatedTierKey) - return translated - } - const minUnits = toBilledUnits(step, step.step_min) const maxUnits = toBilledUnits(step, step.step_max) const openEnded = isOpenEndedTier(step) if (step.step_min === 0) { return t('credits-pricing-tier-first', { - amount: formatCreditTierAmount(step.type, maxUnits, locale), + amount: formatCreditTierAmount(step.type, maxUnits, t, locale), }) } if (openEnded) { return t('credits-pricing-tier-over', { - amount: formatCreditTierAmount(step.type, minUnits, locale), + amount: formatCreditTierAmount(step.type, minUnits, t, locale), }) } return t('credits-pricing-tier-next', { - amount: formatCreditTierAmount(step.type, maxUnits - minUnits, locale), + amount: formatCreditTierAmount(step.type, maxUnits - minUnits, t, locale), }) } diff --git a/tests/credit-pricing-ui.unit.test.ts b/tests/credit-pricing-ui.unit.test.ts index 771ef29abb..6626f446f7 100644 --- a/tests/credit-pricing-ui.unit.test.ts +++ b/tests/credit-pricing-ui.unit.test.ts @@ -3,7 +3,7 @@ import { formatCreditPricingPrice, formatCreditPricingTierLabel, formatIncludedT const messages: Record = { 'credits-plan-overage': '{included}, then {price}', - 'credits-pricing-build-tier-first-100': 'First 100 minutes', + 'minutes-short': '{minutes}m', 'credits-pricing-price': '{price} {unit}', 'credits-pricing-tier-first': 'First {amount}', 'credits-pricing-tier-next': 'Next {amount}', @@ -20,13 +20,13 @@ function t(key: string, values: Record = {}) { } describe('credit pricing UI helpers', () => { - it.concurrent('formats known build_time tiers using the existing translated labels', () => { + it.concurrent('formats first build_time tiers with generic translated labels', () => { expect(formatCreditPricingTierLabel({ type: 'build_time', step_min: 0, step_max: 6000, unit_factor: 60, - }, t)).toBe('First 100 minutes') + }, t)).toBe('First 100m') expect(formatCreditPricingPrice('build_time', 0.16, t)).toBe('$0.16 per minute') }) @@ -37,14 +37,14 @@ describe('credit pricing UI helpers', () => { step_min: 3000, step_max: 9000, unit_factor: 60, - }, t)).toBe('Next 100 minutes') + }, t)).toBe('Next 100m') expect(formatCreditPricingTierLabel({ type: 'build_time', step_min: 9000, step_max: Number.MAX_SAFE_INTEGER, unit_factor: 60, - }, t)).toBe('Over 150 minutes') + }, t)).toBe('Over 150m') }) it.concurrent('derives the visible first-tier pricing from the shared step list', () => { diff --git a/tests/webhooks.test.ts b/tests/webhooks.test.ts index 3d94134534..709e81f11b 100644 --- a/tests/webhooks.test.ts +++ b/tests/webhooks.test.ts @@ -49,19 +49,18 @@ beforeAll(async () => { if (appError) throw appError - const appScopedKeyResponse = await fetch(`${BASE_URL}/apikey`, { - method: 'POST', - headers, - body: JSON.stringify({ - name: `webhook-app-scoped-${globalId}`, - limited_to_apps: [webhookAppId], - }), - }) - if (appScopedKeyResponse.status !== 200) { - throw new Error(`Failed to create app-scoped API key for webhook tests: ${await appScopedKeyResponse.text()}`) + const { data: appScopedKeyData, error: appScopedKeyError } = await getSupabaseClient().rpc('create_hashed_apikey_for_user', { + p_user_id: USER_ID, + p_mode: 'all', + p_name: `webhook-app-scoped-${globalId}`, + p_limited_to_orgs: [], + p_limited_to_apps: [webhookAppId], + p_expires_at: null as unknown as string, + }) + if (appScopedKeyError || !appScopedKeyData?.key) { + throw new Error(`Failed to create app-scoped API key for webhook tests: ${appScopedKeyError?.message ?? 'missing key data'}`) } - const appScopedKeyData = await appScopedKeyResponse.json() as { id: number, key: string } appScopedKeyId = appScopedKeyData.id appScopedKey = appScopedKeyData.key }) @@ -73,10 +72,7 @@ afterAll(async () => { await (getSupabaseClient() as any).from('webhooks').delete().eq('id', createdWebhookId) } if (appScopedKeyId) { - await fetch(`${BASE_URL}/apikey/${appScopedKeyId}`, { - method: 'DELETE', - headers, - }) + await getSupabaseClient().from('apikeys').delete().eq('id', appScopedKeyId) } await getSupabaseClient().from('apps').delete().eq('app_id', webhookAppId) // Clean up test organization and stripe_info From 5aca3a16174fdddb64ecc45912a50a6d1404c2ae Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 21:59:36 +0200 Subject: [PATCH 12/17] test(cloudflare): stabilize bundle error cases --- tests/bundle-error-cases.test.ts | 75 +++++++++++--------------------- 1 file changed, 26 insertions(+), 49 deletions(-) diff --git a/tests/bundle-error-cases.test.ts b/tests/bundle-error-cases.test.ts index 5e541517bf..f9bc4dad96 100644 --- a/tests/bundle-error-cases.test.ts +++ b/tests/bundle-error-cases.test.ts @@ -1,10 +1,13 @@ import { randomUUID } from 'node:crypto' import { afterAll, beforeAll, describe, expect, it } from 'vitest' -import { BASE_URL, getSupabaseClient, headers, resetAndSeedAppData, resetAppData, USER_ID } from './test-utils.ts' +import { BASE_URL, createAppVersions, getSupabaseClient, headers, resetAndSeedAppData, resetAppData, USER_ID } from './test-utils.ts' const id = randomUUID() const APPNAME = `com.bundle.error.${id}` let testOrgId: string +let metadataVersionId: number +let channelVersionId: number +let testChannelId: number beforeAll(async () => { await resetAndSeedAppData(APPNAME) @@ -20,6 +23,26 @@ beforeAll(async () => { throw new Error('App not found after seeding') testOrgId = app.owner_org + + metadataVersionId = (await createAppVersions(`1.0.0-test-metadata-${id}`, APPNAME)).id + channelVersionId = (await createAppVersions(`1.0.0-test-channel-${id}`, APPNAME)).id + + const { data: channel, error: channelError } = await getSupabaseClient() + .from('channels') + .insert({ + name: `test-channel-${id}`, + app_id: APPNAME, + version: channelVersionId, + created_by: USER_ID, + owner_org: testOrgId, + }) + .select('id') + .single() + + if (channelError || !channel) + throw channelError ?? new Error('Failed to create test channel') + + testChannelId = channel.id }) afterAll(async () => { @@ -124,27 +147,12 @@ describe('[DELETE] /bundle - Error Cases', () => { describe('[POST] /bundle/metadata - Extended Error Cases', () => { it('should return 400 when no fields to update', async () => { - // First create a version to test with - const supabase = getSupabaseClient() - const { data: version, error } = await supabase - .from('app_versions') - .insert({ - app_id: APPNAME, - name: '1.0.0-test-metadata', - owner_org: testOrgId, - }) - .select() - .single() - - if (error) - throw error - const response = await fetch(`${BASE_URL}/bundle/metadata`, { method: 'POST', headers, body: JSON.stringify({ app_id: APPNAME, - version_id: version.id, + version_id: metadataVersionId, // No updateable fields provided }), }) @@ -210,37 +218,6 @@ describe('[PUT] /bundle - Extended Error Cases', () => { }) it('should return 500 when bundle cannot be set to channel', async () => { - // Create a version and channel first - const supabase = getSupabaseClient() - - const { data: version, error: versionError } = await supabase - .from('app_versions') - .insert({ - app_id: APPNAME, - name: '1.0.0-test-channel', - owner_org: testOrgId, - }) - .select() - .single() - - if (versionError) - throw versionError - - const { data: channel, error: channelError } = await supabase - .from('channels') - .insert({ - name: 'test-channel', - app_id: APPNAME, - version: version.id, - created_by: USER_ID, - owner_org: testOrgId, - }) - .select() - .single() - - if (channelError) - throw channelError - // Try to set bundle to channel with conflicting data const response = await fetch(`${BASE_URL}/bundle`, { method: 'PUT', @@ -248,7 +225,7 @@ describe('[PUT] /bundle - Extended Error Cases', () => { body: JSON.stringify({ app_id: APPNAME, version_id: 999999, // Non-existent version ID - channel_id: channel.id, + channel_id: testChannelId, }), }) From 6488319e98ef0beef3439a63546e5cf73b302fb4 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 22:07:06 +0200 Subject: [PATCH 13/17] test(cloudflare): retry bundle channel sync --- tests/bundle.test.ts | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/tests/bundle.test.ts b/tests/bundle.test.ts index 5f6d240e58..96cb35caae 100644 --- a/tests/bundle.test.ts +++ b/tests/bundle.test.ts @@ -5,6 +5,29 @@ import { BASE_URL, createAppVersions, fetchBundle, getSupabaseClient, headers, r const id = randomUUID() const APPNAME = `com.app.b.${id}` +async function putBundleToChannelWithRetry(body: { app_id: string, version_id: number, channel_id: number }, maxRetries = 3): Promise { + let response: Response | undefined + + for (let attempt = 0; attempt < maxRetries; attempt++) { + response = await fetch(`${BASE_URL}/bundle`, { + method: 'PUT', + headers, + body: JSON.stringify(body), + }) + + if (response.status === 200) + return response + + if (attempt < maxRetries - 1) + await new Promise(resolve => setTimeout(resolve, 250 * (attempt + 1))) + } + + if (!response) + throw new Error('Failed to set bundle to channel: no response received') + + return response +} + beforeAll(async () => { await resetAndSeedAppData(APPNAME) }) @@ -215,14 +238,10 @@ describe('[PUT] /bundle operations - Set bundle to channel', () => { }) it('should set bundle to channel successfully', async () => { - const response = await fetch(`${BASE_URL}/bundle`, { - method: 'PUT', - headers, - body: JSON.stringify({ - app_id: APPNAME, - version_id: versionId, - channel_id: channelId, - }), + const response = await putBundleToChannelWithRetry({ + app_id: APPNAME, + version_id: versionId, + channel_id: channelId, }) const data = await response.json() as { status: string, message: string } From 8271140a757ff30ed4c64615925ca4bdde0439ee Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 22:08:39 +0200 Subject: [PATCH 14/17] fix(pricing): align tier label spans --- src/services/creditPricing.ts | 6 +++++- tests/credit-pricing-ui.unit.test.ts | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/services/creditPricing.ts b/src/services/creditPricing.ts index 6c6c90dd54..1fa8b8dd04 100644 --- a/src/services/creditPricing.ts +++ b/src/services/creditPricing.ts @@ -36,6 +36,10 @@ function toBilledUnits(step: Pick, rawValue: n return Math.ceil(rawValue / factor) } +function getTierSpanUnits(step: Pick) { + return toBilledUnits(step, Math.max(step.step_max - step.step_min, 0)) +} + function formatCreditTierAmount(metric: CreditMetricType, billedUnits: number, t: Translate, locale?: string) { const formatter = new Intl.NumberFormat(locale, { maximumFractionDigits: 0, @@ -123,7 +127,7 @@ export function formatCreditPricingTierLabel( } return t('credits-pricing-tier-next', { - amount: formatCreditTierAmount(step.type, maxUnits - minUnits, t, locale), + amount: formatCreditTierAmount(step.type, getTierSpanUnits(step), t, locale), }) } diff --git a/tests/credit-pricing-ui.unit.test.ts b/tests/credit-pricing-ui.unit.test.ts index 6626f446f7..c4dbc879df 100644 --- a/tests/credit-pricing-ui.unit.test.ts +++ b/tests/credit-pricing-ui.unit.test.ts @@ -47,6 +47,15 @@ describe('credit pricing UI helpers', () => { }, t)).toBe('Over 150m') }) + it.concurrent('rounds bounded custom spans from the raw tier width', () => { + expect(formatCreditPricingTierLabel({ + type: 'build_time', + step_min: 5000, + step_max: 6000, + unit_factor: 60, + }, t)).toBe('Next 17m') + }) + it.concurrent('derives the visible first-tier pricing from the shared step list', () => { expect(getFirstTierCreditUnitPricing([ { From 744cbd6bb5c87d67413da862ad480836ec23b216 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 22:20:18 +0200 Subject: [PATCH 15/17] refactor(i18n): simplify pricing tier labels --- messages/en.json | 6 +++--- src/services/creditPricing.ts | 13 +++++-------- tests/credit-pricing-ui.unit.test.ts | 14 +++++++------- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/messages/en.json b/messages/en.json index 73bbc96b83..e2d95e7b9a 100644 --- a/messages/en.json +++ b/messages/en.json @@ -647,9 +647,9 @@ "credits-pricing-mau-title": "Monthly Active Users (MAU)", "credits-pricing-storage-subtitle": "Per GiB occupying storage for your releases.", "credits-pricing-storage-title": "Storage (GiB)", - "credits-pricing-tier-first": "First {amount}", - "credits-pricing-tier-next": "Next {amount}", - "credits-pricing-tier-over": "Over {amount}", + "credits-pricing-tier-first": "Up to {to}", + "credits-pricing-tier-range": "From {from} to {to}", + "credits-pricing-tier-over": "Over {from}", "credits-pricing-title": "Credit pricing", "credits-pricing-unit-per-gib": "per GiB", "credits-pricing-unit-per-mau": "per MAU", diff --git a/src/services/creditPricing.ts b/src/services/creditPricing.ts index 1fa8b8dd04..a53f2ff70c 100644 --- a/src/services/creditPricing.ts +++ b/src/services/creditPricing.ts @@ -36,10 +36,6 @@ function toBilledUnits(step: Pick, rawValue: n return Math.ceil(rawValue / factor) } -function getTierSpanUnits(step: Pick) { - return toBilledUnits(step, Math.max(step.step_max - step.step_min, 0)) -} - function formatCreditTierAmount(metric: CreditMetricType, billedUnits: number, t: Translate, locale?: string) { const formatter = new Intl.NumberFormat(locale, { maximumFractionDigits: 0, @@ -116,18 +112,19 @@ export function formatCreditPricingTierLabel( if (step.step_min === 0) { return t('credits-pricing-tier-first', { - amount: formatCreditTierAmount(step.type, maxUnits, t, locale), + to: formatCreditTierAmount(step.type, maxUnits, t, locale), }) } if (openEnded) { return t('credits-pricing-tier-over', { - amount: formatCreditTierAmount(step.type, minUnits, t, locale), + from: formatCreditTierAmount(step.type, minUnits, t, locale), }) } - return t('credits-pricing-tier-next', { - amount: formatCreditTierAmount(step.type, getTierSpanUnits(step), t, locale), + return t('credits-pricing-tier-range', { + from: formatCreditTierAmount(step.type, minUnits, t, locale), + to: formatCreditTierAmount(step.type, maxUnits, t, locale), }) } diff --git a/tests/credit-pricing-ui.unit.test.ts b/tests/credit-pricing-ui.unit.test.ts index c4dbc879df..5db730983a 100644 --- a/tests/credit-pricing-ui.unit.test.ts +++ b/tests/credit-pricing-ui.unit.test.ts @@ -5,9 +5,9 @@ const messages: Record = { 'credits-plan-overage': '{included}, then {price}', 'minutes-short': '{minutes}m', 'credits-pricing-price': '{price} {unit}', - 'credits-pricing-tier-first': 'First {amount}', - 'credits-pricing-tier-next': 'Next {amount}', - 'credits-pricing-tier-over': 'Over {amount}', + 'credits-pricing-tier-first': 'Up to {to}', + 'credits-pricing-tier-range': 'From {from} to {to}', + 'credits-pricing-tier-over': 'Over {from}', 'credits-pricing-unit-per-gib': 'per GiB', 'credits-pricing-unit-per-mau': 'per MAU', 'credits-pricing-unit-per-minute': 'per minute', @@ -26,7 +26,7 @@ describe('credit pricing UI helpers', () => { step_min: 0, step_max: 6000, unit_factor: 60, - }, t)).toBe('First 100m') + }, t)).toBe('Up to 100m') expect(formatCreditPricingPrice('build_time', 0.16, t)).toBe('$0.16 per minute') }) @@ -37,7 +37,7 @@ describe('credit pricing UI helpers', () => { step_min: 3000, step_max: 9000, unit_factor: 60, - }, t)).toBe('Next 100m') + }, t)).toBe('From 50m to 150m') expect(formatCreditPricingTierLabel({ type: 'build_time', @@ -47,13 +47,13 @@ describe('credit pricing UI helpers', () => { }, t)).toBe('Over 150m') }) - it.concurrent('rounds bounded custom spans from the raw tier width', () => { + it.concurrent('formats bounded custom ranges with both dynamic endpoints', () => { expect(formatCreditPricingTierLabel({ type: 'build_time', step_min: 5000, step_max: 6000, unit_factor: 60, - }, t)).toBe('Next 17m') + }, t)).toBe('From 84m to 100m') }) it.concurrent('derives the visible first-tier pricing from the shared step list', () => { From ff0dec2d7d46b56178efe7cb21cd35cc71e9dae7 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 22:24:23 +0200 Subject: [PATCH 16/17] fix(api): guard org-scoped credit pricing --- .../functions/_backend/private/credits.ts | 45 +++++++++++++++---- tests/credits-pricing.test.ts | 38 +++++++++++++++- 2 files changed, 73 insertions(+), 10 deletions(-) diff --git a/supabase/functions/_backend/private/credits.ts b/supabase/functions/_backend/private/credits.ts index 03ecfeb01e..540bfbc173 100644 --- a/supabase/functions/_backend/private/credits.ts +++ b/supabase/functions/_backend/private/credits.ts @@ -1,9 +1,9 @@ import type { Context } from 'hono' import type Stripe from 'stripe' -import type { MiddlewareKeyVariables } from '../utils/hono.ts' +import type { AuthInfo, MiddlewareKeyVariables } from '../utils/hono.ts' import { Hono } from 'hono/tiny' import { getFallbackCreditProductId } from '../utils/credits.ts' -import { middlewareAuth, parseBody, simpleError, useCors } from '../utils/hono.ts' +import { getClaimsFromJWT, middlewareAuth, parseBody, simpleError, useCors } from '../utils/hono.ts' import { cloudlog, cloudlogErr } from '../utils/logging.ts' import { checkPermission } from '../utils/rbac.ts' import { createOneTimeCheckout, getCreditCheckoutDetails, getStripe, isStripeEmulatorEnabled } from '../utils/stripe.ts' @@ -135,8 +135,8 @@ function preferScopedCreditSteps(steps: CreditStep[], orgId?: string): CreditSte const normalizedSteps: CreditStep[] = [] - for (const [type, group] of stepGroups.entries()) { - const scopedSteps = sortCreditSteps(group.scoped.filter(step => step.type === type)) + for (const [, group] of stepGroups.entries()) { + const scopedSteps = sortCreditSteps(group.scoped) if (scopedSteps.length === 0) { normalizedSteps.push(...sortCreditSteps(group.global)) continue @@ -152,16 +152,43 @@ function preferScopedCreditSteps(steps: CreditStep[], orgId?: string): CreditSte return sortCreditSteps(normalizedSteps) } +async function requireOrgScopedPricingAccess(c: AppContext, orgId: string, authorization: string) { + c.set('authorization', authorization) + + const claims = await getClaimsFromJWT(c, authorization) + if (!claims?.sub) { + throw simpleError('not_authorized', 'Not authorized') + } + + c.set('auth', { + userId: claims.sub, + authType: 'jwt', + apikey: null, + jwt: authorization, + } satisfies AuthInfo) + + if (!await checkPermission(c, 'org.read', { orgId })) { + throw simpleError('not_authorized', 'Not authorized') + } +} + async function getScopedCreditSteps(c: AppContext, orgId?: string): Promise { const authorization = c.req.header('authorization') ?? c.req.header('Authorization') ?? c.get('authorization') - const pricingClient = orgId - ? authorization - ? supabaseClient(c, authorization) - : undefined - : supabaseAdmin(c) + let pricingClient: ReturnType | ReturnType | undefined + if (orgId) { + if (!authorization) { + throw simpleError('not_authorized', 'Not authorized') + } + + await requireOrgScopedPricingAccess(c, orgId, authorization) + pricingClient = supabaseClient(c, authorization) + } + else { + pricingClient = supabaseAdmin(c) + } if (!pricingClient) throw simpleError('not_authorized', 'Not authorized') diff --git a/tests/credits-pricing.test.ts b/tests/credits-pricing.test.ts index 2247c99157..1122fcd371 100644 --- a/tests/credits-pricing.test.ts +++ b/tests/credits-pricing.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { executeSQL, fetchWithRetry, getAuthHeaders, getEndpointUrl, ORG_ID } from './test-utils' +import { executeSQL, fetchWithRetry, getAuthHeaders, getAuthHeadersForCredentials, getEndpointUrl, ORG_ID, USER_EMAIL_NONMEMBER, USER_PASSWORD_NONMEMBER } from './test-utils' interface CreditStep { type: string @@ -33,6 +33,20 @@ describe('credits pricing API', () => { expect(data.error).toBe('not_authorized') }) + it.concurrent('rejects org-scoped pricing queries for authenticated non-members', async () => { + const response = await fetchWithRetry(getEndpointUrl(`/private/credits?org_id=${ORG_ID}`), { + headers: await getAuthHeadersForCredentials(USER_EMAIL_NONMEMBER, USER_PASSWORD_NONMEMBER), + }) + + expect(response.status).toBe(400) + + const data = await response.json() as { + error: string + } + + expect(data.error).toBe('not_authorized') + }) + it.concurrent('prices build_time overage through the shared calculator endpoint', async () => { const response = await fetchWithRetry(getEndpointUrl('/private/credits'), { method: 'POST', @@ -89,6 +103,28 @@ describe('credits pricing API', () => { expect(data.error).toBe('invalid_build_time') }) + it.concurrent('rejects org-scoped cost calculation for authenticated non-members', async () => { + const response = await fetchWithRetry(getEndpointUrl('/private/credits'), { + method: 'POST', + headers: await getAuthHeadersForCredentials(USER_EMAIL_NONMEMBER, USER_PASSWORD_NONMEMBER), + body: JSON.stringify({ + org_id: ORG_ID, + mau: 0, + bandwidth: 0, + storage: 0, + build_time: 6000, + }), + }) + + expect(response.status).toBe(400) + + const data = await response.json() as { + error: string + } + + expect(data.error).toBe('not_authorized') + }) + it('uses org-scoped build_time tiers when an authorized org_id is supplied', async () => { await executeSQL('DELETE FROM public.capgo_credits_steps WHERE org_id = $1 AND type = $2', [ORG_ID, 'build_time']) From cd34fc208b84b7faa0308033eca69aeb97236ed9 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Wed, 8 Apr 2026 22:33:37 +0200 Subject: [PATCH 17/17] fix(webhooks): enforce org key expiration policy --- .../_backend/public/webhooks/index.ts | 25 +- tests/webhooks-apikey-policy.test.ts | 273 ++++++++++++++++++ 2 files changed, 296 insertions(+), 2 deletions(-) create mode 100644 tests/webhooks-apikey-policy.test.ts diff --git a/supabase/functions/_backend/public/webhooks/index.ts b/supabase/functions/_backend/public/webhooks/index.ts index c2a1dc06d3..e107836e79 100644 --- a/supabase/functions/_backend/public/webhooks/index.ts +++ b/supabase/functions/_backend/public/webhooks/index.ts @@ -1,9 +1,9 @@ import type { Context } from 'hono' import type { AuthInfo, MiddlewareKeyVariables } from '../../utils/hono.ts' import type { Database } from '../../utils/supabase.types.ts' -import { getBodyOrQuery, honoFactory, simpleError } from '../../utils/hono.ts' +import { getBodyOrQuery, honoFactory, quickError, simpleError } from '../../utils/hono.ts' import { middlewareKey, middlewareV2 } from '../../utils/hono_middleware.ts' -import { apikeyHasOrgRight, hasOrgRight, hasOrgRightApikey } from '../../utils/supabase.ts' +import { apikeyHasOrgRight, apikeyHasOrgRightWithPolicy, hasOrgRight, hasOrgRightApikey, supabaseApikey } from '../../utils/supabase.ts' import { deleteWebhook } from './delete.ts' import { getDeliveries, retryDelivery } from './deliveries.ts' import { get } from './get.ts' @@ -26,6 +26,24 @@ function assertOrgWebhookScope( } } +async function assertWebhookOrgPolicy( + c: Context, + orgId: string, + apikey: Database['public']['Tables']['apikeys']['Row'], +): Promise { + const supabase = supabaseApikey(c, c.get('capgkey') as string) + const orgCheck = await apikeyHasOrgRightWithPolicy(c, apikey, orgId, supabase) + if (orgCheck.valid) { + return + } + + if (orgCheck.error === 'org_requires_expiring_key') { + throw quickError(401, 'org_requires_expiring_key', 'This organization requires API keys with an expiration date. Please use a different key or update this key with an expiration date.') + } + + throw simpleError('invalid_org_id', 'You can\'t access this organization', { org_id: orgId }) +} + /** * Shared permission check for webhook endpoints (API key auth) * Validates admin access to organization @@ -40,6 +58,7 @@ export async function checkWebhookPermission( } assertOrgWebhookScope(orgId, apikey) + await assertWebhookOrgPolicy(c, orgId, apikey) } /** @@ -59,6 +78,8 @@ export async function checkWebhookPermissionV2( // If using API key, also check the key has org access if (auth.authType === 'apikey' && auth.apikey) { assertOrgWebhookScope(orgId, auth.apikey) + const policyKey = c.get('apikey') as Database['public']['Tables']['apikeys']['Row'] | undefined + await assertWebhookOrgPolicy(c, orgId, policyKey ?? auth.apikey) } } diff --git a/tests/webhooks-apikey-policy.test.ts b/tests/webhooks-apikey-policy.test.ts new file mode 100644 index 0000000000..2ac206385a --- /dev/null +++ b/tests/webhooks-apikey-policy.test.ts @@ -0,0 +1,273 @@ +import { randomUUID } from 'node:crypto' +import { afterAll, beforeAll, describe, expect, it } from 'vitest' +import { getEndpointUrl, getSupabaseClient, headers, TEST_EMAIL, USER_ID } from './test-utils.ts' + +const globalId = randomUUID() +const policyOrgId = randomUUID() +const policyCustomerId = `cus_webhook_policy_${globalId}` +const APIKEY_URL = getEndpointUrl('/apikey') +const WEBHOOKS_URL = getEndpointUrl('/webhooks') +const WEBHOOKS_TEST_URL = getEndpointUrl('/webhooks/test') +const WEBHOOKS_RETRY_URL = getEndpointUrl('/webhooks/deliveries/retry') + +let legacyApiKeyId: number | null = null +let legacyApiKeyValue: string | null = null +let expiringSubkeyId: number | null = null +let expiringSubkeyValue: string | null = null +let createdWebhookId: string | null = null +let createdDeliveryId: string | null = null + +beforeAll(async () => { + const supabase = getSupabaseClient() + + const { error: stripeError } = await supabase.from('stripe_info').insert({ + customer_id: policyCustomerId, + status: 'succeeded', + product_id: 'prod_LQIregjtNduh4q', + subscription_id: `sub_webhook_policy_${globalId}`, + trial_at: new Date(Date.now() + 15 * 24 * 60 * 60 * 1000).toISOString(), + is_good_plan: true, + }) + if (stripeError) + throw stripeError + + const { error: orgError } = await supabase.from('orgs').insert({ + id: policyOrgId, + name: `Webhook Policy Org ${globalId}`, + management_email: TEST_EMAIL, + created_by: USER_ID, + customer_id: policyCustomerId, + }) + if (orgError) + throw orgError + + const { error: memberError } = await supabase.from('org_users').insert({ + org_id: policyOrgId, + user_id: USER_ID, + user_right: 'super_admin', + }) + if (memberError) + throw memberError + + const keyResponse = await fetch(APIKEY_URL, { + method: 'POST', + headers, + body: JSON.stringify({ + name: `legacy-webhook-key-${globalId}`, + limited_to_orgs: [policyOrgId], + }), + }) + expect(keyResponse.status).toBe(200) + const keyData = await keyResponse.json() as { id: number, key: string } + legacyApiKeyId = keyData.id + legacyApiKeyValue = keyData.key + + const webhookResponse = await fetch(WEBHOOKS_URL, { + method: 'POST', + headers, + body: JSON.stringify({ + orgId: policyOrgId, + name: `policy-webhook-${globalId}`, + url: 'https://example.com/webhook-policy', + events: ['orgs'], + }), + }) + expect(webhookResponse.status).toBe(201) + const webhookData = await webhookResponse.json() as { webhook: { id: string } } + createdWebhookId = webhookData.webhook.id + + const testWebhookResponse = await fetch(WEBHOOKS_TEST_URL, { + method: 'POST', + headers, + body: JSON.stringify({ + orgId: policyOrgId, + webhookId: createdWebhookId, + }), + }) + expect(testWebhookResponse.status).toBe(200) + const testWebhookData = await testWebhookResponse.json() as { delivery_id: string } + createdDeliveryId = testWebhookData.delivery_id + + const { error: policyError } = await supabase.from('orgs').update({ + require_apikey_expiration: true, + max_apikey_expiration_days: 30, + }).eq('id', policyOrgId) + if (policyError) + throw policyError + + const subkeyResponse = await fetch(APIKEY_URL, { + method: 'POST', + headers, + body: JSON.stringify({ + name: `expiring-webhook-subkey-${globalId}`, + limited_to_orgs: [policyOrgId], + expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), + }), + }) + expect(subkeyResponse.status).toBe(200) + const subkeyData = await subkeyResponse.json() as { id: number, key: string } + expiringSubkeyId = subkeyData.id + expiringSubkeyValue = subkeyData.key +}, 60000) + +afterAll(async () => { + const supabase = getSupabaseClient() + + if (createdWebhookId) { + await (supabase as any).from('webhooks').delete().eq('id', createdWebhookId) + } + + if (legacyApiKeyId) { + await supabase.from('apikeys').delete().eq('id', legacyApiKeyId) + } + + if (expiringSubkeyId) { + await supabase.from('apikeys').delete().eq('id', expiringSubkeyId) + } + + await supabase.from('org_users').delete().eq('org_id', policyOrgId) + await supabase.from('orgs').delete().eq('id', policyOrgId) + await supabase.from('stripe_info').delete().eq('customer_id', policyCustomerId) +}, 60000) + +describe('webhook endpoints enforce org API key expiration policy', () => { + it('rejects webhook listing for legacy non-expiring org key', async () => { + if (!legacyApiKeyValue) + throw new Error('Legacy API key was not created') + + const response = await fetch(`${WEBHOOKS_URL}?orgId=${policyOrgId}`, { + headers: { + 'Content-Type': 'application/json', + 'Authorization': legacyApiKeyValue, + }, + }) + + expect(response.status).toBe(401) + const data = await response.json() as { error: string } + expect(data.error).toBe('org_requires_expiring_key') + }) + + it('rejects webhook creation for legacy non-expiring org key', async () => { + if (!legacyApiKeyValue) + throw new Error('Legacy API key was not created') + + const response = await fetch(WEBHOOKS_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': legacyApiKeyValue, + }, + body: JSON.stringify({ + orgId: policyOrgId, + name: `blocked-webhook-${globalId}`, + url: 'https://example.com/blocked-webhook', + events: ['orgs'], + }), + }) + + expect(response.status).toBe(401) + const data = await response.json() as { error: string } + expect(data.error).toBe('org_requires_expiring_key') + }) + + it('rejects webhook deletion for legacy non-expiring org key', async () => { + if (!legacyApiKeyValue || !createdWebhookId) + throw new Error('Webhook deletion prerequisites were not created') + + const response = await fetch(WEBHOOKS_URL, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Authorization': legacyApiKeyValue, + }, + body: JSON.stringify({ + orgId: policyOrgId, + webhookId: createdWebhookId, + }), + }) + + expect(response.status).toBe(401) + const data = await response.json() as { error: string } + expect(data.error).toBe('org_requires_expiring_key') + }) + + it('rejects webhook test for legacy non-expiring org key', async () => { + if (!legacyApiKeyValue || !createdWebhookId) + throw new Error('Webhook test prerequisites were not created') + + const response = await fetch(WEBHOOKS_TEST_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': legacyApiKeyValue, + }, + body: JSON.stringify({ + orgId: policyOrgId, + webhookId: createdWebhookId, + }), + }) + + expect(response.status).toBe(401) + const data = await response.json() as { error: string } + expect(data.error).toBe('org_requires_expiring_key') + }) + + it('rejects delivery retry for legacy non-expiring org key', async () => { + if (!legacyApiKeyValue || !createdDeliveryId) + throw new Error('Webhook delivery prerequisites were not created') + + const response = await fetch(WEBHOOKS_RETRY_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': legacyApiKeyValue, + }, + body: JSON.stringify({ + orgId: policyOrgId, + deliveryId: createdDeliveryId, + }), + }) + + expect(response.status).toBe(401) + const data = await response.json() as { error: string } + expect(data.error).toBe('org_requires_expiring_key') + }) + + it('rejects webhook test when a legacy parent key attaches an expiring subkey', async () => { + if (!legacyApiKeyValue || !expiringSubkeyId || !createdWebhookId) + throw new Error('Webhook subkey policy prerequisites were not created') + + const response = await fetch(WEBHOOKS_TEST_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': legacyApiKeyValue, + 'x-limited-key-id': String(expiringSubkeyId), + }, + body: JSON.stringify({ + orgId: policyOrgId, + webhookId: createdWebhookId, + }), + }) + + expect(response.status).toBe(401) + const data = await response.json() as { error: string } + expect(data.error).toBe('org_requires_expiring_key') + }) + + it('allows webhook listing for a compliant expiring org key', async () => { + if (!expiringSubkeyValue) + throw new Error('Expiring API key was not created') + + const response = await fetch(`${WEBHOOKS_URL}?orgId=${policyOrgId}`, { + headers: { + 'Content-Type': 'application/json', + 'Authorization': expiringSubkeyValue, + }, + }) + + expect(response.status).toBe(200) + const data = await response.json() + expect(Array.isArray(data)).toBe(true) + }) +})