feat: stripe webhook for camp sales

This commit is contained in:
vas3k 2024-03-14 14:18:21 +01:00
parent 3fe041abae
commit 1056616351
8 changed files with 134 additions and 15 deletions

View File

@ -49,6 +49,7 @@ jobs:
secret_STRIPE_API_KEY: ${{ secrets.STRIPE_API_KEY }}
secret_STRIPE_PUBLIC_KEY: ${{ secrets.STRIPE_PUBLIC_KEY }}
secret_STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }}
secret_STRIPE_CAMP_WEBHOOK_SECRET: ${{ secrets.STRIPE_CAMP_WEBHOOK_SECRET }}
secret_COINBASE_WEBHOOK_SECRET: ${{ secrets.COINBASE_WEBHOOK_SECRET }}
secret_PATREON_CLIENT_ID: ${{ secrets.PATREON_CLIENT_ID }}
secret_PATREON_CLIENT_SECRET: ${{ secrets.PATREON_CLIENT_SECRET }}

View File

@ -28,6 +28,7 @@ from notifications.webhooks import webhook_event
from payments.views.common import membership_expired
from payments.api import api_gift_days
from payments.views.stripe import pay, done, stripe_webhook, stop_subscription
from payments.views.camp import stripe_camp_webhook
from payments.views.crypto import crypto, coinbase_webhook
from posts.api import md_show_post, api_show_post, json_feed
from posts.models.post import Post
@ -88,6 +89,7 @@ urlpatterns = [
path("monies/membership_expired/", membership_expired, name="membership_expired"),
path("monies/subscription/<str:subscription_id>/stop/", stop_subscription, name="stop_subscription"),
path("monies/stripe/webhook/", stripe_webhook, name="stripe_webhook"),
path("monies/stripe/webhook_camp/", stripe_camp_webhook, name="stripe_camp_webhook"),
path("monies/coinbase/webhook/", coinbase_webhook, name="coinbase_webhook"),
path("monies/gift/<int:days>/<slug:user_slug>.json", api_gift_days, name="api_gift_days"),

View File

@ -301,6 +301,12 @@ ACHIEVEMENTS = [
"image": "https://vas3k.club/static/images/achievements/music_jam_6.png",
"style": "background-color: #49D6AC; font-size: 120%;",
}),
("vas3k_camp_2024", {
"name": "Вастрик 🔥 Кэмп 2024",
"description": "Участник клубного Кэмпа в Сербии летом 2024",
"image": "https://vas3k.club/static/images/achievements/vas3k_camp_2024.jpg",
"style": "background-color: #F1DFC5; font-size: 130%;",
}),
]
# VIP: https://i.vas3k.club/3cb.png

View File

@ -0,0 +1,47 @@
{% extends "emails/layout.html" %}
{% load static %}
{% load text_filters %}
{% block css %}
<style>
p {
font-size: 19px;
}
ul {
list-style: none;
font-size: 19px;
margin-left: 0;
padding-left: 0;
}
li {
display: block;
margin-top: 20px;
padding-left: 15px;
}
</style>
{% endblock %}
{% block title %}
🔥 Ждём вас на Вастрик Кэмпе 2024
{% endblock %}
{% block body %}
<p>
👋 Это тестовое письмо пока не будет готово настоящее
</p>
<p>
<a href="{{ settings.APP_HOST }}{% url "login" %}" class="button">Кнопка входа в чат</a>
</p>
<p>
Кек пук
</p>
<br><br><br>
{% endblock %}
{% block unsubscribe %}{% endblock %}

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -10,6 +10,25 @@ from users.models.user import User
log = logging.getLogger(__name__)
def parse_stripe_webhook_event(request, webhook_secret):
payload = request.body
sig_header = request.META.get("HTTP_STRIPE_SIGNATURE")
if not payload or not sig_header:
raise BadRequest(code=400, message="[invalid payload]")
try:
event = stripe.Webhook.construct_event(
payload, sig_header, webhook_secret
)
except ValueError:
raise BadRequest(code=400, message="[invalid payload]")
except stripe.error.SignatureVerificationError:
raise BadRequest(code=400, message="[invalid signature]")
return event
def cancel_all_stripe_subscriptions(stripe_id: str) -> bool:
if not stripe_id:
return False

53
payments/views/camp.py Normal file
View File

@ -0,0 +1,53 @@
import logging
import os
from django.http import HttpResponse
from django.template import loader
from django_q.tasks import async_task
from club.exceptions import BadRequest
from notifications.email.sender import send_transactional_email
from notifications.signals.achievements import async_create_or_update_achievement
from payments.helpers import parse_stripe_webhook_event
from users.models.achievements import Achievement, UserAchievement
from users.models.user import User
log = logging.getLogger()
STRIPE_CAMP_WEBHOOK_SECRET = os.getenv("STRIPE_CAMP_WEBHOOK_SECRET")
# TODO: this is a temporary webhook, remove it after June 2024 (when the camp is over)
def stripe_camp_webhook(request):
try:
event = parse_stripe_webhook_event(request, STRIPE_CAMP_WEBHOOK_SECRET)
except BadRequest as ex:
return HttpResponse(ex.message, status=ex.code)
if event["type"] == "checkout.session.completed":
session = event["data"]["object"]
user = User.objects.filter(email=session["customer_details"]["email"].lower()).first()
if user:
camp_achievement = Achievement.objects.filter(code="vas3k_camp_2024").first()
user_achievement, is_created = UserAchievement.objects.get_or_create(
user=user,
achievement=camp_achievement,
)
if is_created:
async_create_or_update_achievement(user_achievement)
# send confirmation email
camp_confirmation_template = loader.get_template("emails/camp_confirmation.html")
async_task(
send_transactional_email,
recipient=session["customer_details"]["email"].lower(),
subject=f"🔥 Ждём вас на Вастрик Кэмпе 2024",
html=camp_confirmation_template.render({"user": user})
)
return HttpResponse("[ok]", status=200)
return HttpResponse("[unknown event]", status=400)

View File

@ -6,7 +6,9 @@ from django.http import HttpResponse
from django.shortcuts import render
from authn.decorators.auth import require_auth
from club.exceptions import BadRequest
from payments.exceptions import PaymentException
from payments.helpers import parse_stripe_webhook_event
from payments.models import Payment
from payments.products import PRODUCTS, find_by_stripe_id, TAX_RATE_VAT
from payments.service import stripe
@ -156,22 +158,10 @@ def stop_subscription(request, subscription_id):
def stripe_webhook(request):
payload = request.body
sig_header = request.META.get("HTTP_STRIPE_SIGNATURE")
if not payload or not sig_header:
return HttpResponse("[invalid payload]", status=400)
try:
event = stripe.Webhook.construct_event(
payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
)
except ValueError:
return HttpResponse("[invalid payload]", status=400)
except stripe.error.SignatureVerificationError:
return HttpResponse("[invalid signature]", status=400)
log.info("Stripe webhook event: " + event["type"])
event = parse_stripe_webhook_event(request, settings.STRIPE_WEBHOOK_SECRET)
except BadRequest as ex:
return HttpResponse(ex.message, status=ex.code)
if event["type"] == "checkout.session.completed":
session = event["data"]["object"]
@ -214,3 +204,4 @@ def stripe_webhook(request):
return HttpResponse("[ok]", status=200)
return HttpResponse("[unknown event]", status=400)