diff --git a/backend/backend/schema.py b/backend/backend/schema.py index 6bf3ac018..cdb314741 100644 --- a/backend/backend/schema.py +++ b/backend/backend/schema.py @@ -24,9 +24,18 @@ ) from ee.billing.graphene.queries.stripe import ( StripeCheckoutDetails, + StripeSubscriptionDetails, resolve_stripe_checkout_details, + resolve_stripe_subscription_details, +) +from ee.billing.graphene.mutations.stripe import ( + CancelSubscriptionMutation, + CreateProUpgradeCheckoutSession, + CreateSetupIntentMutation, + DeletePaymentMethodMutation, + ResumeSubscriptionMutation, + SetDefaultPaymentMethodMutation, ) -from ee.billing.graphene.mutations.stripe import CreateProUpgradeCheckoutSession from .graphene.mutations.lockbox import CreateLockboxMutation from .graphene.queries.syncing import ( resolve_aws_secret_manager_secrets, @@ -321,6 +330,10 @@ class Query(graphene.ObjectType): StripeCheckoutDetails, stripe_session_id=graphene.String(required=True) ) + stripe_subscription_details = graphene.Field( + StripeSubscriptionDetails, organisation_id=graphene.ID() + ) + # -------------------------------------------------------------------- resolve_server_public_key = resolve_server_public_key @@ -774,6 +787,7 @@ def resolve_app_activity_chart(root, info, app_id, period=TimeRange.DAY): return time_series_logs resolve_stripe_checkout_details = resolve_stripe_checkout_details + resolve_stripe_subscription_details = resolve_stripe_subscription_details class Mutation(graphene.ObjectType): @@ -878,6 +892,11 @@ class Mutation(graphene.ObjectType): # Billing 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() schema = graphene.Schema(query=Query, mutation=Mutation) diff --git a/backend/ee/billing/graphene/mutations/stripe.py b/backend/ee/billing/graphene/mutations/stripe.py index 76b6c8a29..1fee93e00 100644 --- a/backend/ee/billing/graphene/mutations/stripe.py +++ b/backend/ee/billing/graphene/mutations/stripe.py @@ -3,10 +3,17 @@ from api.utils.access.permissions import user_has_permission import stripe from django.conf import settings -from graphene import Mutation, ID, String +from graphene import Mutation, ID, String, Boolean, ObjectType, Mutation from graphql import GraphQLError +class UpdateSubscriptionResponse(ObjectType): + success = Boolean() + message = String() + canceled_at = String() + status = String() + + class CreateProUpgradeCheckoutSession(Mutation): class Arguments: organisation_id = ID(required=True) @@ -21,7 +28,9 @@ def mutate(self, info, organisation_id, billing_period): organisation = Organisation.objects.get(id=organisation_id) - if not user_has_permission(info.context.user, "update", "Billing", organisation): + if not user_has_permission( + info.context.user, "update", "Billing", organisation + ): raise GraphQLError("You don't have permission to update Billing") seats = get_organisation_seats(organisation) @@ -52,6 +61,9 @@ def mutate(self, info, organisation_id, billing_period): "trial_period_days": 30, }, return_url=f"{settings.OAUTH_REDIRECT_URI}/{organisation.name}/settings?stripe_session_id={{CHECKOUT_SESSION_ID}}", + saved_payment_method_options={ + "allow_redisplay_filters": ["always", "limited", "unspecified"], + }, ) return CreateProUpgradeCheckoutSession(client_secret=session.client_secret) @@ -61,3 +73,199 @@ def mutate(self, info, organisation_id, billing_period): raise GraphQLError( f"Something went wrong during checkout. Please try again." ) + + +class DeletePaymentMethodMutation(Mutation): + class Arguments: + organisation_id = ID() + payment_method_id = String() + + ok = Boolean() + + def mutate(self, info, organisation_id, payment_method_id): + 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." + ) + + try: + stripe.api_key = settings.STRIPE["secret_key"] + stripe.PaymentMethod.detach(payment_method_id) + + return DeletePaymentMethodMutation(ok=True) + except Exception as e: + raise GraphQLError("Something went wrong. Please try again.") + + +class CancelSubscriptionMutation(Mutation): + class Arguments: + organisation_id = ID() + subscription_id = String(required=True) + + Output = UpdateSubscriptionResponse + + def mutate(self, info, organisation_id, subscription_id): + stripe.api_key = settings.STRIPE["secret_key"] + + 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.") + + try: + # Retrieve the subscription + subscription = stripe.Subscription.retrieve(subscription_id) + + # Cancel at the end of the current billing cycle + updated_subscription = stripe.Subscription.modify( + subscription_id, cancel_at_period_end=True + ) + + 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 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, + 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, + status=None, + ) + + +class CreateSetupIntentMutation(Mutation): + class Arguments: + organisation_id = ID() + + client_secret = String() + + def mutate(self, info, organisation_id): + stripe.api_key = settings.STRIPE["secret_key"] + + 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." + ) + + # Create a SetupIntent for the customer + setup_intent = stripe.SetupIntent.create( + customer=org.stripe_customer_id, + usage="off_session", + automatic_payment_methods={ + "enabled": False, + }, + payment_method_types=["card"], + ) + + return CreateSetupIntentMutation(client_secret=setup_intent.client_secret) + + +class SetDefaultPaymentMethodMutation(Mutation): + class Arguments: + # Arguments passed to the mutation + organisation_id = ID() + payment_method_id = String( + required=True, description="Payment Method ID to set as default" + ) + + # Define the return type + ok = Boolean() + + def mutate(root, info, organisation_id, payment_method_id): + + 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." + ) + + try: + # Retrieve the customer from Stripe + stripe.Customer.modify( + org.stripe_customer_id, + invoice_settings={"default_payment_method": payment_method_id}, + ) + + stripe.PaymentMethod.modify(payment_method_id, allow_redisplay="limited") + + return SetDefaultPaymentMethodMutation(ok=True) + except stripe.error.StripeError as e: + # Handle Stripe errors + raise GraphQLError(f"Stripe error: {str(e)}") + except Exception as e: + # Handle other potential exceptions + raise GraphQLError(f"An error occurred: {str(e)}") diff --git a/backend/ee/billing/graphene/queries/stripe.py b/backend/ee/billing/graphene/queries/stripe.py index b0ee4eefe..68f98adda 100644 --- a/backend/ee/billing/graphene/queries/stripe.py +++ b/backend/ee/billing/graphene/queries/stripe.py @@ -1,7 +1,10 @@ +from api.models import Organisation +from api.utils.access.permissions import user_has_permission import graphene -from graphene import ObjectType, String, Field +from graphene import ObjectType, String, Boolean, List, Int import stripe from django.conf import settings +from graphql import GraphQLError class StripeCheckoutDetails(graphene.ObjectType): @@ -13,6 +16,27 @@ class StripeCheckoutDetails(graphene.ObjectType): plan_name = graphene.String() +class PaymentMethodDetails(graphene.ObjectType): + id = graphene.String() + brand = graphene.String() + last4 = graphene.String() + exp_month = graphene.Int() + exp_year = graphene.Int() + is_default = graphene.Boolean() + + +class StripeSubscriptionDetails(ObjectType): + subscription_id = String() + plan_name = String() + status = String() + current_period_start = Int() + current_period_end = Int() + renewal_date = Int() + cancel_at = Int() + cancel_at_period_end = Boolean() + payment_methods = List(PaymentMethodDetails) + + def resolve_stripe_checkout_details(self, info, stripe_session_id): stripe.api_key = settings.STRIPE["secret_key"] @@ -40,3 +64,62 @@ def resolve_stripe_checkout_details(self, info, stripe_session_id): ) except stripe.error.StripeError as e: return None + + +def resolve_stripe_subscription_details(self, info, organisation_id): + stripe.api_key = settings.STRIPE["secret_key"] + + try: + org = Organisation.objects.get(id=organisation_id) + if not user_has_permission(info.context.user, "read", "Billing", org): + raise GraphQLError("You don't have permission to view Tokens in this App") + + # Retrieve subscription details + subscription = stripe.Subscription.retrieve(org.stripe_subscription_id) + + plan_name = subscription["items"]["data"][0]["plan"]["nickname"] + current_period_start = subscription["current_period_start"] + current_period_end = subscription["current_period_end"] + renewal_date = subscription["current_period_end"] + status = subscription["status"] + + # Retrieve cancellation details + cancel_at = subscription.get("cancel_at") # Timestamp of cancellation, if set + cancel_at_period_end = subscription.get("cancel_at_period_end") # Boolean + + customer = stripe.Customer.retrieve(org.stripe_customer_id) + + default_payment_method_id = customer.get("invoice_settings", {}).get( + "default_payment_method" + ) + + # Retrieve payment methods for the customer + payment_methods = stripe.PaymentMethod.list( + customer=org.stripe_customer_id, type="card" + ) + + payment_methods_list = [ + PaymentMethodDetails( + id=pm["id"], + brand=pm["card"]["brand"], + last4=pm["card"]["last4"], + is_default=(pm["id"] == default_payment_method_id), # Check if default + exp_month=pm["card"]["exp_month"], + exp_year=pm["card"]["exp_year"], + ) + for pm in payment_methods["data"] + ] + + return StripeSubscriptionDetails( + subscription_id=org.stripe_subscription_id, + plan_name=plan_name, + status=status, + current_period_start=str(current_period_start), + current_period_end=str(current_period_end), + renewal_date=str(renewal_date), + cancel_at=str(cancel_at) if cancel_at else None, + cancel_at_period_end=cancel_at_period_end, + payment_methods=payment_methods_list, + ) + except stripe.error.StripeError as e: + return None diff --git a/backend/ee/billing/webhooks/stripe.py b/backend/ee/billing/webhooks/stripe.py index f86f9cbf9..0e2c8bacc 100644 --- a/backend/ee/billing/webhooks/stripe.py +++ b/backend/ee/billing/webhooks/stripe.py @@ -2,6 +2,7 @@ from django.http import JsonResponse from ee.billing.stripe import map_stripe_plan_to_tier import stripe +import logging from api.models import Organisation from django.conf import settings @@ -61,23 +62,52 @@ def handle_subscription_deleted(event): settings.STRIPE["prices"]["pro_monthly"], settings.STRIPE["prices"]["pro_yearly"], ] - if subscription["items"]["data"][0]["price"]["id"] in pro_price_ids: - active_subscriptions = stripe.Subscription.list( - customer=organisation.stripe_customer_id, status="active" - ) + free_price_id = settings.STRIPE["prices"]["free"] - has_active_pro_subscription = any( - item["price"]["id"] in pro_price_ids - for sub in active_subscriptions["data"] - for item in sub["items"]["data"] + # Fetch all active subscriptions for the customer + active_subscriptions = stripe.Subscription.list( + customer=organisation.stripe_customer_id, status="active" + ) + + # Check for active Pro subscriptions + active_pro_subscriptions = [ + sub + for sub in active_subscriptions["data"] + if any( + item["price"]["id"] in pro_price_ids for item in sub["items"]["data"] ) + ] - if not has_active_pro_subscription: + if active_pro_subscriptions: + # Update the organisation's subscription ID to the first active Pro subscription + organisation.stripe_subscription_id = active_pro_subscriptions[0]["id"] + else: + # Check for the Free subscription + free_subscriptions = [ + sub + for sub in active_subscriptions["data"] + if any( + item["price"]["id"] == free_price_id + for item in sub["items"]["data"] + ) + ] + + if free_subscriptions: + # Update the organisation's subscription ID to the first Free subscription + organisation.stripe_subscription_id = free_subscriptions[0]["id"] + organisation.plan = Organisation.FREE_PLAN + else: + # If no active subscription exists, set the plan to Free and clear the subscription ID organisation.plan = Organisation.FREE_PLAN - organisation.save() + organisation.stripe_subscription_id = None + + organisation.save() except Organisation.DoesNotExist: return JsonResponse({"error": "Organisation not found"}, status=404) + except Exception as e: + logging.error("An error occurred: %s", str(e)) + return JsonResponse({"error": "An internal error has occurred"}, status=500) @csrf_exempt diff --git a/frontend/apollo/gql.ts b/frontend/apollo/gql.ts index 7218aa94f..f37105eed 100644 --- a/frontend/apollo/gql.ts +++ b/frontend/apollo/gql.ts @@ -19,7 +19,12 @@ const documents = { "mutation AddMemberToApp($memberId: ID!, $memberType: MemberType, $appId: ID!, $envKeys: [EnvironmentKeyInput]) {\n addAppMember(\n memberId: $memberId\n memberType: $memberType\n appId: $appId\n envKeys: $envKeys\n ) {\n app {\n id\n }\n }\n}": types.AddMemberToAppDocument, "mutation RemoveMemberFromApp($memberId: ID!, $memberType: MemberType, $appId: ID!) {\n removeAppMember(memberId: $memberId, memberType: $memberType, appId: $appId) {\n app {\n id\n }\n }\n}": types.RemoveMemberFromAppDocument, "mutation UpdateEnvScope($memberId: ID!, $memberType: MemberType, $appId: ID!, $envKeys: [EnvironmentKeyInput]) {\n updateMemberEnvironmentScope(\n memberId: $memberId\n memberType: $memberType\n appId: $appId\n envKeys: $envKeys\n ) {\n app {\n id\n }\n }\n}": types.UpdateEnvScopeDocument, + "mutation CancelStripeSubscription($organisationId: ID!, $subscriptionId: String!) {\n cancelSubscription(\n organisationId: $organisationId\n subscriptionId: $subscriptionId\n ) {\n success\n }\n}": types.CancelStripeSubscriptionDocument, + "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, "mutation DeleteApplication($id: ID!) {\n deleteApp(id: $id) {\n ok\n }\n}": types.DeleteApplicationDocument, @@ -77,6 +82,7 @@ const documents = { "query GetAppMembers($appId: ID!) {\n appUsers(appId: $appId) {\n id\n identityKey\n email\n fullName\n avatarUrl\n createdAt\n role {\n id\n name\n description\n permissions\n color\n }\n }\n}": types.GetAppMembersDocument, "query GetAppServiceAccounts($appId: ID!) {\n appServiceAccounts(appId: $appId) {\n id\n identityKey\n name\n createdAt\n role {\n id\n name\n description\n permissions\n color\n }\n tokens {\n id\n name\n }\n }\n}": types.GetAppServiceAccountsDocument, "query GetCheckoutDetails($stripeSessionId: String!) {\n stripeCheckoutDetails(stripeSessionId: $stripeSessionId) {\n paymentStatus\n customerEmail\n billingStartDate\n billingEndDate\n subscriptionId\n planName\n }\n}": types.GetCheckoutDetailsDocument, + "query GetSubscriptionDetails($organisationId: ID!) {\n stripeSubscriptionDetails(organisationId: $organisationId) {\n subscriptionId\n planName\n status\n currentPeriodStart\n currentPeriodEnd\n renewalDate\n cancelAt\n cancelAtPeriodEnd\n paymentMethods {\n id\n brand\n last4\n expMonth\n expYear\n isDefault\n }\n }\n}": types.GetSubscriptionDetailsDocument, "query GetAppActivityChart($appId: ID!, $period: TimeRange) {\n appActivityChart(appId: $appId, period: $period) {\n index\n date\n data\n }\n}": types.GetAppActivityChartDocument, "query GetAppDetail($organisationId: ID!, $appId: ID!) {\n apps(organisationId: $organisationId, appId: $appId) {\n id\n name\n identityKey\n createdAt\n appToken\n appSeed\n appVersion\n sseEnabled\n }\n}": types.GetAppDetailDocument, "query GetAppKmsLogs($appId: ID!, $start: BigInt, $end: BigInt) {\n logs(appId: $appId, start: $start, end: $end) {\n kms {\n id\n timestamp\n phaseNode\n eventType\n ipAddress\n country\n city\n phSize\n }\n }\n kmsLogsCount(appId: $appId)\n}": types.GetAppKmsLogsDocument, @@ -158,10 +164,30 @@ export function graphql(source: "mutation RemoveMemberFromApp($memberId: ID!, $m * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "mutation UpdateEnvScope($memberId: ID!, $memberType: MemberType, $appId: ID!, $envKeys: [EnvironmentKeyInput]) {\n updateMemberEnvironmentScope(\n memberId: $memberId\n memberType: $memberType\n appId: $appId\n envKeys: $envKeys\n ) {\n app {\n id\n }\n }\n}"): (typeof documents)["mutation UpdateEnvScope($memberId: ID!, $memberType: MemberType, $appId: ID!, $envKeys: [EnvironmentKeyInput]) {\n updateMemberEnvironmentScope(\n memberId: $memberId\n memberType: $memberType\n appId: $appId\n envKeys: $envKeys\n ) {\n app {\n id\n }\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 CancelStripeSubscription($organisationId: ID!, $subscriptionId: String!) {\n cancelSubscription(\n organisationId: $organisationId\n subscriptionId: $subscriptionId\n ) {\n success\n }\n}"): (typeof documents)["mutation CancelStripeSubscription($organisationId: ID!, $subscriptionId: String!) {\n cancelSubscription(\n organisationId: $organisationId\n subscriptionId: $subscriptionId\n ) {\n success\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 CreateStripeSetupIntentOp($organisationId: ID!) {\n createSetupIntent(organisationId: $organisationId) {\n clientSecret\n }\n}"): (typeof documents)["mutation CreateStripeSetupIntentOp($organisationId: ID!) {\n createSetupIntent(organisationId: $organisationId) {\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 DeleteStripePaymentMethod($organisationId: ID!, $paymentMethodId: String!) {\n deletePaymentMethod(\n organisationId: $organisationId\n paymentMethodId: $paymentMethodId\n ) {\n ok\n }\n}"): (typeof documents)["mutation DeleteStripePaymentMethod($organisationId: ID!, $paymentMethodId: String!) {\n deletePaymentMethod(\n organisationId: $organisationId\n paymentMethodId: $paymentMethodId\n ) {\n ok\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 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. + */ +export function graphql(source: "mutation SetDefaultStripePaymentMethodOp($organisationId: ID!, $paymentMethodId: String!) {\n setDefaultPaymentMethod(\n organisationId: $organisationId\n paymentMethodId: $paymentMethodId\n ) {\n ok\n }\n}"): (typeof documents)["mutation SetDefaultStripePaymentMethodOp($organisationId: ID!, $paymentMethodId: String!) {\n setDefaultPaymentMethod(\n organisationId: $organisationId\n paymentMethodId: $paymentMethodId\n ) {\n ok\n }\n}"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -390,6 +416,10 @@ export function graphql(source: "query GetAppServiceAccounts($appId: ID!) {\n a * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "query GetCheckoutDetails($stripeSessionId: String!) {\n stripeCheckoutDetails(stripeSessionId: $stripeSessionId) {\n paymentStatus\n customerEmail\n billingStartDate\n billingEndDate\n subscriptionId\n planName\n }\n}"): (typeof documents)["query GetCheckoutDetails($stripeSessionId: String!) {\n stripeCheckoutDetails(stripeSessionId: $stripeSessionId) {\n paymentStatus\n customerEmail\n billingStartDate\n billingEndDate\n subscriptionId\n planName\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: "query GetSubscriptionDetails($organisationId: ID!) {\n stripeSubscriptionDetails(organisationId: $organisationId) {\n subscriptionId\n planName\n status\n currentPeriodStart\n currentPeriodEnd\n renewalDate\n cancelAt\n cancelAtPeriodEnd\n paymentMethods {\n id\n brand\n last4\n expMonth\n expYear\n isDefault\n }\n }\n}"): (typeof documents)["query GetSubscriptionDetails($organisationId: ID!) {\n stripeSubscriptionDetails(organisationId: $organisationId) {\n subscriptionId\n planName\n status\n currentPeriodStart\n currentPeriodEnd\n renewalDate\n cancelAt\n cancelAtPeriodEnd\n paymentMethods {\n id\n brand\n last4\n expMonth\n expYear\n isDefault\n }\n }\n}"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/frontend/apollo/graphql.ts b/frontend/apollo/graphql.ts index e899e643b..941f9fccd 100644 --- a/frontend/apollo/graphql.ts +++ b/frontend/apollo/graphql.ts @@ -296,6 +296,11 @@ export type CreateServiceTokenMutation = { serviceToken?: Maybe; }; +export type CreateSetupIntentMutation = { + __typename?: 'CreateSetupIntentMutation'; + clientSecret?: Maybe; +}; + export type CreateUserTokenMutation = { __typename?: 'CreateUserTokenMutation'; ok?: Maybe; @@ -337,6 +342,11 @@ export type DeleteOrganisationMemberMutation = { ok?: Maybe; }; +export type DeletePaymentMethodMutation = { + __typename?: 'DeletePaymentMethodMutation'; + ok?: Maybe; +}; + export type DeletePersonalSecretMutation = { __typename?: 'DeletePersonalSecretMutation'; ok?: Maybe; @@ -589,6 +599,7 @@ export enum MemberType { export type Mutation = { __typename?: 'Mutation'; addAppMember?: Maybe; + cancelSubscription?: Maybe; createApp?: Maybe; createAwsSecretSync?: Maybe; createCloudflarePagesSync?: Maybe; @@ -613,6 +624,7 @@ export type Mutation = { createServiceAccount?: Maybe; createServiceAccountToken?: Maybe; createServiceToken?: Maybe; + createSetupIntent?: Maybe; createUserToken?: Maybe; createVaultSync?: Maybe; createVercelSync?: Maybe; @@ -622,6 +634,7 @@ export type Mutation = { deleteEnvironment?: Maybe; deleteInvitation?: Maybe; deleteOrganisationMember?: Maybe; + deletePaymentMethod?: Maybe; deleteProviderCredentials?: Maybe; deleteSecret?: Maybe; deleteSecretFolder?: Maybe; @@ -639,7 +652,9 @@ export type Mutation = { removeAppMember?: Maybe; removeOverride?: Maybe; renameEnvironment?: Maybe; + resumeSubscription?: Maybe; rotateAppKeys?: Maybe; + setDefaultPaymentMethod?: Maybe; swapEnvironmentOrder?: Maybe; toggleSyncActive?: Maybe; triggerSync?: Maybe; @@ -662,6 +677,12 @@ export type MutationAddAppMemberArgs = { }; +export type MutationCancelSubscriptionArgs = { + organisationId?: InputMaybe; + subscriptionId: Scalars['String']['input']; +}; + + export type MutationCreateAppArgs = { appSeed: Scalars['String']['input']; appToken: Scalars['String']['input']; @@ -866,6 +887,11 @@ export type MutationCreateServiceTokenArgs = { }; +export type MutationCreateSetupIntentArgs = { + organisationId?: InputMaybe; +}; + + export type MutationCreateUserTokenArgs = { expiry?: InputMaybe; identityKey: Scalars['String']['input']; @@ -928,6 +954,12 @@ export type MutationDeleteOrganisationMemberArgs = { }; +export type MutationDeletePaymentMethodArgs = { + organisationId?: InputMaybe; + paymentMethodId?: InputMaybe; +}; + + export type MutationDeleteProviderCredentialsArgs = { credentialId?: InputMaybe; }; @@ -1022,6 +1054,12 @@ export type MutationRenameEnvironmentArgs = { }; +export type MutationResumeSubscriptionArgs = { + organisationId?: InputMaybe; + subscriptionId: Scalars['String']['input']; +}; + + export type MutationRotateAppKeysArgs = { appToken: Scalars['String']['input']; id: Scalars['ID']['input']; @@ -1029,6 +1067,12 @@ export type MutationRotateAppKeysArgs = { }; +export type MutationSetDefaultPaymentMethodArgs = { + organisationId?: InputMaybe; + paymentMethodId: Scalars['String']['input']; +}; + + export type MutationSwapEnvironmentOrderArgs = { environment1Id: Scalars['ID']['input']; environment2Id: Scalars['ID']['input']; @@ -1166,6 +1210,16 @@ export type OrganisationType = { role?: Maybe; }; +export type PaymentMethodDetails = { + __typename?: 'PaymentMethodDetails'; + brand?: Maybe; + expMonth?: Maybe; + expYear?: Maybe; + id?: Maybe; + isDefault?: Maybe; + last4?: Maybe; +}; + export type PersonalSecretInput = { isActive?: InputMaybe; secretId?: InputMaybe; @@ -1267,6 +1321,7 @@ export type Query = { services?: Maybe>>; sseEnabled?: Maybe; stripeCheckoutDetails?: Maybe; + stripeSubscriptionDetails?: Maybe; syncs?: Maybe>>; testNomadCreds?: Maybe; testVaultCreds?: Maybe; @@ -1462,6 +1517,11 @@ export type QueryStripeCheckoutDetailsArgs = { }; +export type QueryStripeSubscriptionDetailsArgs = { + organisationId?: InputMaybe; +}; + + export type QuerySyncsArgs = { appId?: InputMaybe; envId?: InputMaybe; @@ -1711,6 +1771,11 @@ export type ServiceType = { resourceType?: Maybe; }; +export type SetDefaultPaymentMethodMutation = { + __typename?: 'SetDefaultPaymentMethodMutation'; + ok?: Maybe; +}; + export type StripeCheckoutDetails = { __typename?: 'StripeCheckoutDetails'; billingEndDate?: Maybe; @@ -1721,6 +1786,19 @@ export type StripeCheckoutDetails = { subscriptionId?: Maybe; }; +export type StripeSubscriptionDetails = { + __typename?: 'StripeSubscriptionDetails'; + cancelAt?: Maybe; + cancelAtPeriodEnd?: Maybe; + currentPeriodEnd?: Maybe; + currentPeriodStart?: Maybe; + paymentMethods?: Maybe>>; + planName?: Maybe; + renewalDate?: Maybe; + status?: Maybe; + subscriptionId?: Maybe; +}; + export type SwapEnvironmentOrderMutation = { __typename?: 'SwapEnvironmentOrderMutation'; ok?: Maybe; @@ -1775,6 +1853,14 @@ export type UpdateServiceAccountMutation = { serviceAccount?: Maybe; }; +export type UpdateSubscriptionResponse = { + __typename?: 'UpdateSubscriptionResponse'; + canceledAt?: Maybe; + message?: Maybe; + status?: Maybe; + success?: Maybe; +}; + export type UpdateSyncAuthentication = { __typename?: 'UpdateSyncAuthentication'; sync?: Maybe; @@ -1869,6 +1955,29 @@ export type UpdateEnvScopeMutationVariables = Exact<{ export type UpdateEnvScopeMutation = { __typename?: 'Mutation', updateMemberEnvironmentScope?: { __typename?: 'UpdateMemberEnvScopeMutation', app?: { __typename?: 'AppType', id: string } | null } | null }; +export type CancelStripeSubscriptionMutationVariables = Exact<{ + organisationId: Scalars['ID']['input']; + subscriptionId: Scalars['String']['input']; +}>; + + +export type CancelStripeSubscriptionMutation = { __typename?: 'Mutation', cancelSubscription?: { __typename?: 'UpdateSubscriptionResponse', success?: boolean | null } | null }; + +export type CreateStripeSetupIntentOpMutationVariables = Exact<{ + organisationId: Scalars['ID']['input']; +}>; + + +export type CreateStripeSetupIntentOpMutation = { __typename?: 'Mutation', createSetupIntent?: { __typename?: 'CreateSetupIntentMutation', clientSecret?: string | null } | null }; + +export type DeleteStripePaymentMethodMutationVariables = Exact<{ + organisationId: Scalars['ID']['input']; + paymentMethodId: Scalars['String']['input']; +}>; + + +export type DeleteStripePaymentMethodMutation = { __typename?: 'Mutation', deletePaymentMethod?: { __typename?: 'DeletePaymentMethodMutation', ok?: boolean | null } | null }; + export type InitStripeProUpgradeCheckoutMutationVariables = Exact<{ organisationId: Scalars['ID']['input']; billingPeriod: Scalars['String']['input']; @@ -1877,6 +1986,22 @@ export type InitStripeProUpgradeCheckoutMutationVariables = Exact<{ export type InitStripeProUpgradeCheckoutMutation = { __typename?: 'Mutation', createProUpgradeCheckoutSession?: { __typename?: 'CreateProUpgradeCheckoutSession', clientSecret?: string | null } | null }; +export type ResumeStripeSubscriptionMutationVariables = Exact<{ + organisationId: Scalars['ID']['input']; + subscriptionId: Scalars['String']['input']; +}>; + + +export type ResumeStripeSubscriptionMutation = { __typename?: 'Mutation', resumeSubscription?: { __typename?: 'UpdateSubscriptionResponse', success?: boolean | null, message?: string | null, canceledAt?: string | null, status?: string | null } | null }; + +export type SetDefaultStripePaymentMethodOpMutationVariables = Exact<{ + organisationId: Scalars['ID']['input']; + paymentMethodId: Scalars['String']['input']; +}>; + + +export type SetDefaultStripePaymentMethodOpMutation = { __typename?: 'Mutation', setDefaultPaymentMethod?: { __typename?: 'SetDefaultPaymentMethodMutation', ok?: boolean | null } | null }; + export type CreateApplicationMutationVariables = Exact<{ id: Scalars['ID']['input']; organisationId: Scalars['ID']['input']; @@ -2396,6 +2521,13 @@ export type GetCheckoutDetailsQueryVariables = Exact<{ export type GetCheckoutDetailsQuery = { __typename?: 'Query', stripeCheckoutDetails?: { __typename?: 'StripeCheckoutDetails', paymentStatus?: string | null, customerEmail?: string | null, billingStartDate?: string | null, billingEndDate?: string | null, subscriptionId?: string | null, planName?: string | null } | null }; +export type GetSubscriptionDetailsQueryVariables = Exact<{ + organisationId: Scalars['ID']['input']; +}>; + + +export type GetSubscriptionDetailsQuery = { __typename?: 'Query', stripeSubscriptionDetails?: { __typename?: 'StripeSubscriptionDetails', subscriptionId?: string | null, planName?: string | null, status?: string | null, currentPeriodStart?: number | null, currentPeriodEnd?: number | null, renewalDate?: number | null, cancelAt?: number | null, cancelAtPeriodEnd?: boolean | null, paymentMethods?: Array<{ __typename?: 'PaymentMethodDetails', id?: string | null, brand?: string | null, last4?: string | null, expMonth?: number | null, expYear?: number | null, isDefault?: boolean | null } | null> | null } | null }; + export type GetAppActivityChartQueryVariables = Exact<{ appId: Scalars['ID']['input']; period?: InputMaybe; @@ -2695,7 +2827,12 @@ export const UpdateRoleDocument = {"kind":"Document","definitions":[{"kind":"Ope export const AddMemberToAppDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddMemberToApp"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"memberId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"memberType"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"MemberType"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"appId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"envKeys"}},"type":{"kind":"ListType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EnvironmentKeyInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addAppMember"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"memberId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"memberId"}}},{"kind":"Argument","name":{"kind":"Name","value":"memberType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"memberType"}}},{"kind":"Argument","name":{"kind":"Name","value":"appId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appId"}}},{"kind":"Argument","name":{"kind":"Name","value":"envKeys"},"value":{"kind":"Variable","name":{"kind":"Name","value":"envKeys"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"app"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; export const RemoveMemberFromAppDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveMemberFromApp"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"memberId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"memberType"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"MemberType"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"appId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeAppMember"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"memberId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"memberId"}}},{"kind":"Argument","name":{"kind":"Name","value":"memberType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"memberType"}}},{"kind":"Argument","name":{"kind":"Name","value":"appId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"app"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; export const UpdateEnvScopeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateEnvScope"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"memberId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"memberType"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"MemberType"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"appId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"envKeys"}},"type":{"kind":"ListType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EnvironmentKeyInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateMemberEnvironmentScope"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"memberId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"memberId"}}},{"kind":"Argument","name":{"kind":"Name","value":"memberType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"memberType"}}},{"kind":"Argument","name":{"kind":"Name","value":"appId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appId"}}},{"kind":"Argument","name":{"kind":"Name","value":"envKeys"},"value":{"kind":"Variable","name":{"kind":"Name","value":"envKeys"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"app"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; +export const CancelStripeSubscriptionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CancelStripeSubscription"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"subscriptionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cancelSubscription"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"organisationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}}},{"kind":"Argument","name":{"kind":"Name","value":"subscriptionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"subscriptionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}}]}}]}}]} as unknown as DocumentNode; +export const CreateStripeSetupIntentOpDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateStripeSetupIntentOp"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createSetupIntent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"organisationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"clientSecret"}}]}}]}}]} as unknown as DocumentNode; +export const DeleteStripePaymentMethodDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteStripePaymentMethod"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"paymentMethodId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deletePaymentMethod"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"organisationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}}},{"kind":"Argument","name":{"kind":"Name","value":"paymentMethodId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"paymentMethodId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; export const InitStripeProUpgradeCheckoutDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"InitStripeProUpgradeCheckout"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"billingPeriod"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createProUpgradeCheckoutSession"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"organisationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}}},{"kind":"Argument","name":{"kind":"Name","value":"billingPeriod"},"value":{"kind":"Variable","name":{"kind":"Name","value":"billingPeriod"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"clientSecret"}}]}}]}}]} as unknown as DocumentNode; +export const ResumeStripeSubscriptionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ResumeStripeSubscription"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"subscriptionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"resumeSubscription"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"organisationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}}},{"kind":"Argument","name":{"kind":"Name","value":"subscriptionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"subscriptionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"canceledAt"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode; +export const SetDefaultStripePaymentMethodOpDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SetDefaultStripePaymentMethodOp"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"paymentMethodId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setDefaultPaymentMethod"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"organisationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}}},{"kind":"Argument","name":{"kind":"Name","value":"paymentMethodId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"paymentMethodId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; export const CreateApplicationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateApplication"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"identityKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"appToken"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"appSeed"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"wrappedKeyShare"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"appVersion"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createApp"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}},{"kind":"Argument","name":{"kind":"Name","value":"organisationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}}},{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}},{"kind":"Argument","name":{"kind":"Name","value":"identityKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"identityKey"}}},{"kind":"Argument","name":{"kind":"Name","value":"appToken"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appToken"}}},{"kind":"Argument","name":{"kind":"Name","value":"appSeed"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appSeed"}}},{"kind":"Argument","name":{"kind":"Name","value":"wrappedKeyShare"},"value":{"kind":"Variable","name":{"kind":"Name","value":"wrappedKeyShare"}}},{"kind":"Argument","name":{"kind":"Name","value":"appVersion"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appVersion"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"app"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"identityKey"}}]}}]}}]}}]} as unknown as DocumentNode; export const CreateOrgDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOrg"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"identityKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"wrappedKeyring"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"wrappedRecovery"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOrganisation"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}},{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}},{"kind":"Argument","name":{"kind":"Name","value":"identityKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"identityKey"}}},{"kind":"Argument","name":{"kind":"Name","value":"wrappedKeyring"},"value":{"kind":"Variable","name":{"kind":"Name","value":"wrappedKeyring"}}},{"kind":"Argument","name":{"kind":"Name","value":"wrappedRecovery"},"value":{"kind":"Variable","name":{"kind":"Name","value":"wrappedRecovery"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"organisation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"memberId"}}]}}]}}]}}]} as unknown as DocumentNode; export const DeleteApplicationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteApplication"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteApp"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; @@ -2753,6 +2890,7 @@ export const RevokeUserTokenDocument = {"kind":"Document","definitions":[{"kind" export const GetAppMembersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAppMembers"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"appId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"appUsers"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"appId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"identityKey"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"fullName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"permissions"}},{"kind":"Field","name":{"kind":"Name","value":"color"}}]}}]}}]}}]} as unknown as DocumentNode; export const GetAppServiceAccountsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAppServiceAccounts"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"appId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"appServiceAccounts"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"appId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"identityKey"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"permissions"}},{"kind":"Field","name":{"kind":"Name","value":"color"}}]}},{"kind":"Field","name":{"kind":"Name","value":"tokens"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; export const GetCheckoutDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetCheckoutDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"stripeSessionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stripeCheckoutDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"stripeSessionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"stripeSessionId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"paymentStatus"}},{"kind":"Field","name":{"kind":"Name","value":"customerEmail"}},{"kind":"Field","name":{"kind":"Name","value":"billingStartDate"}},{"kind":"Field","name":{"kind":"Name","value":"billingEndDate"}},{"kind":"Field","name":{"kind":"Name","value":"subscriptionId"}},{"kind":"Field","name":{"kind":"Name","value":"planName"}}]}}]}}]} as unknown as DocumentNode; +export const GetSubscriptionDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetSubscriptionDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stripeSubscriptionDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"organisationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"subscriptionId"}},{"kind":"Field","name":{"kind":"Name","value":"planName"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"currentPeriodStart"}},{"kind":"Field","name":{"kind":"Name","value":"currentPeriodEnd"}},{"kind":"Field","name":{"kind":"Name","value":"renewalDate"}},{"kind":"Field","name":{"kind":"Name","value":"cancelAt"}},{"kind":"Field","name":{"kind":"Name","value":"cancelAtPeriodEnd"}},{"kind":"Field","name":{"kind":"Name","value":"paymentMethods"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"brand"}},{"kind":"Field","name":{"kind":"Name","value":"last4"}},{"kind":"Field","name":{"kind":"Name","value":"expMonth"}},{"kind":"Field","name":{"kind":"Name","value":"expYear"}},{"kind":"Field","name":{"kind":"Name","value":"isDefault"}}]}}]}}]}}]} as unknown as DocumentNode; export const GetAppActivityChartDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAppActivityChart"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"appId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"period"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"TimeRange"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"appActivityChart"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"appId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appId"}}},{"kind":"Argument","name":{"kind":"Name","value":"period"},"value":{"kind":"Variable","name":{"kind":"Name","value":"period"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"data"}}]}}]}}]} as unknown as DocumentNode; export const GetAppDetailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAppDetail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"appId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"apps"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"organisationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"organisationId"}}},{"kind":"Argument","name":{"kind":"Name","value":"appId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"identityKey"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"appToken"}},{"kind":"Field","name":{"kind":"Name","value":"appSeed"}},{"kind":"Field","name":{"kind":"Name","value":"appVersion"}},{"kind":"Field","name":{"kind":"Name","value":"sseEnabled"}}]}}]}}]} as unknown as DocumentNode; export const GetAppKmsLogsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAppKmsLogs"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"appId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"start"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"end"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"logs"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"appId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appId"}}},{"kind":"Argument","name":{"kind":"Name","value":"start"},"value":{"kind":"Variable","name":{"kind":"Name","value":"start"}}},{"kind":"Argument","name":{"kind":"Name","value":"end"},"value":{"kind":"Variable","name":{"kind":"Name","value":"end"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"kms"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"timestamp"}},{"kind":"Field","name":{"kind":"Name","value":"phaseNode"}},{"kind":"Field","name":{"kind":"Name","value":"eventType"}},{"kind":"Field","name":{"kind":"Name","value":"ipAddress"}},{"kind":"Field","name":{"kind":"Name","value":"country"}},{"kind":"Field","name":{"kind":"Name","value":"city"}},{"kind":"Field","name":{"kind":"Name","value":"phSize"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"kmsLogsCount"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"appId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appId"}}}]}]}}]} as unknown as DocumentNode; diff --git a/frontend/apollo/schema.graphql b/frontend/apollo/schema.graphql index d3693677a..1c2ce15fd 100644 --- a/frontend/apollo/schema.graphql +++ b/frontend/apollo/schema.graphql @@ -45,6 +45,7 @@ type Query { testVaultCreds(credentialId: ID): Boolean testNomadCreds(credentialId: ID): Boolean stripeCheckoutDetails(stripeSessionId: String!): StripeCheckoutDetails + stripeSubscriptionDetails(organisationId: ID): StripeSubscriptionDetails } type OrganisationType { @@ -657,6 +658,27 @@ type StripeCheckoutDetails { planName: String } +type StripeSubscriptionDetails { + subscriptionId: String + planName: String + status: String + currentPeriodStart: Int + currentPeriodEnd: Int + renewalDate: Int + cancelAt: Int + cancelAtPeriodEnd: Boolean + paymentMethods: [PaymentMethodDetails] +} + +type PaymentMethodDetails { + id: String + brand: String + last4: String + expMonth: Int + expYear: Int + isDefault: Boolean +} + type Mutation { createOrganisation(id: ID!, identityKey: String!, name: String!, wrappedKeyring: String!, wrappedRecovery: String!): CreateOrganisationMutation inviteOrganisationMember(apps: [String], email: String!, orgId: ID!): InviteOrganisationMemberMutation @@ -721,6 +743,16 @@ type Mutation { removeOverride(secretId: ID): DeletePersonalSecretMutation createLockbox(input: LockboxInput): CreateLockboxMutation createProUpgradeCheckoutSession(billingPeriod: String, organisationId: ID!): CreateProUpgradeCheckoutSession + deletePaymentMethod(organisationId: ID, paymentMethodId: String): DeletePaymentMethodMutation + cancelSubscription(organisationId: ID, subscriptionId: String!): UpdateSubscriptionResponse + resumeSubscription(organisationId: ID, subscriptionId: String!): UpdateSubscriptionResponse + createSetupIntent(organisationId: ID): CreateSetupIntentMutation + setDefaultPaymentMethod( + organisationId: ID + + """Payment Method ID to set as default""" + paymentMethodId: String! + ): SetDefaultPaymentMethodMutation } type CreateOrganisationMutation { @@ -1035,4 +1067,23 @@ input LockboxInput { type CreateProUpgradeCheckoutSession { clientSecret: String +} + +type DeletePaymentMethodMutation { + ok: Boolean +} + +type UpdateSubscriptionResponse { + success: Boolean + message: String + canceledAt: String + status: String +} + +type CreateSetupIntentMutation { + clientSecret: String +} + +type SetDefaultPaymentMethodMutation { + ok: Boolean } \ No newline at end of file diff --git a/frontend/app/signup/page.tsx b/frontend/app/signup/page.tsx index 07cdb3c27..83fa66d95 100644 --- a/frontend/app/signup/page.tsx +++ b/frontend/app/signup/page.tsx @@ -18,7 +18,6 @@ import GetOrganisations from '@/graphql/queries/getOrganisations.gql' import CheckOrganisationNameAvailability from '@/graphql/queries/organisation/checkOrgNameAvailable.gql' import { copyRecoveryKit, generateRecoveryPdf } from '@/utils/recovery' import { setDevicePassword } from '@/utils/localStorage' -import { License } from '@/components/settings/organisation/License' import { LogoMark } from '@/components/common/LogoMark' import { organisationSeed, @@ -27,6 +26,7 @@ import { encryptAccountKeyring, encryptAccountRecovery, } from '@/utils/crypto' +import { License } from '@/ee/billing/License' const bip39 = require('bip39') diff --git a/frontend/components/common/Button.tsx b/frontend/components/common/Button.tsx index 95b479a51..a83a2137f 100644 --- a/frontend/components/common/Button.tsx +++ b/frontend/components/common/Button.tsx @@ -75,6 +75,10 @@ export function Button(buttonProps: ButtonProps) { return 'red' case 'warning': return 'amber' + case 'secondary': + return 'neutral' + case 'outline': + return 'neutral' default: return 'emerald' } diff --git a/frontend/components/common/Spinner.tsx b/frontend/components/common/Spinner.tsx index e64484abd..83b42354e 100644 --- a/frontend/components/common/Spinner.tsx +++ b/frontend/components/common/Spinner.tsx @@ -1,5 +1,4 @@ import clsx from 'clsx' -import { color } from 'framer-motion' const SIZES = { xs: 'border-[2px] h-[14px] w-[14px]', @@ -13,11 +12,12 @@ const COLORS = { emerald: 'border-emerald-500', red: 'border-red-400', amber: 'border-amber-500', + neutral: 'border-neutral-500', } interface SpinnerProps { size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' - color?: 'emerald' | 'red' | 'amber' + color?: 'emerald' | 'red' | 'amber' | 'neutral' } export default function Spinner(props: SpinnerProps = { size: 'lg', color: 'emerald' }) { diff --git a/frontend/components/settings/organisation/PlanInfo.tsx b/frontend/components/settings/organisation/PlanInfo.tsx index 4ed1e2e86..7ea9c1704 100644 --- a/frontend/components/settings/organisation/PlanInfo.tsx +++ b/frontend/components/settings/organisation/PlanInfo.tsx @@ -16,13 +16,12 @@ import { FaCubes, FaTimesCircle, FaUser, - FaUsersCog, } from 'react-icons/fa' import Link from 'next/link' import { ActivatedPhaseLicenseType, ApiOrganisationPlanChoices } from '@/apollo/graphql' import { isCloudHosted } from '@/utils/appConfig' import { LogoWordMark } from '@/components/common/LogoWordMark' -import { License } from './License' +import { License } from '../../../ee/billing/License' import { BsListColumnsReverse } from 'react-icons/bs' import { FaKey } from 'react-icons/fa6' import { useSearchParams } from 'next/navigation' @@ -31,6 +30,7 @@ import { UpsellDialog } from './UpsellDialog' import { userHasPermission } from '@/utils/access/permissions' import Accordion from '@/components/common/Accordion' import clsx from 'clsx' +import { StripeBillingInfo } from '../../../ee/billing/StripeBillingInfo' const plansInfo = { FR: { @@ -202,6 +202,7 @@ export const PlanInfo = () => { )} {license() && } + {isCloudHosted() && } diff --git a/frontend/components/settings/organisation/UpsellDialog.tsx b/frontend/components/settings/organisation/UpsellDialog.tsx index 5f9a1fd76..b73ccc1ba 100644 --- a/frontend/components/settings/organisation/UpsellDialog.tsx +++ b/frontend/components/settings/organisation/UpsellDialog.tsx @@ -5,7 +5,7 @@ import { organisationContext } from '@/contexts/organisationContext' import { GetOrganisationPlan } from '@/graphql/queries/organisation/getOrganisationPlan.gql' import { isCloudHosted } from '@/utils/appConfig' import { useQuery } from '@apollo/client' -import { ReactNode, useContext } from 'react' +import { ReactNode, useContext, useRef } from 'react' import dynamic from 'next/dynamic' export const UpsellDialog = ({ @@ -19,6 +19,10 @@ export const UpsellDialog = ({ }) => { const { activeOrganisation } = useContext(organisationContext) + const dialogRef = useRef<{ closeModal: () => void }>(null) + + const closeModal = () => dialogRef?.current?.closeModal() + // Dynamically import ProUpgradeDialog only if the app is cloud-hosted const ProUpgradeDialog = isCloudHosted() ? dynamic(() => import('@/ee/billing/ProUpgradeDialog')) @@ -42,6 +46,7 @@ export const UpsellDialog = ({ buttonContent={buttonLabel || 'Upgrade'} size="sm" onClose={() => {}} + ref={dialogRef} >
@@ -52,7 +57,12 @@ export const UpsellDialog = ({ activeOrganisation.plan === ApiOrganisationPlanChoices.Pr ? ( {}} /> ) : ( - ProUpgradeDialog && + ProUpgradeDialog && ( + + ) ) ) : (
diff --git a/frontend/ee/billing/AddPaymentMethodForm.tsx b/frontend/ee/billing/AddPaymentMethodForm.tsx new file mode 100644 index 000000000..7eeea963a --- /dev/null +++ b/frontend/ee/billing/AddPaymentMethodForm.tsx @@ -0,0 +1,128 @@ +import { useMutation } from '@apollo/client' +import { useContext, useRef, useState } from 'react' +import { loadStripe, StripeElementsOptions } from '@stripe/stripe-js' +import { Elements, useStripe, useElements, PaymentElement } from '@stripe/react-stripe-js' +import CreateStripeSetupIntentOp from '@/graphql/mutations/billing/createStripeSetupIntent.gql' + +import { Button } from '@/components/common/Button' +import { organisationContext } from '@/contexts/organisationContext' +import GenericDialog from '@/components/common/GenericDialog' +import { FaPlus } from 'react-icons/fa' +import { toast } from 'react-toastify' + +const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY!) + +export const AddPaymentMethodForm = ({ onSuccess }: { onSuccess: () => void }) => { + const { activeOrganisation } = useContext(organisationContext) + const dialogRef = useRef<{ closeModal: () => void }>(null) + const stripe = useStripe() + const elements = useElements() + const [createSetupIntent] = useMutation(CreateStripeSetupIntentOp) + + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const [success, setSuccess] = useState(false) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + + if (!stripe || !elements) { + setError('Stripe has not loaded yet.') + return + } + + setLoading(true) + setError(null) + + try { + const { error: submitError } = await elements.submit() + if (submitError) setError(submitError.message || 'An error occurred') + // Fetch the client secret from the server + const { data } = await createSetupIntent({ + variables: { organisationId: activeOrganisation!.id }, + }) + const clientSecret = data.createSetupIntent.clientSecret + + // Confirm the payment setup + const result = await stripe.confirmSetup({ + elements, + clientSecret, + confirmParams: { + return_url: `${window.location.origin}/${activeOrganisation?.name}/settings`, + }, + redirect: 'if_required', + }) + + if (result.error) { + setError(result.error.message || 'Failed to set up payment method.') + setLoading(false) + return + } + + // Handle success + setSuccess(true) + toast.success('Added payment method') + onSuccess() + if (dialogRef.current) dialogRef.current.closeModal() + } catch (err: any) { + setError(err.message || 'An unexpected error occurred.') + } finally { + setLoading(false) + } + } + + return ( +
+
+ +
+
Powered by Stripe
+ {error &&
{error}
} + +
+ +
+
+ ) +} + +export const AddPaymentMethodDialog = ({ onSuccess }: { onSuccess: () => void }) => { + const dialogRef = useRef<{ closeModal: () => void }>(null) + + const handleSuccess = () => { + dialogRef.current?.closeModal() + onSuccess() + } + + const elementsOptions: StripeElementsOptions = { + mode: 'setup', + currency: 'usd', + setupFutureUsage: 'off_session', + payment_method_types: ['card'], + appearance: { + theme: 'night', + variables: { + colorPrimary: '#10b981', + }, + }, + } + + return ( + + Add payment method + + } + buttonVariant="primary" + ref={dialogRef} + > + + + + + ) +} diff --git a/frontend/components/settings/organisation/License.tsx b/frontend/ee/billing/License.tsx similarity index 100% rename from frontend/components/settings/organisation/License.tsx rename to frontend/ee/billing/License.tsx diff --git a/frontend/ee/billing/PostCheckoutScreen.tsx b/frontend/ee/billing/PostCheckoutScreen.tsx index 3226c9a98..c4fa8d96e 100644 --- a/frontend/ee/billing/PostCheckoutScreen.tsx +++ b/frontend/ee/billing/PostCheckoutScreen.tsx @@ -4,7 +4,7 @@ import { Button } from '@/components/common/Button' import { Dialog, Transition } from '@headlessui/react' import { useState, Fragment } from 'react' import { FaCheckCircle, FaTimes, FaTimesCircle } from 'react-icons/fa' -import { relativeTimeFromDates } from '@/utils/time' +import { useRouter } from 'next/navigation' export const PostCheckoutScreen = ({ stripeSessionId }: { stripeSessionId: string }) => { const { loading, error, data } = useQuery(GetCheckoutDetails, { @@ -12,9 +12,22 @@ export const PostCheckoutScreen = ({ stripeSessionId }: { stripeSessionId: strin }) const [isOpen, setIsOpen] = useState(true) + const router = useRouter() const closeModal = () => { setIsOpen(false) + + // Get the router instance + + // Create a new URLSearchParams object based on the current search params + const params = new URLSearchParams(window.location.search) + + // Remove the stripe_session_id parameter + params.delete('stripe_session_id') + + // Update the URL without refreshing the page + const newUrl = `${window.location.pathname}?${params.toString()}` + router.replace(newUrl) } if (loading) return

Loading...

diff --git a/frontend/ee/billing/ProUpgradeDialog.tsx b/frontend/ee/billing/ProUpgradeDialog.tsx index 67cdafa35..13995ffb8 100644 --- a/frontend/ee/billing/ProUpgradeDialog.tsx +++ b/frontend/ee/billing/ProUpgradeDialog.tsx @@ -57,7 +57,7 @@ const prices: PriceOption[] = [ }, ] -const ProUpgradeDialog = (props: { userCount: number }) => { +const ProUpgradeDialog = (props: { userCount: number; onSuccess: () => void }) => { const [checkoutPreview, setCheckoutPreview] = useState('yearly') const [billingPeriod, setBillingPeriod] = useState(null) @@ -125,10 +125,7 @@ const ProUpgradeDialog = (props: { userCount: number }) => { ) return (
- console.log('Upgrade successful!')} - /> +
+
+
+ + ) +} + +const ResumeSubscription = ({ + subscriptionData, +}: { + subscriptionData: StripeSubscriptionDetails +}) => { + const { activeOrganisation } = useContext(organisationContext) + + const elementsOptions: StripeElementsOptions = { + mode: 'setup', + currency: 'usd', + setupFutureUsage: 'off_session', + payment_method_types: ['card'], + appearance: { + theme: 'night', + variables: { + colorPrimary: '#10b981', + }, + }, + } + + const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY!) + + const paymentMethodExists = subscriptionData.paymentMethods!.length > 0 + + const [resumeSubscription, { loading: resumeIsPending }] = useMutation(ResumeStripeSubscription) + + const handleResumeSubscription = async () => { + if (subscriptionData?.cancelAtPeriodEnd) { + await resumeSubscription({ + variables: { + organisationId: activeOrganisation?.id, + subscriptionId: subscriptionData.subscriptionId, + }, + refetchQueries: [ + { query: GetSubscriptionDetails, variables: { organisationId: activeOrganisation?.id } }, + { query: GetOrganisations }, + ], + }) + toast.success('Resumed subscription') + } + } + + if (paymentMethodExists) + return ( + + ) + else + return ( + + Resume + + } + > +
+
Please add a payment method to resume your subscription.
+ + + +
+
+ ) +} + +const ManagePaymentMethodsDialog = () => { + const { activeOrganisation } = useContext(organisationContext) + + const { data, loading } = useQuery(GetSubscriptionDetails, { + variables: { organisationId: activeOrganisation?.id }, + skip: !activeOrganisation, + }) + + const subscriptionData: StripeSubscriptionDetails | undefined = + data?.stripeSubscriptionDetails ?? undefined + + const userCanDeleteBilling = activeOrganisation + ? userHasPermission(activeOrganisation.role?.permissions, 'Billing', 'delete') + : false + + const allowDelete = + userCanDeleteBilling && + (subscriptionData?.paymentMethods!.length! > 1 || + subscriptionData?.cancelAtPeriodEnd || + activeOrganisation?.plan === ApiOrganisationPlanChoices.Fr) + + const [getSubscriptionDetails] = useLazyQuery(GetSubscriptionDetails) + const [setDefaultPaymentMethod, { loading: setDefaultPending }] = useMutation( + SetDefaultStripePaymentMethodOp + ) + + const refetchSubscription = async () => { + await getSubscriptionDetails({ + variables: { organisationId: activeOrganisation?.id }, + fetchPolicy: 'cache-and-network', + }) + } + + const handleSetDefaultPaymentMethod = async (paymentMethodId: string) => { + await setDefaultPaymentMethod({ + variables: { organisationId: activeOrganisation?.id, paymentMethodId }, + refetchQueries: [ + { query: GetSubscriptionDetails, variables: { organisationId: activeOrganisation?.id } }, + ], + }) + toast.success('Updated default payment method') + } + + if (loading || !subscriptionData) + return ( +
+ +
+ ) + + const PaymentMethodCard = ({ paymentMethod }: { paymentMethod: PaymentMethodDetails }) => { + const { isDefault } = paymentMethod + + return ( +
+ {/* Payment Info */} +
+
+
+
+ +
+ + + {paymentMethod?.last4} + +
+
+ Expires {paymentMethod?.expMonth}/{paymentMethod?.expYear} +
+
+
+ + {/* Actions */} +
+ {paymentMethod?.isDefault && ( +
+ Default +
+ )} + + {subscriptionData?.paymentMethods!.length > 1 && !paymentMethod?.isDefault && ( +
+ +
+ )} + {allowDelete && ( +
+ +
+ )} +
+
+ ) + } + + if ( + activeOrganisation?.plan === ApiOrganisationPlanChoices.Fr && + subscriptionData?.paymentMethods?.length === 0 + ) + return <> + + return ( + + Manage payment methods + + } + buttonVariant="secondary" + title="Manage payment methods" + > +
+
+ Add or remove payment methods, and manage your default payment method +
+
+ {subscriptionData?.paymentMethods?.map((paymentMethod, index) => ( + + ))} +
+ + {activeOrganisation?.plan === ApiOrganisationPlanChoices.Pr && ( +
+ +
+ )} +
+
+ ) +} + +const CancelSubscriptionDialog = ({ subscriptionId }: { subscriptionId: string }) => { + const { activeOrganisation } = useContext(organisationContext) + + const dialogRef = useRef<{ closeModal: () => void }>(null) + + const [cancelSubscription] = useMutation(CancelStripeSubscription) + + const handleCancelSubscription = async () => + await cancelSubscription({ + variables: { subscriptionId, organisationId: activeOrganisation!.id }, + refetchQueries: [ + { query: GetSubscriptionDetails, variables: { organisationId: activeOrganisation?.id } }, + { query: GetOrganisations }, + ], + }).then(() => { + toast.success('Cancelled subscription') + if (dialogRef.current) dialogRef.current.closeModal() + }) + + return ( + + Cancel + + } + buttonVariant="danger" + ref={dialogRef} + > +
+
+

+ Are you sure you want to cancel your subscription of Phase Pro? You will lose access to + all current and future Phase Pro features at the end of the current billing cycle. +

+
+
+ +
+
+
+ ) +} + +export const StripeBillingInfo = () => { + const { activeOrganisation } = useContext(organisationContext) + + // Permission checks + const userCanReadBilling = activeOrganisation + ? userHasPermission(activeOrganisation.role?.permissions, 'Billing', 'read') + : false + + const userCanUpdateBilling = activeOrganisation + ? userHasPermission(activeOrganisation.role?.permissions, 'Billing', 'update') + : false + + const { data, loading } = useQuery(GetSubscriptionDetails, { + variables: { organisationId: activeOrganisation?.id }, + skip: !activeOrganisation || !userCanReadBilling, + }) + + const subscriptionData: StripeSubscriptionDetails | undefined = + data?.stripeSubscriptionDetails ?? undefined + + const defaultPaymentMethod = + subscriptionData?.paymentMethods!.length === 1 + ? subscriptionData?.paymentMethods[0] + : subscriptionData?.paymentMethods!.find((paymentMethod) => paymentMethod?.isDefault) + + const topBorderColor = () => { + const classMap: Record = { + free: 'border-t-neutral-500', // For free plan + pro_active: 'border-t-emerald-500', // For active pro plan + pro_cancelled: 'border-t-amber-500', // For cancelled pro plan + } + + if (activeOrganisation?.plan === ApiOrganisationPlanChoices.Fr) { + return classMap.free + } else if (activeOrganisation?.plan === ApiOrganisationPlanChoices.Pr) { + if (subscriptionData?.cancelAtPeriodEnd) { + return classMap.pro_cancelled + } else { + return classMap.pro_active + } + } + + return '' // Default case if no condition matches + } + + if (loading || !subscriptionData) + return ( +
+ +
+ ) + + if (!userCanReadBilling) return <> + + return ( +
+
Current Subscription
+
+
+
+ {subscriptionData.planName}{' '} + {activeOrganisation?.plan !== ApiOrganisationPlanChoices.Fr && ( + + ({subscriptionData.cancelAtPeriodEnd ? 'Cancelled' : subscriptionData.status}) + + )} +
+ + {activeOrganisation?.plan !== ApiOrganisationPlanChoices.Fr && ( +
+
+ Current billing cycle:{' '} + {new Date(subscriptionData.currentPeriodStart! * 1000).toDateString()} + {' - '} + {new Date(subscriptionData.currentPeriodEnd! * 1000).toDateString()} +
+ +
+ {!subscriptionData.cancelAtPeriodEnd + ? `Next payment ${relativeTimeFromDates(new Date(subscriptionData.renewalDate! * 1000))}` + : `Ends ${relativeTimeFromDates(new Date(subscriptionData.cancelAt! * 1000))}`} + + {!subscriptionData.cancelAtPeriodEnd && ( +
+ {defaultPaymentMethod && ` on card ending in ${defaultPaymentMethod.last4}`} + {defaultPaymentMethod && } +
+ )} +
+
+ )} +
+ +
+ {subscriptionData.cancelAtPeriodEnd && ( + + Your subscription will end{' '} + {relativeTimeFromDates(new Date(subscriptionData.cancelAt! * 1000))}{' '} + + )} + + {userCanUpdateBilling && ( +
+ + {activeOrganisation?.plan !== ApiOrganisationPlanChoices.Fr && ( +
+ {!subscriptionData.cancelAtPeriodEnd ? ( + + ) : ( + + )} +
+ )} +
+ )} +
+
+
+ ) +} diff --git a/frontend/graphql/mutations/billing/cancelProSubscription.gql b/frontend/graphql/mutations/billing/cancelProSubscription.gql new file mode 100644 index 000000000..51746430c --- /dev/null +++ b/frontend/graphql/mutations/billing/cancelProSubscription.gql @@ -0,0 +1,5 @@ +mutation CancelStripeSubscription($organisationId: ID!, $subscriptionId: String!) { + cancelSubscription(organisationId: $organisationId, subscriptionId: $subscriptionId) { + success + } +} diff --git a/frontend/graphql/mutations/billing/createStripeSetupIntent.gql b/frontend/graphql/mutations/billing/createStripeSetupIntent.gql new file mode 100644 index 000000000..df3aba783 --- /dev/null +++ b/frontend/graphql/mutations/billing/createStripeSetupIntent.gql @@ -0,0 +1,5 @@ +mutation CreateStripeSetupIntentOp($organisationId: ID!) { + createSetupIntent(organisationId: $organisationId) { + clientSecret + } +} diff --git a/frontend/graphql/mutations/billing/deletePaymentMethod.gql b/frontend/graphql/mutations/billing/deletePaymentMethod.gql new file mode 100644 index 000000000..f4247c793 --- /dev/null +++ b/frontend/graphql/mutations/billing/deletePaymentMethod.gql @@ -0,0 +1,5 @@ +mutation DeleteStripePaymentMethod($organisationId: ID!, $paymentMethodId: String!) { + deletePaymentMethod(organisationId: $organisationId, paymentMethodId: $paymentMethodId) { + ok + } +} diff --git a/frontend/graphql/mutations/billing/resumeProSubscription.gql b/frontend/graphql/mutations/billing/resumeProSubscription.gql new file mode 100644 index 000000000..7cc0b8af2 --- /dev/null +++ b/frontend/graphql/mutations/billing/resumeProSubscription.gql @@ -0,0 +1,8 @@ +mutation ResumeStripeSubscription($organisationId: ID!, $subscriptionId: String!) { + resumeSubscription(organisationId: $organisationId, subscriptionId: $subscriptionId) { + success + message + canceledAt + status + } +} diff --git a/frontend/graphql/mutations/billing/setDefaultPaymentMethod.gql b/frontend/graphql/mutations/billing/setDefaultPaymentMethod.gql new file mode 100644 index 000000000..52f8e0aa2 --- /dev/null +++ b/frontend/graphql/mutations/billing/setDefaultPaymentMethod.gql @@ -0,0 +1,5 @@ +mutation SetDefaultStripePaymentMethodOp($organisationId: ID!, $paymentMethodId: String!) { + setDefaultPaymentMethod(organisationId: $organisationId, paymentMethodId: $paymentMethodId) { + ok + } +} diff --git a/frontend/graphql/queries/billing/getSubscriptionDetails.gql b/frontend/graphql/queries/billing/getSubscriptionDetails.gql new file mode 100644 index 000000000..6c7d74730 --- /dev/null +++ b/frontend/graphql/queries/billing/getSubscriptionDetails.gql @@ -0,0 +1,20 @@ +query GetSubscriptionDetails($organisationId: ID!) { + stripeSubscriptionDetails(organisationId: $organisationId) { + subscriptionId + planName + status + currentPeriodStart + currentPeriodEnd + renewalDate + cancelAt + cancelAtPeriodEnd + paymentMethods { + id + brand + last4 + expMonth + expYear + isDefault + } + } +} diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 744050479..72bbd9677 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -5,6 +5,7 @@ module.exports = { './pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}', ], + safelist: ['border-t-neutral-500', 'border-t-amber-500', 'border-t-emerald-500', 'border-t-8'], darkMode: 'class', theme: { fontSize: { diff --git a/staging-docker-compose.yml b/staging-docker-compose.yml index 147e7cd74..01881dd08 100644 --- a/staging-docker-compose.yml +++ b/staging-docker-compose.yml @@ -29,7 +29,6 @@ services: env_file: .env environment: NEXTAUTH_URL: "${HTTP_PROTOCOL}${HOST}" - OAUTH_REDIRECT_URI: "${HTTP_PROTOCOL}${HOST}" BACKEND_API_BASE: "http://backend:8000" NEXT_PUBLIC_BACKEND_API_BASE: "${HTTP_PROTOCOL}${HOST}/service" NEXT_PUBLIC_NEXTAUTH_PROVIDERS: "${SSO_PROVIDERS}" @@ -53,6 +52,7 @@ services: ALLOWED_HOSTS: "${HOST},backend" ALLOWED_ORIGINS: "${HTTP_PROTOCOL}${HOST}" SESSION_COOKIE_DOMAIN: "${HOST}" + OAUTH_REDIRECT_URI: "${HTTP_PROTOCOL}${HOST}" networks: - phase-net-dev