Payments¶
django-user-payments allows quickly adding line items for a user and paying later for those.
For example, if some functionality is really expensive you might want to add a line item each time the user requests the functionality:
@login_required
def expensive_view(request):
LineItem.objects.create(
user=request.user,
amount=Decimal("0.05"),
title="expensive view at %s" % timezone.now(),
)
# .. further processing and response generation
At the time the user wants to pay the costs that have run up you create a pending payment and maybe process it using a moocher.
A quick introduction to moochers
Moochers (provided by django-mooch) take a request and a payment instance, show a form or a button, and handle interaction with and responses from payment service providers. They allow processing individual payments one at a time.
django-user-payments’ Payment
model extends the abstract
mooch.Payment
so that moochers may be readily used.
The first view below, pay
creates the pending payment and redirects
the user to the next step. pay_payment
fetches the pending payment
from the database and allows selecting a payment method. Further
processing is the responsibility of the selected moocher.
@login_required
def pay(request):
payment = Payment.objects.create_pending(user=request.user)
if not payment:
# No line items, redirect somewhere else!
return ...
# django-mooch's Payment uses UUID4 fields:
return redirect('pay_payment', id=payment.id.hex)
@login_required
def pay_payment(request, id):
payment = get_object_or_404(
request.user.user_payments.pending(),
id=id,
)
return render(request, "pay_payment.html", {
"payment": payment,
"moochers": [
moocher.payment_form(request, payment)
for moocher in moochers.values()
],
})
A payment life cycle¶
Payments will most often be created by calling
Payment.objects.create_pending(user=<user>)
. This creates an unpaid
payment instance and binds all unbound line items to the payment
instance by updating their payment
foreign key field. The amount
fields of all line items are summed up and assigned to the payments’
amount
field. If there were no unbound line items, no payment
instance is created and the manager method returns None
.
Next, the instance is hopefully processed by a moocher or
django-user-payment’s processing which will be discussed later. A
paid-for payment has its nullable charged_at
field (among some other
fields) set to the date and time of payment.
If payment or processing failed for some reason, the payment instance is
in most cases not very useful anymore. Deleting the instance directly
fails because the line items’ payment
foreign key protects against
cascading deletion. Instead, payment.cancel_pending()
unbinds the
line items from the payment and deletes the payment instance.
Undoing payments¶
In rare cases it may even be necessary to undo a payment which has
already been marked as paid, respectively has its charged_at
field
set to a truthy value. In this case, the payment.undo()
method sets
charged_at
back to None
and unbinds all the payments’ line
items.