forked from data.coop/membersystem
Compare commits
No commits in common. "9a61c237c77e9394083aea8b5cff7cb6deda4d9c" and "78b3264bb6dd15846c32fb5d3dc77a259582ff99" have entirely different histories.
9a61c237c7
...
78b3264bb6
|
@ -6,5 +6,3 @@ DATABASE_URL=postgres://postgres:postgres@postgres:5432/postgres
|
||||||
# Use something along the the following if you are not using docker
|
# Use something along the the following if you are not using docker
|
||||||
# DATABASE_URL=postgres://postgres:postgres@localhost:5432/datacoop_membersystem
|
# DATABASE_URL=postgres://postgres:postgres@localhost:5432/datacoop_membersystem
|
||||||
DEBUG=True
|
DEBUG=True
|
||||||
STRIPE_API_KEY=sk_test_51Pi1452K58NJXwrP3ayQi0LmGCjiVPUdVFnPOT4didTslRQpmGLSxiuYsmwoTJSLDny5I4uIPcpL2fwpG7GvlTbH00mewgemdz
|
|
||||||
STRIPE_ENDPOINT_SECRET=whsec_
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block head_title %}
|
|
||||||
{% trans "Payment cancelled" %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<div class="content-view">
|
|
||||||
<h2>{% trans "Payment canceled" %}</h2>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="{% order:detail order_id=order.id %}">{% trans "Return to order page" %}</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
|
@ -41,7 +41,7 @@
|
||||||
<h2>{% trans "Total price" %}: {{ order.total_with_vat }}</h2>
|
<h2>{% trans "Total price" %}: {{ order.total_with_vat }}</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="{% url "order:pay" order_id=order.pk %}" class="button">{% trans "Pay now" %}</a>
|
<a href="" class="button">{% trans "Pay now" %}</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block head_title %}
|
|
||||||
{% trans "Payment received" %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<div class="content-view">
|
|
||||||
<h2>{% trans "Payment received" %}</h2>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
{% blocktrans trimmed with order.id as order_id %}
|
|
||||||
We received your payment for Order {{ order_id }}.
|
|
||||||
{% endblocktrans %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
|
@ -1,23 +1,20 @@
|
||||||
"""Views for the membership app."""
|
"""Views for the membership app."""
|
||||||
|
|
||||||
import stripe
|
from __future__ import annotations
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.sites.models import Site
|
from typing import TYPE_CHECKING
|
||||||
from django.core.mail import send_mail
|
|
||||||
from django.db import transaction
|
|
||||||
from django.http import HttpRequest
|
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.shortcuts import redirect
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.urls import reverse
|
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
from django_view_decorator import namespaced_decorator_factory
|
from django_view_decorator import namespaced_decorator_factory
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
order_view = namespaced_decorator_factory(namespace="order", base_path="order")
|
if TYPE_CHECKING:
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
stripe.api_key = settings.STRIPE_API_KEY
|
|
||||||
|
order_view = namespaced_decorator_factory(namespace="order", base_path="order")
|
||||||
|
|
||||||
|
|
||||||
@order_view(
|
@order_view(
|
||||||
|
@ -39,128 +36,3 @@ def order_detail(request: HttpRequest, order_id: int) -> HttpResponse:
|
||||||
template_name="accounting/order/detail.html",
|
template_name="accounting/order/detail.html",
|
||||||
context=context,
|
context=context,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@order_view(
|
|
||||||
paths="<int:order_id>/pay/",
|
|
||||||
name="pay",
|
|
||||||
login_required=True,
|
|
||||||
)
|
|
||||||
def order_pay(request: HttpRequest, order_id: int) -> HttpResponse:
|
|
||||||
"""Create a Stripe session and redirects to Stripe Checkout."""
|
|
||||||
user = request.user # People just need to login to pay something, not necessarily be a member
|
|
||||||
order = models.Order.objects.get(pk=order_id, member=user)
|
|
||||||
current_site = Site.objects.get_current(request)
|
|
||||||
base_domain = f"https://{current_site.domain}"
|
|
||||||
|
|
||||||
try:
|
|
||||||
line_items = []
|
|
||||||
for item in order.items.all():
|
|
||||||
line_items.append( # noqa: PERF401
|
|
||||||
{
|
|
||||||
"price_data": {
|
|
||||||
"currency": item.total_with_vat.currency,
|
|
||||||
"unit_amount": int((item.price + item.vat).amount * 100),
|
|
||||||
"product_data": {
|
|
||||||
"name": item.product.name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"quantity": item.quantity,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
checkout_session = stripe.checkout.Session.create(
|
|
||||||
line_items=line_items,
|
|
||||||
mode="payment",
|
|
||||||
success_url=base_domain
|
|
||||||
+ reverse("order:success", kwargs={"order_id": order.id, "stripe_session_id": "{CHECKOUT_SESSION_ID}"}),
|
|
||||||
cancel_url=base_domain + "/cancel",
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
send_mail("Error in checkout", str(e), settings.DEFAULT_FROM_EMAIL, settings.ADMINS)
|
|
||||||
raise
|
|
||||||
|
|
||||||
# TODO: Redirect with status=303
|
|
||||||
return redirect(checkout_session.url)
|
|
||||||
|
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
@order_view(
|
|
||||||
paths="<int:order_id>/pay/success/<str:stripe_session_id>/",
|
|
||||||
name="success",
|
|
||||||
login_required=True,
|
|
||||||
)
|
|
||||||
def success(request: HttpRequest, order_id: int, stripe_session_id: str) -> HttpResponse:
|
|
||||||
"""Create a Stripe session and redirects to Stripe Checkout.
|
|
||||||
|
|
||||||
From Stripe docs: When you have a webhook endpoint set up to listen for checkout.session.completed events and
|
|
||||||
you set a success_url, Checkout waits for your server to respond to the webhook event delivery before redirecting
|
|
||||||
your customer. If you use this approach, make sure your server responds to checkout.session.completed events as
|
|
||||||
quickly as possible.
|
|
||||||
"""
|
|
||||||
user = request.user # People just need to login to pay something, not necessarily be a member
|
|
||||||
order = models.Order.objects.get(pk=order_id, member=user)
|
|
||||||
|
|
||||||
bool(stripe_session_id)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"order": order,
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(
|
|
||||||
request=request,
|
|
||||||
template_name="accounting/order/success.html",
|
|
||||||
context=context,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
@order_view(
|
|
||||||
paths="<int:order_id>/pay/cancel/",
|
|
||||||
name="cancel",
|
|
||||||
login_required=True,
|
|
||||||
)
|
|
||||||
def cancel(request: HttpRequest, order_id: int) -> HttpResponse:
|
|
||||||
"""Page to display when a payment is canceled."""
|
|
||||||
user = request.user # People just need to login to pay something, not necessarily be a member
|
|
||||||
order = models.Order.objects.get(pk=order_id, member=user)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"order": order,
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(
|
|
||||||
request=request,
|
|
||||||
template_name="accounting/order/cancel.html",
|
|
||||||
context=context,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
@order_view(
|
|
||||||
paths="stripe/webhook/",
|
|
||||||
name="webhook",
|
|
||||||
login_required=True,
|
|
||||||
)
|
|
||||||
def stripe_webhook(request: HttpRequest) -> HttpResponse:
|
|
||||||
"""Handle Stripe webhook.
|
|
||||||
|
|
||||||
https://docs.stripe.com/metadata/use-cases
|
|
||||||
"""
|
|
||||||
payload = request.body
|
|
||||||
sig_header = request.headers["stripe-signature"]
|
|
||||||
event = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
event = stripe.Webhook.construct_event(payload, sig_header, settings.STRIPE_ENDPOINT_SECRET)
|
|
||||||
except ValueError:
|
|
||||||
# Invalid payload
|
|
||||||
return HttpResponse(status=400)
|
|
||||||
except stripe.error.SignatureVerificationError:
|
|
||||||
# Invalid signature
|
|
||||||
return HttpResponse(status=400)
|
|
||||||
|
|
||||||
if event["type"] == "checkout.session.completed" or event["type"] == "checkout.session.async_payment_succeeded":
|
|
||||||
# TODO: Implement the payment
|
|
||||||
pass
|
|
||||||
|
|
||||||
return HttpResponse(status=200)
|
|
||||||
|
|
|
@ -28,8 +28,6 @@ CSRF_TRUSTED_ORIGINS = env.list(
|
||||||
|
|
||||||
ADMINS = [tuple(x.split(":")) for x in env.list("DJANGO_ADMINS", default=[])]
|
ADMINS = [tuple(x.split(":")) for x in env.list("DJANGO_ADMINS", default=[])]
|
||||||
|
|
||||||
DEFAULT_FROM_EMAIL = "server@data.coop"
|
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
@ -180,8 +178,6 @@ LOGGING = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
STRIPE_API_KEY = env.str("STRIPE_API_KEY")
|
|
||||||
STRIPE_ENDPOINT_SECRET = env.str("STRIPE_ENDPOINT_SECRET")
|
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
INSTALLED_APPS += ["debug_toolbar", "django_browser_reload"]
|
INSTALLED_APPS += ["debug_toolbar", "django_browser_reload"]
|
||||||
|
|
Loading…
Reference in a new issue