Skip to content

Commit

Permalink
feat: allow resuming cancelled subscription, misc UI improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
rohan-chaturvedi committed Dec 3, 2024
1 parent 75e2d8d commit 0c1bf0e
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 31 deletions.
2 changes: 2 additions & 0 deletions backend/backend/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
CreateProUpgradeCheckoutSession,
CreateSetupIntentMutation,
DeletePaymentMethodMutation,
ResumeSubscriptionMutation,
SetDefaultPaymentMethodMutation,
)
from .graphene.mutations.lockbox import CreateLockboxMutation
Expand Down Expand Up @@ -893,6 +894,7 @@ class Mutation(graphene.ObjectType):
create_pro_upgrade_checkout_session = CreateProUpgradeCheckoutSession.Field()
delete_payment_method = DeletePaymentMethodMutation.Field()
cancel_subscription = CancelSubscriptionMutation.Field()
resume_subscription = ResumeSubscriptionMutation.Field()
create_setup_intent = CreateSetupIntentMutation.Field()
set_default_payment_method = SetDefaultPaymentMethodMutation.Field()

Expand Down
64 changes: 59 additions & 5 deletions backend/ee/billing/graphene/mutations/stripe.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from graphql import GraphQLError


class CancelSubscriptionResponse(ObjectType):
class UpdateSubscriptionResponse(ObjectType):
success = Boolean()
message = String()
canceled_at = String()
Expand Down Expand Up @@ -101,7 +101,7 @@ class Arguments:
organisation_id = ID()
subscription_id = String(required=True)

Output = CancelSubscriptionResponse
Output = UpdateSubscriptionResponse

def mutate(self, info, organisation_id, subscription_id):
stripe.api_key = settings.STRIPE["secret_key"]
Expand All @@ -125,21 +125,75 @@ def mutate(self, info, organisation_id, subscription_id):
subscription_id, cancel_at_period_end=True
)

