Adds support for refunds
This commit is contained in:
parent
6c87b9d08a
commit
abee9e3c62
3 changed files with 122 additions and 0 deletions
|
@ -1,8 +1,10 @@
|
|||
import copy
|
||||
import models
|
||||
|
||||
from django import forms
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import F, Q
|
||||
from django.forms import widgets
|
||||
from django.utils import timezone
|
||||
|
||||
|
@ -10,6 +12,8 @@ from django_countries import countries
|
|||
from django_countries.fields import LazyTypedChoiceField
|
||||
from django_countries.widgets import CountrySelectWidget
|
||||
|
||||
from pinax.stripe import models as pinax_stripe_models
|
||||
|
||||
|
||||
class NoRenderWidget(forms.widgets.HiddenInput):
|
||||
|
||||
|
@ -140,6 +144,53 @@ class CreditCardForm(forms.Form):
|
|||
))
|
||||
|
||||
|
||||
class StripeRefundForm(forms.Form):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
'''
|
||||
|
||||
Arguments:
|
||||
user (User): The user whose charges we should filter to.
|
||||
min_value (Decimal): The minimum value of the charges we should
|
||||
show (currently, credit notes can only be cashed out in full.)
|
||||
|
||||
'''
|
||||
user = kwargs.pop('user', None)
|
||||
min_value = kwargs.pop('min_value', None)
|
||||
super(StripeRefundForm, self).__init__(*args, **kwargs)
|
||||
|
||||
payment_field = self.fields['payment']
|
||||
qs = payment_field.queryset
|
||||
|
||||
if user:
|
||||
qs = qs.filter(
|
||||
charge__customer__user=user,
|
||||
)
|
||||
|
||||
if min_value is not None:
|
||||
# amount >= amount_to_refund + amount_refunded
|
||||
# No refunds yet
|
||||
q1 = (
|
||||
Q(charge__amount_refunded__isnull=True) &
|
||||
Q(charge__amount__gte=min_value)
|
||||
)
|
||||
# There are some refunds
|
||||
q2 = (
|
||||
Q(charge__amount_refunded__isnull=False) &
|
||||
Q(charge__amount__gte=(
|
||||
F("charge__amount_refunded") + min_value)
|
||||
)
|
||||
)
|
||||
qs = qs.filter(q1 | q2)
|
||||
|
||||
payment_field.queryset = qs
|
||||
|
||||
payment = forms.ModelChoiceField(
|
||||
required=True,
|
||||
queryset=models.StripePayment.objects.all(),
|
||||
)
|
||||
|
||||
|
||||
'''{
|
||||
From stripe.js details:
|
||||
|
||||
|
|
|
@ -10,5 +10,6 @@ from pinax.stripe.views import (
|
|||
urlpatterns = [
|
||||
url(r"^card/([0-9]*)/$", views.card, name="registripe_card"),
|
||||
url(r"^pubkey/$", views.pubkey_script, name="registripe_pubkey"),
|
||||
url(r"^refund/([0-9]*)/$", views.refund, name="registripe_refund"),
|
||||
url(r"^webhook/$", Webhook.as_view(), name="pinax_stripe_webhook"),
|
||||
]
|
||||
|
|
|
@ -4,14 +4,18 @@ import models
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.db import transaction
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import redirect, render
|
||||
|
||||
from registrasion.controllers.credit_note import CreditNoteController
|
||||
from registrasion.controllers.invoice import InvoiceController
|
||||
from registrasion.models import commerce
|
||||
|
||||
from pinax.stripe import actions
|
||||
from pinax.stripe.actions import refunds as pinax_stripe_actions_refunds
|
||||
|
||||
from stripe.error import StripeError
|
||||
|
||||
from symposion.conference.models import Conference
|
||||
|
@ -20,6 +24,11 @@ CURRENCY = settings.INVOICE_CURRENCY
|
|||
CONFERENCE_ID = settings.CONFERENCE_ID
|
||||
|
||||
|
||||
def _staff_only(user):
|
||||
''' Returns true if the user is staff. '''
|
||||
return user.is_staff
|
||||
|
||||
|
||||
def pubkey_script(request):
|
||||
''' Returns a JS snippet that sets the Stripe public key for Stripe.js. '''
|
||||
|
||||
|
@ -126,3 +135,64 @@ def process_card(request, form, inv):
|
|||
inv.update_status()
|
||||
|
||||
messages.success(request, "This invoice was successfully paid.")
|
||||
|
||||
|
||||
@user_passes_test(_staff_only)
|
||||
def refund(request, credit_note_id):
|
||||
''' Allows staff to select a Stripe charge for the owner of the credit
|
||||
note, and refund the credit note into stripe. '''
|
||||
|
||||
cn = CreditNoteController.for_id_or_404(str(credit_note_id))
|
||||
|
||||
to_credit_note = redirect("credit_note", cn.credit_note.id)
|
||||
|
||||
if not cn.credit_note.is_unclaimed:
|
||||
return to_credit_note
|
||||
|
||||
form = forms.StripeRefundForm(
|
||||
request.POST or None,
|
||||
user=cn.credit_note.invoice.user,
|
||||
min_value=cn.credit_note.value,
|
||||
)
|
||||
|
||||
if request.POST and form.is_valid():
|
||||
try:
|
||||
process_refund(cn, form)
|
||||
return to_credit_note
|
||||
except StripeError as se:
|
||||
form.add_error(None, ValidationError(se))
|
||||
|
||||
data = {
|
||||
"credit_note": cn.credit_note,
|
||||
"form": form,
|
||||
}
|
||||
|
||||
return render(
|
||||
request, "registrasion/stripe/refund.html", data
|
||||
)
|
||||
|
||||
|
||||
def process_refund(cn, form):
|
||||
payment = form.cleaned_data["payment"]
|
||||
charge = payment.charge
|
||||
|
||||
to_refund = cn.credit_note.value
|
||||
stripe_charge_id = charge.stripe_charge.id
|
||||
|
||||
# Test that the given charge is allowed to be refunded.
|
||||
max_refund = actions.charges.calculate_refund_amount(charge)
|
||||
|
||||
if max_refund < to_refund:
|
||||
raise ValidationError(
|
||||
"You must select a payment holding greater value than "
|
||||
"the credit note."
|
||||
)
|
||||
|
||||
refund = actions.refunds.create(charge, to_refund)
|
||||
|
||||
commerce.CreditNoteRefund.objects.create(
|
||||
parent=cn.credit_note,
|
||||
reference="Refunded %s to Stripe charge %s" % (
|
||||
to_refund, stripe_charge_id
|
||||
)
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue