mirror of
https://github.com/graphql-hive/console
synced 2026-05-23 09:08:34 +00:00
Bug fixes for subscription management page (#4326)
This commit is contained in:
parent
975f4b6247
commit
fcf4fe03de
5 changed files with 94 additions and 55 deletions
|
|
@ -8,6 +8,7 @@ export default gql`
|
|||
|
||||
type BillingConfiguration {
|
||||
hasActiveSubscription: Boolean!
|
||||
canUpdateSubscription: Boolean!
|
||||
hasPaymentIssues: Boolean!
|
||||
paymentMethod: BillingPaymentMethod
|
||||
billingAddress: BillingDetails
|
||||
|
|
|
|||
|
|
@ -40,6 +40,18 @@ export const resolvers: BillingModule.Resolvers = {
|
|||
Organization: {
|
||||
plan: org => (org.billingPlan || 'HOBBY') as BillingPlanType,
|
||||
billingConfiguration: async (org, _args, { injector }) => {
|
||||
if (org.billingPlan === 'ENTERPRISE') {
|
||||
return {
|
||||
hasActiveSubscription: true,
|
||||
canUpdateSubscription: false,
|
||||
hasPaymentIssues: false,
|
||||
paymentMethod: null,
|
||||
billingAddress: null,
|
||||
invoices: null,
|
||||
upcomingInvoice: null,
|
||||
};
|
||||
}
|
||||
|
||||
const billingRecord = await injector
|
||||
.get(BillingProvider)
|
||||
.getOrganizationBillingParticipant({ organization: org.id });
|
||||
|
|
@ -47,6 +59,21 @@ export const resolvers: BillingModule.Resolvers = {
|
|||
if (!billingRecord) {
|
||||
return {
|
||||
hasActiveSubscription: false,
|
||||
canUpdateSubscription: true,
|
||||
hasPaymentIssues: false,
|
||||
paymentMethod: null,
|
||||
billingAddress: null,
|
||||
invoices: null,
|
||||
upcomingInvoice: null,
|
||||
};
|
||||
}
|
||||
|
||||
// This is a special case where customer is on Pro and doesn't have a record for external billing.
|
||||
// This happens when the customer is paying through an external system and not through Stripe.
|
||||
if (org.billingPlan === 'PRO' && billingRecord.externalBillingReference === 'wire') {
|
||||
return {
|
||||
hasActiveSubscription: true,
|
||||
canUpdateSubscription: false,
|
||||
hasPaymentIssues: false,
|
||||
paymentMethod: null,
|
||||
billingAddress: null,
|
||||
|
|
@ -62,6 +89,7 @@ export const resolvers: BillingModule.Resolvers = {
|
|||
if (!subscriptionInfo) {
|
||||
return {
|
||||
hasActiveSubscription: false,
|
||||
canUpdateSubscription: true,
|
||||
hasPaymentIssues: false,
|
||||
paymentMethod: null,
|
||||
billingAddress: null,
|
||||
|
|
@ -85,6 +113,7 @@ export const resolvers: BillingModule.Resolvers = {
|
|||
|
||||
return {
|
||||
hasActiveSubscription: subscriptionInfo.subscription !== null,
|
||||
canUpdateSubscription: subscriptionInfo.subscription !== null,
|
||||
hasPaymentIssues,
|
||||
paymentMethod: subscriptionInfo.paymentMethod?.card || null,
|
||||
billingAddress: subscriptionInfo.paymentMethod?.billing_details || null,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ const ManageSubscriptionInner_OrganizationFragment = graphql(`
|
|||
}
|
||||
billingConfiguration {
|
||||
hasPaymentIssues
|
||||
canUpdateSubscription
|
||||
paymentMethod {
|
||||
__typename
|
||||
}
|
||||
|
|
@ -324,7 +325,10 @@ function Inner(props: {
|
|||
<Card className="w-full">
|
||||
<Heading className="mb-4">Choose Your Plan</Heading>
|
||||
<BillingPlanPicker
|
||||
disabled={organization.plan === BillingPlanType.Enterprise}
|
||||
disabled={
|
||||
organization.plan === BillingPlanType.Enterprise ||
|
||||
!organization.billingConfiguration.canUpdateSubscription
|
||||
}
|
||||
activePlan={organization.plan}
|
||||
value={plan}
|
||||
plans={billingPlans}
|
||||
|
|
@ -347,62 +351,66 @@ function Inner(props: {
|
|||
</PlanSummary>
|
||||
</div>
|
||||
|
||||
{plan === BillingPlanType.Pro && (
|
||||
<div className="my-8 w-1/2">
|
||||
<Heading>Define your reserved volume</Heading>
|
||||
<p className="text-sm text-gray-500">
|
||||
Pro plan requires to defined quota of reported operations.
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
Pick a volume a little higher than you think you'll need to avoid being rate
|
||||
limited.
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
Don't worry, you can always adjust it later.
|
||||
</p>
|
||||
<div className="mt-5 pl-2.5">
|
||||
<Slider
|
||||
min={1}
|
||||
max={300}
|
||||
disabled={isFetching}
|
||||
value={[operationsRateLimit]}
|
||||
onValueChange={onOperationsRateLimitChange}
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<span>1M</span>
|
||||
<span>100M</span>
|
||||
<span>200M</span>
|
||||
<span>300M</span>
|
||||
{plan === BillingPlanType.Pro &&
|
||||
organization.billingConfiguration.canUpdateSubscription && (
|
||||
<div className="my-8 w-1/2">
|
||||
<Heading>Define your reserved volume</Heading>
|
||||
<p className="text-sm text-gray-500">
|
||||
Pro plan requires to defined quota of reported operations.
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
Pick a volume a little higher than you think you'll need to avoid being rate
|
||||
limited.
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
Don't worry, you can always adjust it later.
|
||||
</p>
|
||||
<div className="mt-5 pl-2.5">
|
||||
<Slider
|
||||
min={1}
|
||||
max={300}
|
||||
disabled={isFetching}
|
||||
value={[operationsRateLimit]}
|
||||
onValueChange={onOperationsRateLimitChange}
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<span>1M</span>
|
||||
<span>100M</span>
|
||||
<span>200M</span>
|
||||
<span>300M</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
<div className="my-8 flex flex-row gap-6">
|
||||
<BillingPaymentMethod
|
||||
className="w-1/2"
|
||||
plan={selectedPlan.planType}
|
||||
organization={organization}
|
||||
onValidationChange={setPaymentDetailsValid}
|
||||
/>
|
||||
<div className="w-1/2">
|
||||
{plan === BillingPlanType.Pro && plan !== organization.plan ? (
|
||||
<div>
|
||||
<Heading className="mb-3">Discount</Heading>
|
||||
<Input
|
||||
className="w-full"
|
||||
size="medium"
|
||||
value={couponCode ?? ''}
|
||||
disabled={isFetching}
|
||||
onChange={e => setCouponCode(e.target.value)}
|
||||
placeholder="Code"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{error && <QueryError showError error={error} />}
|
||||
<div>{renderActions()}</div>
|
||||
|
||||
{organization.billingConfiguration.canUpdateSubscription ? (
|
||||
<div className="my-8 flex flex-row gap-6">
|
||||
<BillingPaymentMethod
|
||||
className="w-1/2"
|
||||
plan={selectedPlan.planType}
|
||||
organization={organization}
|
||||
onValidationChange={setPaymentDetailsValid}
|
||||
/>
|
||||
<div className="w-1/2">
|
||||
{plan === BillingPlanType.Pro && plan !== organization.plan ? (
|
||||
<div>
|
||||
<Heading className="mb-3">Discount</Heading>
|
||||
<Input
|
||||
className="w-full"
|
||||
size="medium"
|
||||
value={couponCode ?? ''}
|
||||
disabled={isFetching}
|
||||
onChange={e => setCouponCode(e.target.value)}
|
||||
placeholder="Code"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ const SubscriptionPage_OrganizationFragment = graphql(`
|
|||
}
|
||||
billingConfiguration {
|
||||
hasPaymentIssues
|
||||
canUpdateSubscription
|
||||
invoices {
|
||||
id
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,9 +114,9 @@ export const BillingPaymentMethod = ({
|
|||
{mutation.fetching ? (
|
||||
'Loading...'
|
||||
) : (
|
||||
<>
|
||||
<ExternalLinkIcon /> Stripe Billing Dashboard
|
||||
</>
|
||||
<div className="flex items-center">
|
||||
<ExternalLinkIcon className="mr-1" /> Stripe Billing Dashboard
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue