mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 18:37:24 +00:00
fix(cloud): JA-21 blocker reads billing's default cap (ENTITLEMENTS-C): Turn on overage without a guessed $25; cap suggestion adds billing's default
This commit is contained in:
@@ -38,7 +38,7 @@ afterEach(() => {
|
||||
|
||||
function renderBlocker(
|
||||
kind: BillingBlockKind,
|
||||
{ canManageBilling = true, capCents = 2500 } = {}
|
||||
{ canManageBilling = true, capCents = 2500, offers = true } = {}
|
||||
): {
|
||||
onContinue: ReturnType<typeof vi.fn>
|
||||
onBackToDraft: ReturnType<typeof vi.fn>
|
||||
@@ -52,6 +52,7 @@ function renderBlocker(
|
||||
canManageBilling={canManageBilling}
|
||||
entitlement={{
|
||||
...sampleTeam,
|
||||
offers: offers ? sampleTeam.offers : null,
|
||||
overage: { ...sampleTeam.overage, spendCapCents: capCents }
|
||||
}}
|
||||
onContinue={onContinue}
|
||||
@@ -74,6 +75,14 @@ describe('billing blocker on the creating sheet (JA-21)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("without billing's default cap: plain Turn on overage, and billing applies its default", async () => {
|
||||
setOverage.mockResolvedValue({ status: 'ok', entitlement: sampleTeam })
|
||||
const { onContinue } = renderBlocker('hoursUsed', { offers: false })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Turn on overage' }))
|
||||
await waitFor(() => expect(onContinue).toHaveBeenCalledOnce())
|
||||
expect(setOverage).toHaveBeenCalledWith({ enabled: true })
|
||||
})
|
||||
|
||||
it('a failed save shows inline and does not continue', async () => {
|
||||
setOverage.mockResolvedValue({ status: 'failed', error: 'boom' })
|
||||
const { onContinue } = renderBlocker('hoursUsed')
|
||||
|
||||
@@ -15,11 +15,6 @@ import { formatCents } from '../billing/plans/plans-pricing'
|
||||
/** The four ways billing can refuse a create (JA-21 progressive billing). */
|
||||
export type BillingBlockKind = 'plan' | 'hoursUsed' | 'spendCap' | 'payment'
|
||||
|
||||
/** Overage turned on from the blocker: the same $25 cap as Plan & billing's off state. */
|
||||
export const BLOCKER_OVERAGE_CAP_CENTS = 2500
|
||||
/** Raising a reached cap suggests this much more. */
|
||||
export const BLOCKER_CAP_STEP_CENTS = 2500
|
||||
|
||||
export type BillingBlockerPhase =
|
||||
| { kind: 'idle' }
|
||||
| { kind: 'saving' }
|
||||
@@ -80,6 +75,8 @@ export type VmBuildBillingBlockerViewProps = {
|
||||
phase: BillingBlockerPhase
|
||||
/** Spend cap reached: the field's starting value, in whole dollars. */
|
||||
suggestedCapDollars: number
|
||||
/** Billing's default cap for "Turn on with a $X cap"; null: plain "Turn on overage" (ENTITLEMENTS-C). */
|
||||
defaultCapCents: number | null
|
||||
onTurnOnOverage: () => void
|
||||
onRaiseCap: (dollars: number) => void
|
||||
onComparePlans: () => void
|
||||
@@ -97,6 +94,7 @@ export function VmBuildBillingBlockerView({
|
||||
canManageBilling,
|
||||
phase,
|
||||
suggestedCapDollars,
|
||||
defaultCapCents,
|
||||
onTurnOnOverage,
|
||||
onRaiseCap,
|
||||
onComparePlans,
|
||||
@@ -205,9 +203,11 @@ export function VmBuildBillingBlockerView({
|
||||
onClick={saving ? undefined : onTurnOnOverage}
|
||||
>
|
||||
{saving ? <Loader2 className="animate-spin" /> : null}
|
||||
{t('auto.components.billing.overage.turnOn', 'Turn on with a {{cap}} cap', {
|
||||
cap: formatCents(BLOCKER_OVERAGE_CAP_CENTS, locale)
|
||||
})}
|
||||
{defaultCapCents === null
|
||||
? t('auto.components.billing.overage.turnOnNoCap', 'Turn on overage')
|
||||
: t('auto.components.billing.overage.turnOn', 'Turn on with a {{cap}} cap', {
|
||||
cap: formatCents(defaultCapCents, locale)
|
||||
})}
|
||||
</Button>
|
||||
) : null}
|
||||
{kind === 'payment' ? (
|
||||
@@ -278,14 +278,19 @@ export function VmBuildBillingBlocker({
|
||||
const [phase, setPhase] = React.useState<BillingBlockerPhase>({
|
||||
kind: 'idle'
|
||||
})
|
||||
// Plan numbers come from billing, never guessed (ENTITLEMENTS-C): a reached cap suggests one more
|
||||
// default cap on top; without billing's default, double the current cap.
|
||||
const currentCap = entitlement?.overage.spendCapCents ?? 0
|
||||
const suggestedCapDollars = Math.round((currentCap + BLOCKER_CAP_STEP_CENTS) / 100)
|
||||
const defaultCapCents = entitlement?.offers?.team.defaultSpendCapCents ?? null
|
||||
const suggestedCapDollars = Math.round((currentCap + (defaultCapCents ?? currentCap)) / 100)
|
||||
const maxCap = entitlement?.offers?.team.maxSpendCapCents ?? null
|
||||
|
||||
const saveOverage = async (spendCapCents: number): Promise<void> => {
|
||||
const saveOverage = async (spendCapCents: number | null): Promise<void> => {
|
||||
setPhase({ kind: 'saving' })
|
||||
const outcome = await window.api.orcaProfiles
|
||||
.billingSetOverage({ enabled: true, spendCapCents })
|
||||
.billingSetOverage(
|
||||
spendCapCents === null ? { enabled: true } : { enabled: true, spendCapCents }
|
||||
)
|
||||
.catch((error: unknown): OrcaBillingOverageResult => ({
|
||||
status: 'failed',
|
||||
error: String(error)
|
||||
@@ -336,7 +341,8 @@ export function VmBuildBillingBlocker({
|
||||
canManageBilling={canManageBilling}
|
||||
phase={phase}
|
||||
suggestedCapDollars={suggestedCapDollars}
|
||||
onTurnOnOverage={() => void saveOverage(BLOCKER_OVERAGE_CAP_CENTS)}
|
||||
defaultCapCents={defaultCapCents}
|
||||
onTurnOnOverage={() => void saveOverage(defaultCapCents)}
|
||||
onRaiseCap={(dollars) => void saveOverage(dollars * 100)}
|
||||
onComparePlans={() => {
|
||||
// Once paid, the plans page closes and continues this create.
|
||||
|
||||
Reference in New Issue
Block a user