Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1741574eb2 | |||
| d78f9f2696 |
@@ -4,7 +4,6 @@ import {
|
|||||||
createTenantRequest,
|
createTenantRequest,
|
||||||
createTenantRequestPendingPayment,
|
createTenantRequestPendingPayment,
|
||||||
deletePendingPaymentRequest,
|
deletePendingPaymentRequest,
|
||||||
getOrgBillingConfig,
|
|
||||||
getTenantRequestById,
|
getTenantRequestById,
|
||||||
listTenantRequestsByOrgId,
|
listTenantRequestsByOrgId,
|
||||||
listActiveTenantRequestsByOrgId,
|
listActiveTenantRequestsByOrgId,
|
||||||
@@ -417,29 +416,6 @@ export async function POST(request: Request) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 9b (revised): a saved card on file IS the consent to
|
|
||||||
// auto-bill. There is no customer-facing "disable auto-pay"
|
|
||||||
// switch — ordering requires a card, full stop. The
|
|
||||||
// auto_charge_enabled flag is now an admin-only pause (used
|
|
||||||
// during disputes) and does NOT block a customer from ordering:
|
|
||||||
// if admin has paused recurring charges, that's a separate
|
|
||||||
// concern handled on the invoice side, not here. So the gate is
|
|
||||||
// simply: do they have a card on file?
|
|
||||||
const cfg = await getOrgBillingConfig(user.orgId);
|
|
||||||
const hasSavedCard = !!cfg.stripeDefaultPaymentMethodId;
|
|
||||||
if (!hasSavedCard) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
error:
|
|
||||||
"A payment card is required before ordering a new instance. " +
|
|
||||||
"Please save a card on /settings/billing, then submit again.",
|
|
||||||
code: "card_required",
|
|
||||||
redirectTo: "/settings/billing",
|
|
||||||
},
|
|
||||||
{ status: 402 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look up the setup fee. If it's 0 we skip the Checkout flow
|
// Look up the setup fee. If it's 0 we skip the Checkout flow
|
||||||
// entirely and create a normal pending request (same as the
|
// entirely and create a normal pending request (same as the
|
||||||
// pre-Phase-9b behaviour).
|
// pre-Phase-9b behaviour).
|
||||||
@@ -524,35 +500,33 @@ export async function POST(request: Request) {
|
|||||||
tenantRequest.id
|
tenantRequest.id
|
||||||
);
|
);
|
||||||
|
|
||||||
// Build the billing snapshot from the org's address (already
|
// Re-fetch orgBilling here: the variable at the top of POST was
|
||||||
// fetched above for the wizard's billing-address resolution).
|
// captured BEFORE the upsertOrgBilling call upstream (which fires
|
||||||
// The snapshot is what the invoice + Stripe customer use.
|
// when the wizard collected the address on first onboarding). For
|
||||||
//
|
// a brand-new user that initial fetch returned null; only by
|
||||||
// orgBilling MUST exist here: the auto-pay pre-check above
|
// re-fetching now do we get the row we just wrote. Existing
|
||||||
// requires a saved Stripe PaymentMethod, which can only be
|
// customers get the same orgBilling back either way.
|
||||||
// created via ensureStripeCustomerForOrg, which requires
|
const billingForOrder = await getOrgBilling(user.orgId);
|
||||||
// org_billing. If it's missing the system is in an inconsistent
|
if (!billingForOrder) {
|
||||||
// state we shouldn't paper over.
|
|
||||||
if (!orgBilling) {
|
|
||||||
console.error(
|
console.error(
|
||||||
`Paid-fee onboarding path reached without org_billing for org ${user.orgId} — auto-pay pre-check should have prevented this.`
|
`Paid-fee onboarding path: no org_billing for org ${user.orgId} even after upsert — wizard did not collect address?`
|
||||||
);
|
);
|
||||||
await deletePendingPaymentRequest(tenantRequest.id).catch(() => undefined);
|
await deletePendingPaymentRequest(tenantRequest.id).catch(() => undefined);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "Billing record missing. Please re-save your billing details on /settings/billing." },
|
{ error: "Billing record missing. Please re-save your billing details." },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const billingSnapshot: InvoiceBillingSnapshot = {
|
const billingSnapshot: InvoiceBillingSnapshot = {
|
||||||
companyName: orgBilling.companyName,
|
companyName: billingForOrder.companyName,
|
||||||
contactName: orgBilling.contactName ?? null,
|
contactName: billingForOrder.contactName ?? null,
|
||||||
streetAddress: orgBilling.streetAddress,
|
streetAddress: billingForOrder.streetAddress,
|
||||||
postalCode: orgBilling.postalCode,
|
postalCode: billingForOrder.postalCode,
|
||||||
city: orgBilling.city,
|
city: billingForOrder.city,
|
||||||
country: orgBilling.country,
|
country: billingForOrder.country,
|
||||||
vatNumber: orgBilling.vatNumber ?? null,
|
vatNumber: billingForOrder.vatNumber ?? null,
|
||||||
billingEmail: orgBilling.billingEmail,
|
billingEmail: billingForOrder.billingEmail,
|
||||||
notes: orgBilling.notes ?? null,
|
notes: billingForOrder.notes ?? null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Locale for the invoice + PDF — pick from the org's country
|
// Locale for the invoice + PDF — pick from the org's country
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useTranslations } from "next-intl";
|
|||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { PACKAGE_CATALOG, DEFAULT_PACKAGE_IDS, type PackageDef } from "@/lib/packages";
|
import { PACKAGE_CATALOG, DEFAULT_PACKAGE_IDS, type PackageDef } from "@/lib/packages";
|
||||||
import { isPersonalOrgName, displayOrgNameFor } from "@/lib/personal-org";
|
import { isPersonalOrgName, displayOrgNameFor } from "@/lib/personal-org";
|
||||||
|
import { THREEMA_GATEWAY } from "@/lib/threema-gateway-config";
|
||||||
import {
|
import {
|
||||||
configureStepSchema,
|
configureStepSchema,
|
||||||
billingStepSchema,
|
billingStepSchema,
|
||||||
@@ -192,11 +193,6 @@ export function OnboardingWizard({
|
|||||||
const [step, setStep] = useState<Step>(isEditing ? "configure" : "welcome");
|
const [step, setStep] = useState<Step>(isEditing ? "configure" : "welcome");
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
// Phase 9b: 402 from the onboarding endpoint indicates the org
|
|
||||||
// needs to set up auto-pay before ordering. We render a tailored
|
|
||||||
// error block with a clickable link to /settings/billing rather
|
|
||||||
// than the generic red message.
|
|
||||||
const [autoPayRequired, setAutoPayRequired] = useState(false);
|
|
||||||
const [advancedOpen, setAdvancedOpen] = useState(false);
|
const [advancedOpen, setAdvancedOpen] = useState(false);
|
||||||
// In edit mode we already have soulMd/agentsMd from the request;
|
// In edit mode we already have soulMd/agentsMd from the request;
|
||||||
// skip the workspace-defaults round trip that would overwrite them.
|
// skip the workspace-defaults round trip that would overwrite them.
|
||||||
@@ -444,7 +440,6 @@ export function OnboardingWizard({
|
|||||||
|
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
setError("");
|
setError("");
|
||||||
setAutoPayRequired(false);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Build secrets payload — only for packages that require them
|
// Build secrets payload — only for packages that require them
|
||||||
@@ -491,19 +486,6 @@ export function OnboardingWizard({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Phase 9b (revised): 402 means the org needs a saved card
|
|
||||||
// before ordering. There's no "enable auto-pay" step anymore
|
|
||||||
// — a card on file is all that's required.
|
|
||||||
if (res.status === 402) {
|
|
||||||
const data = await res.json().catch(() => ({}));
|
|
||||||
if (data?.code === "card_required" || data?.code === "auto_pay_required") {
|
|
||||||
setAutoPayRequired(true);
|
|
||||||
setError(t("cardRequiredError"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new Error(data.error || "Submission failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
throw new Error(data.error || "Submission failed");
|
throw new Error(data.error || "Submission failed");
|
||||||
@@ -811,8 +793,16 @@ export function OnboardingWizard({
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Inline credential inputs — expand when selected + requires secrets */}
|
{/* Inline expansion when selected — shows
|
||||||
{isSelected && pkg.requiresSecrets && (
|
instructions (if any), credential inputs
|
||||||
|
(if requiresSecrets), and the disclaimer
|
||||||
|
checkbox (if any). Threema for example
|
||||||
|
has no customer-entered secrets but has
|
||||||
|
instructions + a disclaimer to accept. */}
|
||||||
|
{isSelected &&
|
||||||
|
(pkg.requiresSecrets ||
|
||||||
|
pkg.instructionsKey ||
|
||||||
|
pkg.disclaimerKey) && (
|
||||||
<div className="border-t border-border px-3 py-3 space-y-3 bg-surface-1/50">
|
<div className="border-t border-border px-3 py-3 space-y-3 bg-surface-1/50">
|
||||||
{pkg.instructionsKey && (
|
{pkg.instructionsKey && (
|
||||||
<div className="bg-surface-2 border border-border rounded-lg p-3 text-xs text-text-secondary leading-relaxed whitespace-pre-line">
|
<div className="bg-surface-2 border border-border rounded-lg p-3 text-xs text-text-secondary leading-relaxed whitespace-pre-line">
|
||||||
@@ -825,6 +815,40 @@ export function OnboardingWizard({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Threema: show the bot's Threema ID
|
||||||
|
and QR right here in the wizard. The
|
||||||
|
instructions text refers to a QR
|
||||||
|
that isn't visible until after
|
||||||
|
provisioning — without this block
|
||||||
|
the message is confusing. The QR is
|
||||||
|
the platform's shared gateway QR
|
||||||
|
(*AIAGENT), identical for every
|
||||||
|
tenant, so we can render it before
|
||||||
|
the tenant even exists. */}
|
||||||
|
{pkg.id === "threema" && (
|
||||||
|
<div className="rounded-lg border border-accent/30 bg-surface-1 p-3 flex items-start gap-3">
|
||||||
|
<div className="bg-white p-1.5 rounded-md shrink-0">
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img
|
||||||
|
src={THREEMA_GATEWAY.qrCodePath}
|
||||||
|
alt={`QR code for ${THREEMA_GATEWAY.displayName}`}
|
||||||
|
width={96}
|
||||||
|
height={96}
|
||||||
|
style={{ display: "block" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-text-secondary leading-relaxed">
|
||||||
|
<div className="text-text-primary font-medium mb-1">
|
||||||
|
{tPkg("threemaBotIdHeading")}
|
||||||
|
</div>
|
||||||
|
<div className="font-mono text-sm text-accent mb-2">
|
||||||
|
{THREEMA_GATEWAY.displayName}
|
||||||
|
</div>
|
||||||
|
<div>{tPkg("threemaBotIdHint")}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{(pkg.secrets || []).map((field) => (
|
{(pkg.secrets || []).map((field) => (
|
||||||
<label key={field.key} className="block">
|
<label key={field.key} className="block">
|
||||||
<span className="text-xs text-text-secondary mb-1 block">
|
<span className="text-xs text-text-secondary mb-1 block">
|
||||||
@@ -1275,17 +1299,6 @@ export function OnboardingWizard({
|
|||||||
{error && (
|
{error && (
|
||||||
<div className="text-xs text-red-400 bg-red-400/10 border border-red-400/20 rounded-lg px-3 py-2 mt-4">
|
<div className="text-xs text-red-400 bg-red-400/10 border border-red-400/20 rounded-lg px-3 py-2 mt-4">
|
||||||
{error}
|
{error}
|
||||||
{autoPayRequired && (
|
|
||||||
<>
|
|
||||||
{" "}
|
|
||||||
<a
|
|
||||||
href="/settings/billing"
|
|
||||||
className="underline font-medium text-red-300 hover:text-red-200"
|
|
||||||
>
|
|
||||||
{t("autoPaySetupLink")}
|
|
||||||
</a>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type {
|
|||||||
SkillPricing,
|
SkillPricing,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
import { SkillCostDialog } from "./skill-cost-dialog";
|
import { SkillCostDialog } from "./skill-cost-dialog";
|
||||||
|
import { ThreemaQrModal } from "@/components/channel-users/threema-qr-modal";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
pkg: PackageDef;
|
pkg: PackageDef;
|
||||||
@@ -51,6 +52,11 @@ export function PackageCard({
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
// Phase 2.5: cost-disclosure flow + activation-request flow.
|
// Phase 2.5: cost-disclosure flow + activation-request flow.
|
||||||
const [showCostDialog, setShowCostDialog] = useState(false);
|
const [showCostDialog, setShowCostDialog] = useState(false);
|
||||||
|
// Threema: after a successful enable on customProvisioning, surface
|
||||||
|
// the gateway QR + bot Threema ID so the customer immediately knows
|
||||||
|
// how to add the assistant to their Threema contacts. Without this,
|
||||||
|
// the toggle just flips silently with no actionable info.
|
||||||
|
const [showThreemaInfo, setShowThreemaInfo] = useState(false);
|
||||||
const isPriced =
|
const isPriced =
|
||||||
(pricing?.dailyPriceChf ?? 0) > 0 || (pricing?.setupFeeChf ?? 0) > 0;
|
(pricing?.dailyPriceChf ?? 0) > 0 || (pricing?.setupFeeChf ?? 0) > 0;
|
||||||
|
|
||||||
@@ -79,6 +85,14 @@ export function PackageCard({
|
|||||||
throw new Error(err.error || `Provisioning failed (HTTP ${provRes.status})`);
|
throw new Error(err.error || `Provisioning failed (HTTP ${provRes.status})`);
|
||||||
}
|
}
|
||||||
await togglePackage(true);
|
await togglePackage(true);
|
||||||
|
// For Threema specifically: now that the relay's minted the
|
||||||
|
// per-tenant token and the package is enabled, show the
|
||||||
|
// gateway QR + bot Threema ID so the customer can add the
|
||||||
|
// assistant to their Threema contacts straight away. Other
|
||||||
|
// customProvisioning packages don't need this confirmation.
|
||||||
|
if (pkg.id === "threema") {
|
||||||
|
setShowThreemaInfo(true);
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
setError(e.message);
|
setError(e.message);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -320,6 +334,16 @@ export function PackageCard({
|
|||||||
busy={saving}
|
busy={saving}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Threema: post-enable confirmation showing the gateway QR
|
||||||
|
and bot Threema ID. Only rendered for the threema package
|
||||||
|
and only after a successful enable. The same modal is also
|
||||||
|
reachable later on the channel-users page. */}
|
||||||
|
{pkg.id === "threema" && (
|
||||||
|
<ThreemaQrModal
|
||||||
|
open={showThreemaInfo}
|
||||||
|
onClose={() => setShowThreemaInfo(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{showModal && (
|
{showModal && (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||||
<div className="w-full max-w-md bg-surface-1 border border-border rounded-2xl p-6 space-y-4 shadow-2xl shadow-black/40">
|
<div className="w-full max-w-md bg-surface-1 border border-border rounded-2xl p-6 space-y-4 shadow-2xl shadow-black/40">
|
||||||
|
|||||||
@@ -123,11 +123,8 @@
|
|||||||
"billingVatHelp": "Ihre registrierte MWST-Nummer. Falls Ihre Firma von der MWST befreit ist, leer lassen und in den Notizen erläutern.",
|
"billingVatHelp": "Ihre registrierte MWST-Nummer. Falls Ihre Firma von der MWST befreit ist, leer lassen und in den Notizen erläutern.",
|
||||||
"billingNotesPlaceholderPersonal": "Was wir wissen sollten — bevorzugte Zahlungsart, Rechnungsreferenz, etc.",
|
"billingNotesPlaceholderPersonal": "Was wir wissen sollten — bevorzugte Zahlungsart, Rechnungsreferenz, etc.",
|
||||||
"reviewContactPersonPrefix": "z.Hd.",
|
"reviewContactPersonPrefix": "z.Hd.",
|
||||||
"autoPayRequiredError": "Auto-Zahlung muss vor der Bestellung einer neuen Instanz eingerichtet sein. Richten Sie zuerst die Auto-Zahlung ein und senden Sie das Formular erneut.",
|
|
||||||
"autoPaySetupLink": "Karte hinzufügen →",
|
|
||||||
"setupFeeNoticeHeading": "Einrichtungsgebühr wird beim Senden belastet",
|
"setupFeeNoticeHeading": "Einrichtungsgebühr wird beim Senden belastet",
|
||||||
"setupFeeNoticeBody": "Mit dem nächsten Klick werden Sie zu Stripe weitergeleitet, um die einmalige Einrichtungsgebühr für diese Instanz zu bezahlen. Anschliessend gelangen Sie direkt zurück zum Dashboard. Die Instanz startet erst nach Admin-Freigabe — monatliche Gebühren beginnen ab dem Freigabedatum.",
|
"setupFeeNoticeBody": "Mit dem nächsten Klick werden Sie zu Stripe weitergeleitet, um Ihre Zahlungsdetails einzugeben und die einmalige Einrichtungsgebühr zu bezahlen. Ihre Karte wird automatisch für die zukünftige monatliche Abrechnung gespeichert. Anschliessend gelangen Sie direkt zurück zum Dashboard. Die Instanz startet erst nach Admin-Freigabe — monatliche Gebühren beginnen ab dem Freigabedatum.",
|
||||||
"cardRequiredError": "Vor der Bestellung ist eine Zahlungskarte erforderlich. Fügen Sie eine Karte hinzu und senden Sie erneut.",
|
|
||||||
"setupFeeAmountLabel": "Einmalige Einrichtungsgebühr",
|
"setupFeeAmountLabel": "Einmalige Einrichtungsgebühr",
|
||||||
"setupFeePlusVat": "+ MwSt."
|
"setupFeePlusVat": "+ MwSt."
|
||||||
},
|
},
|
||||||
@@ -327,7 +324,9 @@
|
|||||||
"tryAgain": "Erneut versuchen",
|
"tryAgain": "Erneut versuchen",
|
||||||
"credentialsSaved": "Zugangsdaten gespeichert",
|
"credentialsSaved": "Zugangsdaten gespeichert",
|
||||||
"credentialsSavedTip": "Die eingegebenen Zugangsdaten sind sicher gespeichert und werden verwendet, sobald die Aktivierung vom Admin genehmigt wurde. Sie müssen sie nicht erneut eingeben.",
|
"credentialsSavedTip": "Die eingegebenen Zugangsdaten sind sicher gespeichert und werden verwendet, sobald die Aktivierung vom Admin genehmigt wurde. Sie müssen sie nicht erneut eingeben.",
|
||||||
"recommended": "Empfohlen"
|
"recommended": "Empfohlen",
|
||||||
|
"threemaBotIdHeading": "Bot-Threema-ID",
|
||||||
|
"threemaBotIdHint": "Sobald Ihr Mandant freigegeben ist, scannen Sie diesen QR-Code mit Threema, um den Assistenten zu Ihren Kontakten hinzuzufügen. Der QR-Code ist für jeden PieCed-Mandanten identisch — Sie können ihn schon jetzt speichern."
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"title": "Plattform-Admin",
|
"title": "Plattform-Admin",
|
||||||
|
|||||||
@@ -123,11 +123,8 @@
|
|||||||
"billingVatHelp": "Your registered VAT identifier. If your company is VAT-exempt, leave blank and explain in the notes field.",
|
"billingVatHelp": "Your registered VAT identifier. If your company is VAT-exempt, leave blank and explain in the notes field.",
|
||||||
"billingNotesPlaceholderPersonal": "Anything we should know — preferred payment method, billing reference, etc.",
|
"billingNotesPlaceholderPersonal": "Anything we should know — preferred payment method, billing reference, etc.",
|
||||||
"reviewContactPersonPrefix": "Attn:",
|
"reviewContactPersonPrefix": "Attn:",
|
||||||
"autoPayRequiredError": "Auto-pay is required before ordering a new instance. Set up auto-pay first, then submit again.",
|
|
||||||
"autoPaySetupLink": "Add a card →",
|
|
||||||
"setupFeeNoticeHeading": "Setup fee will be charged on submit",
|
"setupFeeNoticeHeading": "Setup fee will be charged on submit",
|
||||||
"setupFeeNoticeBody": "On the next click you'll be redirected to Stripe to pay the one-time setup fee for this instance. You'll be brought back to your dashboard immediately afterwards. The instance starts running only after admin approval — monthly fees begin from the approval date.",
|
"setupFeeNoticeBody": "On the next click you'll be redirected to Stripe to enter your payment details and pay the one-time setup fee. Your card is saved automatically for future monthly billing. You'll be brought back to your dashboard immediately afterwards. The instance starts running only after admin approval — monthly fees begin from the approval date.",
|
||||||
"cardRequiredError": "A payment card is required before ordering. Add a card, then submit again.",
|
|
||||||
"setupFeeAmountLabel": "One-time setup fee",
|
"setupFeeAmountLabel": "One-time setup fee",
|
||||||
"setupFeePlusVat": "+ VAT"
|
"setupFeePlusVat": "+ VAT"
|
||||||
},
|
},
|
||||||
@@ -327,7 +324,9 @@
|
|||||||
"tryAgain": "Try again",
|
"tryAgain": "Try again",
|
||||||
"credentialsSaved": "credentials saved",
|
"credentialsSaved": "credentials saved",
|
||||||
"credentialsSavedTip": "The credentials you entered are securely stored and will be used as soon as admin approves the activation. You don't need to re-enter them.",
|
"credentialsSavedTip": "The credentials you entered are securely stored and will be used as soon as admin approves the activation. You don't need to re-enter them.",
|
||||||
"recommended": "Recommended"
|
"recommended": "Recommended",
|
||||||
|
"threemaBotIdHeading": "Bot Threema ID",
|
||||||
|
"threemaBotIdHint": "Once your tenant is approved, scan this QR with Threema to add the assistant to your contacts. The QR is the same for every PieCed tenant — you can save it now."
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"title": "Platform Admin",
|
"title": "Platform Admin",
|
||||||
|
|||||||
@@ -123,11 +123,8 @@
|
|||||||
"billingVatHelp": "Votre identifiant TVA enregistré. Si votre entreprise est exonérée de TVA, laissez vide et précisez dans les notes.",
|
"billingVatHelp": "Votre identifiant TVA enregistré. Si votre entreprise est exonérée de TVA, laissez vide et précisez dans les notes.",
|
||||||
"billingNotesPlaceholderPersonal": "Tout ce que nous devons savoir — moyen de paiement préféré, référence de facturation, etc.",
|
"billingNotesPlaceholderPersonal": "Tout ce que nous devons savoir — moyen de paiement préféré, référence de facturation, etc.",
|
||||||
"reviewContactPersonPrefix": "À l'attention de",
|
"reviewContactPersonPrefix": "À l'attention de",
|
||||||
"autoPayRequiredError": "Le paiement automatique est requis avant de commander une nouvelle instance. Configurez d'abord le paiement automatique, puis soumettez à nouveau.",
|
|
||||||
"autoPaySetupLink": "Ajouter une carte →",
|
|
||||||
"setupFeeNoticeHeading": "Les frais de configuration seront facturés à l'envoi",
|
"setupFeeNoticeHeading": "Les frais de configuration seront facturés à l'envoi",
|
||||||
"setupFeeNoticeBody": "Au prochain clic vous serez redirigé vers Stripe pour régler les frais d'activation uniques de cette instance. Vous reviendrez immédiatement au tableau de bord. L'instance ne démarre qu'après validation par l'administrateur — les frais mensuels commencent à compter de la date de validation.",
|
"setupFeeNoticeBody": "Au prochain clic vous serez redirigé vers Stripe pour saisir vos coordonnées de paiement et régler les frais d'activation uniques. Votre carte est enregistrée automatiquement pour la facturation mensuelle future. Vous reviendrez immédiatement au tableau de bord. L'instance ne démarre qu'après validation par l'administrateur — les frais mensuels commencent à compter de la date de validation.",
|
||||||
"cardRequiredError": "Une carte de paiement est requise avant de commander. Ajoutez une carte, puis soumettez à nouveau.",
|
|
||||||
"setupFeeAmountLabel": "Frais d'activation uniques",
|
"setupFeeAmountLabel": "Frais d'activation uniques",
|
||||||
"setupFeePlusVat": "+ TVA"
|
"setupFeePlusVat": "+ TVA"
|
||||||
},
|
},
|
||||||
@@ -327,7 +324,9 @@
|
|||||||
"tryAgain": "Réessayer",
|
"tryAgain": "Réessayer",
|
||||||
"credentialsSaved": "identifiants enregistrés",
|
"credentialsSaved": "identifiants enregistrés",
|
||||||
"credentialsSavedTip": "Les identifiants saisis sont stockés en sécurité et seront utilisés dès l'approbation de l'activation par l'administrateur. Vous n'avez pas besoin de les ressaisir.",
|
"credentialsSavedTip": "Les identifiants saisis sont stockés en sécurité et seront utilisés dès l'approbation de l'activation par l'administrateur. Vous n'avez pas besoin de les ressaisir.",
|
||||||
"recommended": "Recommandé"
|
"recommended": "Recommandé",
|
||||||
|
"threemaBotIdHeading": "ID Threema du bot",
|
||||||
|
"threemaBotIdHint": "Une fois votre tenant approuvé, scannez ce QR avec Threema pour ajouter l'assistant à vos contacts. Le QR est identique pour chaque tenant PieCed — vous pouvez l'enregistrer dès maintenant."
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"title": "Admin plateforme",
|
"title": "Admin plateforme",
|
||||||
|
|||||||
@@ -123,11 +123,8 @@
|
|||||||
"billingVatHelp": "Il tuo identificativo IVA registrato. Se la tua azienda è esente IVA, lascia vuoto e spiega nelle note.",
|
"billingVatHelp": "Il tuo identificativo IVA registrato. Se la tua azienda è esente IVA, lascia vuoto e spiega nelle note.",
|
||||||
"billingNotesPlaceholderPersonal": "Qualsiasi cosa dovremmo sapere — metodo di pagamento preferito, riferimento per fatturazione, ecc.",
|
"billingNotesPlaceholderPersonal": "Qualsiasi cosa dovremmo sapere — metodo di pagamento preferito, riferimento per fatturazione, ecc.",
|
||||||
"reviewContactPersonPrefix": "c.a.",
|
"reviewContactPersonPrefix": "c.a.",
|
||||||
"autoPayRequiredError": "Il pagamento automatico è obbligatorio prima di ordinare una nuova istanza. Configuri prima il pagamento automatico, poi invii nuovamente.",
|
|
||||||
"autoPaySetupLink": "Aggiungi una carta →",
|
|
||||||
"setupFeeNoticeHeading": "Le spese di attivazione saranno addebitate all'invio",
|
"setupFeeNoticeHeading": "Le spese di attivazione saranno addebitate all'invio",
|
||||||
"setupFeeNoticeBody": "Al clic successivo sarà reindirizzato a Stripe per pagare le spese di attivazione una tantum per questa istanza. Tornerà subito alla dashboard. L'istanza si avvia solo dopo l'approvazione dell'admin — i canoni mensili decorrono dalla data di approvazione.",
|
"setupFeeNoticeBody": "Al clic successivo sarà reindirizzato a Stripe per inserire i dati di pagamento e pagare le spese di attivazione una tantum. La sua carta viene salvata automaticamente per la fatturazione mensile futura. Tornerà subito alla dashboard. L'istanza si avvia solo dopo l'approvazione dell'admin — i canoni mensili decorrono dalla data di approvazione.",
|
||||||
"cardRequiredError": "Prima di ordinare è necessaria una carta di pagamento. Aggiunga una carta e invii nuovamente.",
|
|
||||||
"setupFeeAmountLabel": "Spese di attivazione una tantum",
|
"setupFeeAmountLabel": "Spese di attivazione una tantum",
|
||||||
"setupFeePlusVat": "+ IVA"
|
"setupFeePlusVat": "+ IVA"
|
||||||
},
|
},
|
||||||
@@ -327,7 +324,9 @@
|
|||||||
"tryAgain": "Riprova",
|
"tryAgain": "Riprova",
|
||||||
"credentialsSaved": "credenziali salvate",
|
"credentialsSaved": "credenziali salvate",
|
||||||
"credentialsSavedTip": "Le credenziali inserite sono memorizzate in modo sicuro e saranno utilizzate non appena l'attivazione viene approvata dall'amministratore. Non è necessario reinserirle.",
|
"credentialsSavedTip": "Le credenziali inserite sono memorizzate in modo sicuro e saranno utilizzate non appena l'attivazione viene approvata dall'amministratore. Non è necessario reinserirle.",
|
||||||
"recommended": "Consigliato"
|
"recommended": "Consigliato",
|
||||||
|
"threemaBotIdHeading": "ID Threema del bot",
|
||||||
|
"threemaBotIdHint": "Una volta approvato il suo tenant, scansioni questo QR con Threema per aggiungere l'assistente ai suoi contatti. Il QR è identico per ogni tenant PieCed — può salvarlo già adesso."
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"title": "Admin piattaforma",
|
"title": "Admin piattaforma",
|
||||||
|
|||||||
Reference in New Issue
Block a user