Package: baseapp-payments
BaseApp Payments - DjangoThis app provides the integration of Stripe with The SilverLogic'sBaseApp:django-restframeworkanddj-stripeInstall the packageAdd torequirements/base.txt:baseapp-payments==0.16.1Setup Stripe's credentialsAdd to yoursettings/base.py:# StripeSTRIPE_LIVE_SECRET_KEY=env("STRIPE_LIVE_SECRET_KEY")STRIPE_TEST_SECRET_KEY=env("STRIPE_TEST_SECRET_KEY")STRIPE_LIVE_MODE=env("STRIPE_LIVE_MODE")# Change to True in productionDJSTRIPE_WEBHOOK_SECRET=env("DJSTRIPE_WEBHOOK_SECRET")DJSTRIPE_FOREIGN_KEY_TO_FIELD="id"Add the payments_router to your urlpatternsfrombaseapp_payments.routerimportpayments_routerv1_urlpatterns=[...re_path(r"payments",include(payments_router.urls)),...]SubscriberA subscriber can be an User, an Organization, a Project, any model that have anemailproperty. You can specify the model of your subscriber with the setting:DJSTRIPE_SUBSCRIBER_MODEL='apps.organizations.Organization`Make sure to also implementget_subscriber_from_requestin yourapps.users.Userto grab the subscriber for the current authenticated user:classUser(PermissionsMixin,AbstractBaseUser):...defget_subscriber_from_request(self,request):org_pk=request.GET.get('organization')returnOrganization.objects.get(pk=org_pk,admins=request.user)Implement the following methods in the subscriber's model:classOrganization(models.Model):defget_subscription_plan(self):returnself.subscription_plandefsubscription_start_request(self,plan,customer,subscription,request):self.subscription_plan=planself.show_payment_method_action_banner=Falseself.save()defsubscription_cancel_request(self,customer,subscription,request):# in this use case the self.subscription_plan will be set to null when we receive the event from stripe insteadpassdefsubscription_update_request(self,plan,is_upgrade,request):# is_upgrade = current plan's price < new plan's price# if we want to upgrade right way but wait to the end of the period to change plans when it is a downgrade:ifis_upgrade:self.subscription_plan=planself.save()defsubscription_plan_changed_webhook(self,plan,price,event):# stripe's event: invoice.paid# this method is called if the plan is different from the one returned by self.get_subscription_plan()self.subscription_plan=planself.save()defsubscription_deleted_webhook(self,event):# stripe's event: customer.subscription.deletedself.subscription_plan=Noneself.show_payment_method_action_banner=Trueself.save()definvoice_payment_failed_webhook(self,event):# stripe's event: invoice.payment_failedself.show_payment_method_action_banner=Trueself.save()Plan modelYou can extend the plan model by inheritingbaseapp_payments.models.BasePlan:fromdjango.dbimportmodelsfrombaseapp_payments.modelsimportBasePlanclassSubscriptionPlan(BasePlan):video_calls_per_month=models.PositiveIntegerField(default=5)Add to yoursettings/base.pythe path to your custom plan model:BASEAPP_PAYMENTS_PLAN_MODEL = "apps.plans.SubscriptionPlan"Toextend the serializeryou can create a normal serializer:frombaseapp_payments.serializersimportPlanSerializerfrom.modelsimportSubscriptionPlanclassSubscriptionPlanSerializer(PlanSerializer):classMeta:model=SubscriptionPlanfields=super().Meta.fields+("searches_per_month","can_create_favorites",)Then add to yoursettings/base.pythe path to your custom serializer:BASEAPP_PAYMENTS_PLAN_SERIALIZER="apps.plans.serializers.SubscriptionPlanSerializer"One time payment / buy a productImplement method stripe_payment_intent_params in your product model:defstripe_payment_intent_params(self,request,validated_data):price=self.priceifself.class_type.slug=="donation":price=validated_data["amount"]amount=int(price*100)return{"amount":amount,"application_fee_amount":int(self.instructor.stripe_percentage_fee/100.00*amount),"transfer_data":{"destination":self.instructor.stripe_account_id,},}And then call create_payment_intent in the Viewset you're using for 'checkout/purchase' your product:FLOW Examplefrombaseapp_payments.utilsimportcreate_payment_intent,stripe@action(detail=True,methods=["POST"],permission_classes=[permissions.IsAuthenticated],serializer_class=PurchaseClassSerializer,)defpurchase(self,request,*args,**kwargs):class_obj=self.get_object()user=request.userserializer=self.get_serializer(data=request.data)serializer.class_obj=class_objserializer.is_valid(raise_exception=True)ifclass_obj.class_type.slug!="free":payment_intent=create_payment_intent(class_obj,request,serializer.validated_data)ClassStudent.objects.create(student=user,clss=class_obj,payment_intent=payment_intent)else:ClassStudent.objects.create(student=user,clss=class_obj)serializer.send_email()returnresponse.Response({},status=status.HTTP_200_OK)# Stripe's webhook eventsYoucan[listentoanystripeeventsusingthewebhooks](https://dj-stripe.readthedocs.io/en/master/usage/webhooks/)```pyfromdjstripeimportwebhooks@webhooks.handler("customer.subscription.trial_will_end")defmy_handler(event,**kwargs):event.customer.subscriber.show_trial_ended_action_banner=Trueevent.customer.subscriber.save()Two-way sync with StripeWhen the webhook is fully setup all data changed in stripe will be updated in the system receiving the webhooks events. If you have data on Stripe already you canmanually sync. For example with the following command you can sync all data:./manage.pydjstripe_sync_modelsTo doCreate a special error message to be handled by the frontend package
Ex: if by trying to perform an action I'm not able due to payment failure or plan is out of credits for that action then show call to action to upgradeMove tests from FinJoy to this repositoryOne time payments (to buy a product)