def get_subscription_info(request: AuthedHttpRequest, team_id: str) -> JsonResponse: account = get_account_or_404(team_id=team_id, user=request.user) subscription_status = account.get_subscription_blocker() if subscription_status is not None: if subscription_status.kind == "trial_expired": return JsonResponse({"type": "TRIAL_EXPIRED"}) if subscription_status.kind == "seats_exceeded": stripe_info = account.stripe_customer_info() license_count = 0 if stripe_info and stripe_info.subscription_quantity: license_count = stripe_info.subscription_quantity active_user_count: int = len( UserPullRequestActivity.get_active_users_in_last_30_days( account=account)) return JsonResponse({ "type": "SUBSCRIPTION_OVERAGE", "activeUserCount": active_user_count, "licenseCount": license_count, }) if subscription_status.kind == "subscription_expired": return JsonResponse({"type": "SUBSCRIPTION_EXPIRED"}) return JsonResponse({"type": "VALID_SUBSCRIPTION"})
def start_checkout(request: AuthedHttpRequest, team_id: str) -> HttpResponse: payload = StartCheckoutModal.parse_obj(request.POST.dict()) account = get_account_or_404(team_id=team_id, user=request.user) # if available, using the existing customer_id allows us to pre-fill the # checkout form with their email. customer_id = account.stripe_customer_id or None # https://stripe.com/docs/api/checkout/sessions/create session = stripe.checkout.Session.create( client_reference_id=account.id, customer=customer_id, # cards only work with subscriptions and StripeCustomerInformation # depends on credit cards # (payment_method_card_{brand,exp_month,exp_year,last4}). payment_method_types=["card"], subscription_data={ "items": [{ "plan": plan_id_from_period(period=payload.planPeriod), "quantity": payload.seatCount, }], }, success_url= f"{settings.KODIAK_WEB_APP_URL}/t/{account.id}/usage?install_complete=1", cancel_url= f"{settings.KODIAK_WEB_APP_URL}/t/{account.id}/usage?start_subscription=1", ) return JsonResponse( dict( stripeCheckoutSessionId=session.id, stripePublishableApiKey=settings.STRIPE_PUBLISHABLE_API_KEY, ))
def accounts(request: AuthedHttpRequest) -> HttpResponse: return JsonResponse([ dict(id=x.id, name=x.github_account_login, profileImgUrl=x.profile_image()) for x in Account.objects.filter( memberships__user=request.user).order_by("github_account_login") ], )
def oauth_complete(request: HttpRequest) -> HttpResponse: """ OAuth callback handler from GitHub. We get a code from GitHub that we can use with our client secret to get an OAuth token for a GitHub user. The GitHub OAuth token only expires when the user uninstalls the app. https://developer.github.com/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps/#2-users-are-redirected-back-to-your-site-by-github """ if request.method == "POST": login_result = process_login_request(request) return JsonResponse(asdict(login_result)) return HttpResponse()
def activity(request: AuthedHttpRequest, team_id: str) -> HttpResponse: account = get_account_or_404(team_id=team_id, user=request.user) kodiak_activity_labels = [] kodiak_activity_approved = [] kodiak_activity_merged = [] kodiak_activity_updated = [] total_labels = [] total_opened = [] total_merged = [] total_closed = [] for day_activity in PullRequestActivity.objects.filter( github_installation_id=account.github_installation_id).order_by( "date"): kodiak_activity_labels.append(day_activity.date) kodiak_activity_approved.append(day_activity.kodiak_approved) kodiak_activity_merged.append(day_activity.kodiak_merged) kodiak_activity_updated.append(day_activity.kodiak_updated) total_labels.append(day_activity.date) total_opened.append(day_activity.total_opened) total_merged.append(day_activity.total_merged) total_closed.append(day_activity.total_closed) active_merge_queues = [ dict(owner=repo.owner, repo=repo.repo, queues=queues) for repo, queues in get_active_merge_queues( install_id=str(account.github_installation_id)).items() ] return JsonResponse( dict( kodiakActivity=dict( labels=kodiak_activity_labels, datasets=dict( approved=kodiak_activity_approved, merged=kodiak_activity_merged, updated=kodiak_activity_updated, ), ), pullRequestActivity=dict( labels=total_labels, datasets=dict(opened=total_opened, merged=total_merged, closed=total_closed), ), activeMergeQueues=active_merge_queues, ))
def fetch_proration(request: AuthedHttpRequest, team_id: str) -> HttpResponse: payload = FetchProrationModal.parse_obj(request.POST.dict()) account = get_account_or_404(user=request.user, team_id=team_id) stripe_plan_id = plan_id_from_period(period=payload.subscriptionPeriod) customer_info = account.stripe_customer_info() if customer_info is not None: proration_date = int(time.time()) return JsonResponse( dict( proratedCost=customer_info.preview_proration( timestamp=proration_date, subscription_quantity=payload.subscriptionQuantity, plan_id=stripe_plan_id, ), prorationTime=proration_date, )) return HttpResponse(status=500)
def modify_payment_details(request: AuthedHttpRequest, team_id: str) -> HttpResponse: account = get_account_or_404(team_id=team_id, user=request.user) if not request.user.can_edit_subscription(account): raise PermissionDenied session = stripe.checkout.Session.create( client_reference_id=account.id, customer=account.stripe_customer_id or None, mode="setup", payment_method_types=["card"], success_url= f"{settings.KODIAK_WEB_APP_URL}/t/{account.id}/usage?install_complete=1", cancel_url= f"{settings.KODIAK_WEB_APP_URL}/t/{account.id}/usage?modify_subscription=1", ) return JsonResponse( dict( stripeCheckoutSessionId=session.id, stripePublishableApiKey=settings.STRIPE_PUBLISHABLE_API_KEY, ))
def current_account(request: AuthedHttpRequest, team_id: str) -> HttpResponse: account = get_account_or_404(team_id=team_id, user=request.user) return JsonResponse( dict( user=dict( id=request.user.id, name=request.user.github_login, profileImgUrl=request.user.profile_image(), ), org=dict( id=account.id, name=account.github_account_login, profileImgUrl=account.profile_image(), ), accounts=[ dict( id=x.id, name=x.github_account_login, profileImgUrl=x.profile_image(), ) for x in Account.objects.filter(memberships__user=request.user) ], ))
def sync_accounts(request: AuthedHttpRequest) -> HttpResponse: try: request.user.sync_accounts() except SyncAccountsError: return JsonResponse(dict(ok=False)) return JsonResponse(dict(ok=True))
def usage_billing(request: AuthedHttpRequest, team_id: str) -> HttpResponse: account = get_account_or_404(user=request.user, team_id=team_id) active_users = UserPullRequestActivity.get_active_users_in_last_30_days( account) subscription = None trial = None if account.trial_start and account.trial_expiration and account.trial_started_by: trial = dict( startDate=account.trial_start, endDate=account.trial_expiration, expired=account.trial_expired(), startedBy=dict( id=account.trial_started_by.id, name=account.trial_started_by.github_login, profileImgUrl=account.trial_started_by.profile_image(), ), ) stripe_customer_info = account.stripe_customer_info() if stripe_customer_info: customer_address = None if stripe_customer_info.customer_address_line1 is not None: customer_address = dict( line1=stripe_customer_info.customer_address_line1, city=stripe_customer_info.customer_address_city, country=stripe_customer_info.customer_address_country, line2=stripe_customer_info.customer_address_line2, postalCode=stripe_customer_info.customer_address_postal_code, state=stripe_customer_info.customer_address_state, ) plan_interval = ("year" if stripe_customer_info.plan_interval == "year" else "month") brand_title = (stripe_customer_info.payment_method_card_brand.title() if stripe_customer_info.payment_method_card_brand is not None else None) subscription = dict( seats=stripe_customer_info.subscription_quantity, nextBillingDate=stripe_customer_info.next_billing_date, expired=stripe_customer_info.expired, cost=dict( totalCents=stripe_customer_info.plan_amount * stripe_customer_info.subscription_quantity, perSeatCents=stripe_customer_info.plan_amount, currency=stripe_customer_info.customer_currency or DEFAULT_CURRENCY, planInterval=plan_interval, ), billingEmail=stripe_customer_info.customer_email, contactEmails=account.contact_emails, customerName=stripe_customer_info.customer_name, customerAddress=customer_address, cardInfo= f"{brand_title} ({stripe_customer_info.payment_method_card_last4})", viewerIsOrgOwner=request.user.is_admin(account), viewerCanModify=request.user.can_edit_subscription(account), limitBillingAccessToOwners=account.limit_billing_access_to_owners, ) subscription_quantity = (stripe_customer_info.subscription_quantity if stripe_customer_info else 0) # we assign seats to users in order of first active for the last 30 days. allowed_user_ids = { user.github_id for user in sorted(active_users, key=lambda x: x.first_active_at) [:subscription_quantity] } active_user_with_license_info = [ dict( id=active_user.github_id, name=active_user.github_login, profileImgUrl=active_user.profile_image(), interactions=active_user.days_active, lastActiveDate=active_user.last_active_at.isoformat(), firstActiveDate=active_user.first_active_at.isoformat(), hasSeatLicense=(active_user.github_id in allowed_user_ids), ) for active_user in active_users ] subscription_exemption = None if account.subscription_exempt: subscription_exemption = dict( message=account.subscription_exempt_message) return JsonResponse( dict( accountCanSubscribe=account.can_subscribe(), subscription=subscription, trial=trial, activeUsers=active_user_with_license_info, subscriptionExemption=subscription_exemption, ))
def healthcheck(request: HttpRequest) -> HttpResponse: return JsonResponse({"ok": True})
def ping(request: HttpRequest) -> HttpResponse: return JsonResponse({"ok": True})