Show payment error notifications (#10497)

* Show payment error notifications

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* Fix year in copyright

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* Fix duplicated ids

Signed-off-by: Artem Savchenko <armisav@gmail.com>

---------

Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
Artyom Savchenko
2026-02-12 11:11:28 +07:00
committed by GitHub
parent baacc66062
commit 93c2b4b694
14 changed files with 96 additions and 21 deletions
+3 -1
View File
@@ -56,6 +56,8 @@
"Of": "z",
"UpgradePlan": "Upgradovat plán",
"LimitReached": "Dosažen limit pracovního prostoru, některé funkce mohou být zakázány. Upgradujte pro obnovení plného přístupu.",
"RestrictedPlans": "Pouze vlastník pracovního prostoru nebo správce fakturace může změnit plán předplatného."
"RestrictedPlans": "Pouze vlastník pracovního prostoru nebo správce fakturace může změnit plán předplatného.",
"SubscriptionOperationFailed": "Operace s předplatným se nezdařila",
"SubscriptionErrorMessage": "Něco se pokazilo. Zkuste to znovu nebo kontaktujte podporu."
}
}
+3 -1
View File
@@ -56,6 +56,8 @@
"Of": "von",
"UpgradePlan": "Plan upgraden",
"LimitReached": "Arbeitsbereich-Limit erreicht, einige Funktionen könnten deaktiviert sein. Upgraden Sie für vollen Zugriff.",
"RestrictedPlans": "Nur der Arbeitsbereichsbesitzer oder Abrechnungsmanager kann den Abonnementplan ändern."
"RestrictedPlans": "Nur der Arbeitsbereichsbesitzer oder Abrechnungsmanager kann den Abonnementplan ändern.",
"SubscriptionOperationFailed": "Abonnementvorgang fehlgeschlagen",
"SubscriptionErrorMessage": "Etwas ist schiefgelaufen. Bitte versuchen Sie es erneut oder kontaktieren Sie den Support."
}
}
+3 -1
View File
@@ -56,6 +56,8 @@
"Of": "of",
"UpgradePlan": "Upgrade Plan",
"LimitReached": "Workspace limit reached, some functionality may be disabled. Upgrade to restore full access.",
"RestrictedPlans": "Only the workspace owner or billing manager can change the subscription plan"
"RestrictedPlans": "Only the workspace owner or billing manager can change the subscription plan",
"SubscriptionOperationFailed": "Subscription operation failed",
"SubscriptionErrorMessage": "Something went wrong. Please try again or contact support."
}
}
+3 -1
View File
@@ -56,6 +56,8 @@
"Of": "de",
"UpgradePlan": "Actualizar Plan",
"LimitReached": "Límite del espacio de trabajo alcanzado, algunas funciones pueden estar deshabilitadas. Actualiza para restaurar acceso completo.",
"RestrictedPlans": "Solo el propietario del espacio de trabajo o el administrador de facturación pueden cambiar el plan de suscripción."
"RestrictedPlans": "Solo el propietario del espacio de trabajo o el administrador de facturación pueden cambiar el plan de suscripción.",
"SubscriptionOperationFailed": "Error en la operación de suscripción",
"SubscriptionErrorMessage": "Algo ha ido mal. Inténtelo de nuevo o contacte con soporte."
}
}
+3 -1
View File
@@ -56,6 +56,8 @@
"Of": "de",
"UpgradePlan": "Mettre à niveau le plan",
"LimitReached": "Limite de l'espace de travail atteinte, certaines fonctionnalités peuvent être désactivées. Mettez à niveau pour restaurer l'accès complet.",
"RestrictedPlans": "Seul le propriétaire de l'espace de travail ou le gestionnaire de facturation peut modifier le plan d'abonnement."
"RestrictedPlans": "Seul le propriétaire de l'espace de travail ou le gestionnaire de facturation peut modifier le plan d'abonnement.",
"SubscriptionOperationFailed": "Échec de l'opération d'abonnement",
"SubscriptionErrorMessage": "Une erreur s'est produite. Veuillez réessayer ou contacter le support."
}
}
+3 -1
View File
@@ -56,6 +56,8 @@
"Of": "di",
"UpgradePlan": "Aggiorna Piano",
"LimitReached": "Limite workspace raggiunto, alcune funzionalità potrebbero essere disabilitate. Aggiorna per ripristinare l'accesso completo.",
"RestrictedPlans": "Solo il proprietario del workspace o il responsabile della fatturazione può modificare il piano di abbonamento."
"RestrictedPlans": "Solo il proprietario del workspace o il responsabile della fatturazione può modificare il piano di abbonamento.",
"SubscriptionOperationFailed": "Operazione di abbonamento non riuscita",
"SubscriptionErrorMessage": "Qualcosa è andato storto. Riprova o contatta l'assistenza."
}
}
+3 -1
View File
@@ -56,6 +56,8 @@
"Of": "/",
"UpgradePlan": "プランをアップグレード",
"LimitReached": "ワークスペースの制限に達しました。一部の機能が無効になる場合があります。アップグレードして完全アクセスを復元してください。",
"RestrictedPlans": "ワークスペースの所有者または請求管理者のみがサブスクリプションプランを変更できます。"
"RestrictedPlans": "ワークスペースの所有者または請求管理者のみがサブスクリプションプランを変更できます。",
"SubscriptionOperationFailed": "サブスクリプション操作に失敗しました",
"SubscriptionErrorMessage": "問題が発生しました。もう一度お試しいただくか、サポートにお問い合わせください。"
}
}
+3 -1
View File
@@ -56,6 +56,8 @@
"Of": "de",
"UpgradePlan": "Atualizar Plano",
"LimitReached": "Limite do workspace atingido, algumas funcionalidades podem estar desabilitadas. Atualize para restaurar acesso completo.",
"RestrictedPlans": "Apenas o proprietário do workspace ou o administrador de faturamento podem alterar o plano de assinatura."
"RestrictedPlans": "Apenas o proprietário do workspace ou o administrador de faturamento podem alterar o plano de assinatura.",
"SubscriptionOperationFailed": "Falha na operação de assinatura",
"SubscriptionErrorMessage": "Algo deu errado. Tente novamente ou entre em contato com o suporte."
}
}
+3 -1
View File
@@ -56,6 +56,8 @@
"Of": "из",
"UpgradePlan": "Обновить план",
"LimitReached": "Достигнут лимит рабочего пространства, некоторые функции могут быть отключены. Обновите план для восстановления полного доступа.",
"RestrictedPlans": "Только владелец рабочего пространства или администратор биллинга может изменить план подписки."
"RestrictedPlans": "Только владелец рабочего пространства или администратор биллинга может изменить план подписки.",
"SubscriptionOperationFailed": "Ошибка операции с подпиской",
"SubscriptionErrorMessage": "Что-то пошло не так. Попробуйте ещё раз или обратитесь в поддержку."
}
}
+3 -1
View File
@@ -56,6 +56,8 @@
"Of": "/",
"UpgradePlan": "Planı Yükselt",
"LimitReached": "Çalışma alanı limiti aşıldı, bazı işlevler devre dışı bırakılabilir. Tam erişimi geri yüklemek için yükseltin.",
"RestrictedPlans": "Abonelik planını yalnızca çalışma alanı sahibi veya faturalama yöneticisi değiştirebilir."
"RestrictedPlans": "Abonelik planını yalnızca çalışma alanı sahibi veya faturalama yöneticisi değiştirebilir.",
"SubscriptionOperationFailed": "Abonelik işlemi başarısız oldu",
"SubscriptionErrorMessage": "Bir şeyler yanlış gitti. Lütfen tekrar deneyin veya destek ile iletişime geçin."
}
}
+3 -1
View File
@@ -56,6 +56,8 @@
"Of": "/",
"UpgradePlan": "升级计划",
"LimitReached": "已达到工作空间限制,某些功能可能被禁用。请升级以恢复完全访问权限。",
"RestrictedPlans": "只有工作空间所有者或计费管理员才能更改订阅计划。"
"RestrictedPlans": "只有工作空间所有者或计费管理员才能更改订阅计划。",
"SubscriptionOperationFailed": "订阅操作失败",
"SubscriptionErrorMessage": "出了点问题。请重试或联系支持。"
}
}
@@ -0,0 +1,31 @@
<!--
//
// Copyright © 2026 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
-->
<script lang="ts">
import { Notification, NotificationToast } from '@hcengineering/ui'
export let notification: Notification
export let onRemove: () => void
</script>
<NotificationToast title={notification.title} severity={notification.severity} onClose={onRemove}>
<svelte:fragment slot="content">
<div class="pl-1">
{notification.subTitle}
</div>
</svelte:fragment>
</NotificationToast>
@@ -16,7 +16,7 @@
import { type SubscriptionData, SubscriptionType } from '@hcengineering/account-client'
import { type SubscribeRequest, type CheckoutStatus } from '@hcengineering/payment-client'
import { Tier } from '@hcengineering/billing'
import { getMetadata } from '@hcengineering/platform'
import { getMetadata, translate } from '@hcengineering/platform'
import presentation, { getClient, MessageBox } from '@hcengineering/presentation'
import { type Ref, SortingOrder, UsageStatus } from '@hcengineering/core'
import {
@@ -29,7 +29,9 @@
themeStore,
getLocation,
navigate,
showPopup
showPopup,
addNotification,
NotificationSeverity
} from '@hcengineering/ui'
import { onMount, onDestroy } from 'svelte'
@@ -37,6 +39,7 @@
import { getAccountClient, getPaymentClient } from '../utils'
import UsageSection from './UsageSection.svelte'
import BillingErrorNotification from './BillingErrorNotification.svelte'
const client = getClient()
const paymentClient = getPaymentClient()
@@ -83,10 +86,21 @@
const { checkoutUrl } = await paymentClient.createSubscription(workspace, request)
window.location.href = checkoutUrl
} catch (error) {
console.error('error while upgrading plan:', error)
console.error('Error while upgrading plan:', error)
await showErrorNotification()
}
}
async function showErrorNotification (): Promise<void> {
addNotification(
await translate(plugin.string.SubscriptionOperationFailed, {}, $themeStore.language),
await translate(plugin.string.SubscriptionErrorMessage, {}, $themeStore.language),
BillingErrorNotification,
undefined,
NotificationSeverity.Error
)
}
async function showPlanChangeConfirmation (newPlan: string, newTier: Tier): Promise<void> {
if (currentTier === undefined) {
return
@@ -167,7 +181,8 @@
// It's a SubscriptionData - direct update successful
currentSubscription = updateResult
} catch (error) {
console.error('error updating subscription:', error)
console.error('Error updating subscription:', error)
await showErrorNotification()
} finally {
isUpdating = false
}
@@ -204,7 +219,8 @@
isCanceling = true
currentSubscription = await paymentClient.cancelSubscription(currentSubscription.id)
} catch (error) {
console.error('error canceling subscription:', error)
console.error('Error canceling subscription:', error)
await showErrorNotification()
} finally {
isCanceling = false
}
@@ -243,7 +259,8 @@
isUncanceling = true
currentSubscription = await paymentClient.uncancelSubscription(currentSubscription.id)
} catch (error) {
console.error('error uncanceling subscription:', error)
console.error('Error uncanceling subscription:', error)
await showErrorNotification()
} finally {
isUncanceling = false
}
@@ -261,7 +278,8 @@
const plan = currentSubscription?.plan
currentTier = plan !== undefined ? tierByPlan[plan] : undefined
} catch (err) {
console.error('error fetching current plan:', err)
console.error('Error fetching current plan:', err)
await showErrorNotification()
} finally {
loading = false
}
@@ -275,7 +293,8 @@
const workspaceInfo = await accountClient.getWorkspaceInfo(false)
usageInfo = workspaceInfo.usageInfo ?? null
} catch (err) {
console.error('error fetching usage stats:', err)
console.error('Error fetching usage stats:', err)
await showErrorNotification()
usageInfo = null
}
}
@@ -317,7 +336,8 @@
}, POLL_INTERVAL)
}
} catch (err) {
console.error('error polling checkout status:', err)
console.error('Error polling checkout status:', err)
await showErrorNotification()
// Retry on error (up to max attempts)
if (pollAttempts < MAX_POLL_ATTEMPTS) {
pollTimer = setTimeout(() => {
+3 -1
View File
@@ -52,6 +52,8 @@ export default mergeIds(billingId, billing, {
TrafficUsage: '' as IntlString,
Usage: '' as IntlString,
Of: '' as IntlString,
RestrictedPlans: '' as IntlString
RestrictedPlans: '' as IntlString,
SubscriptionOperationFailed: '' as IntlString,
SubscriptionErrorMessage: '' as IntlString
}
})