Bug fixes for subscription management page (#4326)

This commit is contained in:
Dotan Simha 2024-03-25 10:50:26 +02:00 committed by GitHub
parent 975f4b6247
commit fcf4fe03de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 94 additions and 55 deletions

View file

@ -8,6 +8,7 @@ export default gql`
type BillingConfiguration {
hasActiveSubscription: Boolean!
canUpdateSubscription: Boolean!
hasPaymentIssues: Boolean!
paymentMethod: BillingPaymentMethod
billingAddress: BillingDetails

View file

@ -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,

View file

@ -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>

View file

@ -31,6 +31,7 @@ const SubscriptionPage_OrganizationFragment = graphql(`
}
billingConfiguration {
hasPaymentIssues
canUpdateSubscription
invoices {
id
}

View file

@ -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>