return CancelSubscriptionResponse(
return UpdateSubscriptionResponse(
success=True,
message="Subscription set to cancel at the end of the current billing cycle.",
canceled_at=None, # The subscription is not yet canceled
status=updated_subscription["status"],
)
except stripe.error.InvalidRequestError as e:
return CancelSubscriptionResponse(
return UpdateSubscriptionResponse(
success=False,
message=f"Error: {str(e)}",
canceled_at=None,
status=None,
)
except Exception as e:
return CancelSubscriptionResponse(
return UpdateSubscriptionResponse(
success=False,
message=f"An unexpected error occurred: {str(e)}",
canceled_at=None,
status=None,
)


class ResumeSubscriptionMutation(Mutation):
class Arguments:
organisation_id = ID()
subscription_id = String(required=True)

Output = UpdateSubscriptionResponse # Reuse the response class for consistency

def mutate(self, info, organisation_id, subscription_id):
stripe.api_key = settings.STRIPE["secret_key"]

try:
org = Organisation.objects.get(id=organisation_id)

if not user_has_permission(info.context.user, "update", "Billing", org):
raise GraphQLError(
"You don't have the permissions required to update Billing information in this Organisation."
)

if org.stripe_subscription_id != subscription_id:
raise GraphQLError("The subscription ID provided is not valid.")

# Retrieve the subscription
subscription = stripe.Subscription.retrieve(subscription_id)

if not subscription.get("cancel_at_period_end"):
raise GraphQLError("The subscription is not marked for cancellation.")

# Resume the subscription by updating cancel_at_period_end to False
updated_subscription = stripe.Subscription.modify(
subscription_id, cancel_at_period_end=False
)

return UpdateSubscriptionResponse(
success=True,
message="Subscription resumed successfully.",
canceled_at=None, # Reset canceled_at since the subscription is active
status=updated_subscription["status"],
)
except stripe.error.InvalidRequestError as e:
return UpdateSubscriptionResponse(
success=False,
message=f"Error: {str(e)}",
canceled_at=None,
status=None,
)
except Exception as e:
return UpdateSubscriptionResponse(
success=False,
message=f"An unexpected error occurred: {str(e)}",
canceled_at=None,
Expand Down
5 changes: 5 additions & 0 deletions frontend/apollo/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const documents = {
"mutation CreateStripeSetupIntentOp($organisationId: ID!) {\n createSetupIntent(organisationId: $organisationId) {\n clientSecret\n }\n}": types.CreateStripeSetupIntentOpDocument,
"mutation DeleteStripePaymentMethod($organisationId: ID!, $paymentMethodId: String!) {\n deletePaymentMethod(\n organisationId: $organisationId\n paymentMethodId: $paymentMethodId\n ) {\n ok\n }\n}": types.DeleteStripePaymentMethodDocument,
"mutation InitStripeProUpgradeCheckout($organisationId: ID!, $billingPeriod: String!) {\n createProUpgradeCheckoutSession(\n organisationId: $organisationId\n billingPeriod: $billingPeriod\n ) {\n clientSecret\n }\n}": types.InitStripeProUpgradeCheckoutDocument,
"mutation ResumeStripeSubscription($organisationId: ID!, $subscriptionId: String!) {\n resumeSubscription(\n organisationId: $organisationId\n subscriptionId: $subscriptionId\n ) {\n success\n message\n canceledAt\n status\n }\n}": types.ResumeStripeSubscriptionDocument,
"mutation SetDefaultStripePaymentMethodOp($organisationId: ID!, $paymentMethodId: String!) {\n setDefaultPaymentMethod(\n organisationId: $organisationId\n paymentMethodId: $paymentMethodId\n ) {\n ok\n }\n}": types.SetDefaultStripePaymentMethodOpDocument,
"mutation CreateApplication($id: ID!, $organisationId: ID!, $name: String!, $identityKey: String!, $appToken: String!, $appSeed: String!, $wrappedKeyShare: String!, $appVersion: Int!) {\n createApp(\n id: $id\n organisationId: $organisationId\n name: $name\n identityKey: $identityKey\n appToken: $appToken\n appSeed: $appSeed\n wrappedKeyShare: $wrappedKeyShare\n appVersion: $appVersion\n ) {\n app {\n id\n name\n identityKey\n }\n }\n}": types.CreateApplicationDocument,
"mutation CreateOrg($id: ID!, $name: String!, $identityKey: String!, $wrappedKeyring: String!, $wrappedRecovery: String!) {\n createOrganisation(\n id: $id\n name: $name\n identityKey: $identityKey\n wrappedKeyring: $wrappedKeyring\n wrappedRecovery: $wrappedRecovery\n ) {\n organisation {\n id\n name\n memberId\n }\n }\n}": types.CreateOrgDocument,
Expand Down Expand Up @@ -179,6 +180,10 @@ export function graphql(source: "mutation DeleteStripePaymentMethod($organisatio
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "mutation InitStripeProUpgradeCheckout($organisationId: ID!, $billingPeriod: String!) {\n createProUpgradeCheckoutSession(\n organisationId: $organisationId\n billingPeriod: $billingPeriod\n ) {\n clientSecret\n }\n}"): (typeof documents)["mutation InitStripeProUpgradeCheckout($organisationId: ID!, $billingPeriod: String!) {\n createProUpgradeCheckoutSession(\n organisationId: $organisationId\n billingPeriod: $billingPeriod\n ) {\n clientSecret\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "mutation ResumeStripeSubscription($organisationId: ID!, $subscriptionId: String!) {\n resumeSubscription(\n organisationId: $organisationId\n subscriptionId: $subscriptionId\n ) {\n success\n message\n canceledAt\n status\n }\n}"): (typeof documents)["mutation ResumeStripeSubscription($organisationId: ID!, $subscriptionId: String!) {\n resumeSubscription(\n organisationId: $organisationId\n subscriptionId: $subscriptionId\n ) {\n success\n message\n canceledAt\n status\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
Expand Down
Loading

0 comments on commit 0c1bf0e

Please sign in to comment.