Skip to content

Commit

Permalink
Added support for assigning a plan manually and allowing a member to …
Browse files Browse the repository at this point in the history
…resume it
  • Loading branch information
jabelone committed Jun 16, 2024
1 parent 5b79b37 commit 85f8f15
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 87 deletions.
219 changes: 135 additions & 84 deletions memberportal/api_billing/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from asgiref.sync import sync_to_async
from django.http import HttpRequest

from profile.models import Profile
from access.models import Doors, Interlock
Expand Down Expand Up @@ -207,102 +208,82 @@ class PaymentPlanSignup(StripeAPIView):
post: attempts to sign the member up to a new payment plan.
"""

def post(self, request, plan_id):
current_plan = request.user.profile.membership_plan
new_plan = PaymentPlan.objects.get(pk=plan_id)
def create_subscription(
self, request: HttpRequest, new_plan: PaymentPlan, attempts: int = 0
):
attempts += 1

if current_plan:
return Response({"success": False}, status=status.HTTP_409_CONFLICT)
if attempts > 3:
request.user.log_event(
"Too many attempts while creating subscription.",
"stripe",
"",
)
return Response(
{
"success": False,
"message": None,
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)

def create_subscription(attempts=0):
attempts += 1
try:
return stripe.Subscription.create(
customer=request.user.profile.stripe_customer_id,
items=[
{"price": new_plan.stripe_id},
],
)

except stripe.error.InvalidRequestError as e:
capture_exception(e)
error = e.json_body.get("error")

if attempts > 3:
if (
error["code"] == "resource_missing"
and "default payment method" in error["message"]
):
request.user.log_event(
"Too many attempts while creating subscription.",
"InvalidRequestError (missing default payment method) from Stripe while creating subscription.",
"stripe",
"",
error,
)

# try to set the default and try again
stripe.Customer.modify(
request.user.profile.stripe_customer_id,
invoice_settings={
"default_payment_method": request.user.profile.stripe_payment_method_id,
},
)

return self.create_subscription(attempts)

if (
error["code"] == "resource_missing"
and "a similar object exists in live mode" in error["message"]
):
request.user.log_event(
"InvalidRequestError (used test key with production object) from Stripe while "
"creating subscription.",
"stripe",
error,
)

return Response(
{
"success": False,
"message": None,
"message": error["message"],
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)

try:
return stripe.Subscription.create(
customer=request.user.profile.stripe_customer_id,
items=[
{"price": new_plan.stripe_id},
],
)

except stripe.error.InvalidRequestError as e:
capture_exception(e)
error = e.json_body.get("error")

if (
error["code"] == "resource_missing"
and "default payment method" in error["message"]
):
request.user.log_event(
"InvalidRequestError (missing default payment method) from Stripe while creating subscription.",
"stripe",
error,
)

# try to set the default and try again
stripe.Customer.modify(
request.user.profile.stripe_customer_id,
invoice_settings={
"default_payment_method": request.user.profile.stripe_payment_method_id,
},
)

return create_subscription(attempts)

if (
error["code"] == "resource_missing"
and "a similar object exists in live mode" in error["message"]
):
request.user.log_event(
"InvalidRequestError (used test key with production object) from Stripe while "
"creating subscription.",
"stripe",
error,
)

return Response(
{
"success": False,
"message": error["message"],
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)

else:
request.user.log_event(
"InvalidRequestError from Stripe while creating subscription.",
"stripe",
error,
)
return Response(
{
"success": False,
"message": None,
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)

except Exception as e:
else:
request.user.log_event(
"InvalidRequestError from Stripe while creating subscription.",
"stripe",
e,
error,
)
capture_exception(e)
return Response(
{
"success": False,
Expand All @@ -311,7 +292,29 @@ def create_subscription(attempts=0):
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)

new_subscription = create_subscription()
except Exception as e:
request.user.log_event(
"InvalidRequestError from Stripe while creating subscription.",
"stripe",
e,
)
capture_exception(e)
return Response(
{
"success": False,
"message": None,
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)

def post(self, request, plan_id):
current_plan = request.user.profile.membership_plan
new_plan = PaymentPlan.objects.get(pk=plan_id)

if current_plan:
return Response({"success": False}, status=status.HTTP_409_CONFLICT)

new_subscription = self.create_subscription(request, new_plan)

try:
if new_subscription.status == "active":
Expand Down Expand Up @@ -487,7 +490,7 @@ class SubscriptionInfo(StripeAPIView):
def get(self, request):
current_plan = request.user.profile.membership_plan

if not current_plan:
if not current_plan or not request.user.profile.stripe_subscription_id:
return Response({"success": False})

else:
Expand Down Expand Up @@ -527,14 +530,62 @@ def post(self, request, resume):
)

else:
# this will modify the subscription to automatically cancel at the end of the current payment period
if resume:
if resume and not request.user.profile.stripe_subscription_id:
request.user.log_event(
"Member tried to resume a payment plan that doesn't exist - creating it.",
"stripe",
)
new_subscription = PaymentPlanSignup().create_subscription(
request, current_plan
)

try:
if new_subscription.status == "active":
request.user.profile.stripe_subscription_id = (
new_subscription.id
)
request.user.profile.subscription_status = "active"
request.user.profile.save()

request.user.log_event(
"Successfully created subscription in Stripe.",
"stripe",
"",
)

return Response({"success": True})

elif new_subscription.status == "incomplete":
# if we got here, that means the subscription wasn't successfully created
request.user.log_event(
f"Failed to create subscription in Stripe with status {new_subscription.status}.",
"stripe",
"",
)

return Response(
{"success": True, "message": "signup.subscriptionFailed"}
)

else:
request.user.log_event(
f"Failed to create subscription in Stripe with status {new_subscription.status}.",
"stripe",
"",
)
return Response({"success": True})

except KeyError as e:
capture_exception(e)
return new_subscription or e

elif resume:
modified_subscription = stripe.Subscription.modify(
request.user.profile.stripe_subscription_id,
cancel_at_period_end=False,
)

if modified_subscription.cancel_at_period_end == False:
if not modified_subscription.cancel_at_period_end:
request.user.profile.subscription_status = "active"
request.user.profile.save()
subject = f"{request.user.get_full_name()} resumed their cancelling membership plan."
Expand Down
1 change: 1 addition & 0 deletions src-frontend/src/boot/routeGuards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default boot(({ router, store }) => {
store.getters['profile/profile']?.memberStatus === 'Needs Induction' &&
to.name !== 'membershipPlan' &&
to.name !== 'webcams' &&
to.name !== 'billing' &&
store.getters['config/features']?.enableMembershipPayments &&
to.meta.admin !== true
) {
Expand Down
2 changes: 0 additions & 2 deletions src-frontend/src/components/Billing/SelectTier.vue
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,6 @@ export default defineComponent({
},
cardExistsHandler(value) {
this.cardExists = value;
console.log('UPDATED CARD SAVED TO');
console.log(value);
},
},
});
Expand Down
11 changes: 10 additions & 1 deletion src-frontend/src/pages/MembershipPlan.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@
</div>

<div
v-if="currentPeriodEnd && subscriptionStatus !== 'cancelling'"
v-if="
this.subscriptionInfo?.currentPeriodEnd &&
subscriptionStatus !== 'cancelling'
"
class="q-mb-md"
>
<div class="text-h6 q-py-md">
Expand Down Expand Up @@ -75,6 +78,7 @@
color="error"
:label="$tc('paymentPlans.cancelButton')"
/>
<member-bucks-manage-billing v-else-if="!cardExists" />
<q-btn
v-else
:disable="disableButton"
Expand All @@ -94,10 +98,12 @@ import { mapGetters, mapActions } from 'vuex';
import SelectTier from '@components/Billing/SelectTier.vue';
import SelectedTier from '@components/Billing/SelectedTier.vue';
import SignupRequiredSteps from '@components/Billing/SignupRequiredSteps.vue';
import MemberBucksManageBilling from 'components/MemberBucksManageBilling.vue';
export default defineComponent({
name: 'MembershipTierPage',
components: {
MemberBucksManageBilling,
SelectTier,
SelectedTier,
SignupRequiredSteps,
Expand Down Expand Up @@ -126,6 +132,9 @@ export default defineComponent({
return false;
}
},
cardExists() {
return this?.profile?.financial?.memberBucks?.savedCard?.last4;
},
currentTier() {
return this.profile.financial.membershipTier;
},
Expand Down

0 comments on commit 85f8f15

Please sign in to comment.