Merge branch 'documentation'

This commit is contained in:
Christopher Neugebauer 2016-04-24 21:42:50 +10:00
commit dc3d64e6a8
5 changed files with 219 additions and 4 deletions

39
CONTRIBUTING.rst Normal file
View file

@ -0,0 +1,39 @@
Contributing to Registrasion
============================
I'm glad that you're interested in helping make Registrasion better! Thanks! This guide is meant to help make sure your contributions to the project fit in well.
Making a contribution
---------------------
This project makes use of GitHub issues to track pending work, so new features that need implementation can be found there. If you think a new feature needs to be added, raise an issue for discussion before submitting a Pull Request.
Code Style
----------
We use PEP8. Your code should pass checks by ``flake8`` with no errors or warnings before it will be merged.
We use `Google-style docstrings <http://sphinxcontrib-napoleon.readthedocs.org/en/latest/example_google.html>`_, primarily because they're far far more readable than ReST docstrings. New functions should have complete docstrings, so that new contributors have a chance of understanding how the API works.
Structure
---------
Django Models live in ``registrasion/models``; we separate our models out into separate files, because there's a lot of them. Models are grouped by logical functionality.
Actions that operate on Models live in ``registrasion/controllers``.
Testing
-------
Functionality that lives in ``regsistrasion/controllers`` was developed in a test-driven fashion, which is sensible, given it's where most of the business logic for registrasion lives. If you're changing behaviour of a controller, either submit a test with your pull request, or modify an existing test.
Documentation
-------------
Registrasion aims towards high-quality documentation, so that conference registration managers can understand how the system works, and so that webmasters working for conferences understand how the system fits together. Make sure that you have docstrings :)
The documentation is written in Australian English: *-ise* and not *-ize*, *-our* and not *-or*; *vegemite* and not *peanut butter*, etc etc etc.

View file

@ -1,7 +1,9 @@
============
Registrasion
============
**Registra** (tion for Sympo) **sion**. A conference registration app for Django,
letting conferences big and small sell tickets from within Symposion.
Symposion
---------
``symposion`` is an Open Source conference management solution built with Pinax

View file

@ -6,9 +6,11 @@
Registrasion
============
Registra(tion for Sympo)sion.
Registrasion is a conference registration package that goes well with the Symposion suite of conference management apps for Django. It's designed to manage the sorts of inventories that large conferences need to manage, build up complex tickets with multiple items, and handle payments using whatever payment gateway you happen to have access to
Development of registrasion was commenced by Christopher Neugebauer in 2016, with the gracious support of the Python Software Foundation.
Development of registrasion was commenced by Christopher Neugebauer in 2016, with the generous support of the Python Software Foundation.
Contents:
@ -18,6 +20,7 @@ Contents:
overview
inventory
payments
for-zookeepr-users

147
docs/payments.rst Normal file
View file

@ -0,0 +1,147 @@
.. automodule:: registrasion.models.commerce
Payments and Refunds
====================
Registrasion aims to support whatever payment platform you have available to use. Therefore, Registrasion uses a bare minimum payments model to track money within the system. It's the role of you, as a deployer of Registrasion, to implement a payment application that communicates with your own payment platform.
Invoices may have multiple ``PaymentBase`` objects attached to them; each of these represent a payment against the invoice. Payments can be negative (and this represents a refund against the Invoice), however, this approach is not recommended for use by implementers.
Registrasion also keeps track of money that is not currently attached to invoices through `credit notes`_. Credit notes may be applied to unpaid invoices *in full*, if there is an amount left over from the credit note, a new credit note will be created from that amount. Credit notes may also be released, at which point they're the responsibility of the payment application to create a refund.
Finally, Registrasion provides a `manual payments`_ feature, which allows for staff members to manually report payments into the system. This is the only payment facility built into Registrasion, but it's not intended as a reference implementation.
Making payments
---------------
Making payments is a three-step process:
#. Validate that the invoice is ready to be paid,
#. Create a payment object for the amount that you are paying towards an invoice,
#. Ask the invoice to calculate its status now that the payment has been made.
Pre-validation
~~~~~~~~~~~~~~
Registrasion's ``InvoiceController`` has a ``validate_allowed_to_pay`` method, which performs all of the pre-payment checks (is the invoice still unpaid and non-void? has the registration been amended?).
If the pre-payment check fails, ``InvoiceController`` will raise a Django ``ValidationError``.
Our the ``demopay`` view from the ``registrasion-demo`` project implements pre-validation like so::
from registrasion.controllers.invoice import InvoiceController
from django.core.exceptions import ValidationError
# Get the Registrasion Invoice model
inv = get_object_or_404(rego.Invoice.objects, pk=invoice_id)
invoice = InvoiceController(inv)
try:
invoice.validate_allowed_to_pay() # Verify that we're allowed to do this.
except ValidationError as ve:
messages.error(request, ve.message)
return REDIRECT_TO_INVOICE # And display the validation message.
In most cases, you don't engage your actual payment application until after pre-validation is finished, as this gives you an opportunity to bail out if the invoice isn't able to have funds applied to it.
Applying payments
~~~~~~~~~~~~~~~~~
Payments in Registrasion are represented as subclasses of the ``PaymentBase`` model. ``PaymentBase`` looks like this:
.. autoclass :: PaymentBase
When you implement your own payment application, you'll need to subclass ``PaymentBase``, as this will allow you to add metadata that lets you link the Registrasion payment object with your payment platform's object.
Generally, the ``reference`` field should be something that lets your end-users identify the payment on their credit card statement, and to provide to your team's tech support in case something goes wrong.
Once you've subclassed ``PaymentBase``, applying a payment is really quite simple. In the ``demopay`` view, we have a subclass called ``DemoPayment``::
invoice = InvoiceController(some_invoice_model)
# Create the payment object
models.DemoPayment.objects.create(
invoice=invoice.invoice,
reference="Demo payment by user: " + request.user.username,
amount=invoice.invoice.value,
)
Note that multiple payments can be provided against an ``Invoice``, however, payments that exceed the total value of the invoice will have credit notes generated.
Updating an invoice's status
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``InvoiceController`` has a method called ``update_status``. You should call ``update_status`` immediately after you create a ``PaymentBase`` object, as this keeps invoice and its payments synchronised::
invoice = InvoiceController(some_invoice_model)
invoice.update_status()
Calling ``update_status`` collects the ``PaymentBase`` objects that are attached to the ``Invoice``, and will update the status of the invoice:
* If an invoice is ``VOID``, it will remain void.
* If an invoice is ``UNPAID`` and it now has ``PaymentBase`` objects whose value meets or exceed's the invoice's value, the invoice becomes ``PAID``.
* If an invoice is ``UNPAID`` and it now has ``PaymentBase`` objects whose values sum to zero, the invoice becomes ``VOID``.
* If an invoice is ``PAID`` and it now has ``PaymentBase`` objects of less than the invoice's value, the invoice becomes ``REFUNDED``.
When your invoice becomes ``PAID`` for the first time, if there's a cart of inventory items attached to it, that cart becomes permanently reserved -- that is, all of the items within it are no longer available for other users to purchase. If an invoice becomes ``REFUNDED``, the items in the cart are released, which means that they are available for anyone to purchase again.
(One GitHub Issue #37 is completed) If you overpay an invoice, or pay into an invoice that should not have funds attached, a credit note for the residual payments will also be issued.
In general, although this means you *can* use negative payments to take an invoice into a *REFUNDED* state, it's still much more sensible to use the credit notes facility, as this makes sure that any leftover funds remain tracked in the system.
Credit Notes
------------
When you refund an invoice, often you're doing so in order to make a minor amendment to items that the attendee has purchased. In order to make it easy to transfer funds from a refunded invoice to a new invoice, Registrasion provides an automatic credit note facility.
Credit notes are created when you mark an invoice as refunded, but they're also created if you overpay an invoice, or if you direct money into an invoice that can no longer take payment.
Once created, Credit Notes act as a payment that can be put towards other invoices, or that can be cashed out, back to the original payment platform. Credits can only be applied or cashed out in full.
This means that it's easy to track funds that aren't accounted for by invoices -- it's just the sum of the credit notes that haven't been applied to new invoices, or haven't been cashed out.
We recommend using credit notes to track all of your refunds for consistency; it also allows you to invoice for cancellation fees and the like.
Creating credit notes
~~~~~~~~~~~~~~~~~~~~~
In Registrasion, credit notes originate against invoices, and are represented as negative payments to an invoice.
Credit notes are usually created automatically. In most cases, Credit Notes come about from asking to refund an invoice::
InvoiceController(invoice).refund()
Calling ``refund()`` will generate a refund of all of the payments applied to that invoice.
Otherwise, credit notes come about when invoices are overpaid, in this case, a credit for the overpay amount will be generated.
Applying credits to new invoices
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Credits can be applied to invoices::
CreditNoteController(credit_not).apply_to_invoice(invoice)
This will result in an instance of ``CreditNoteApplication`` being applied as a payment to ``invoice``. ``CreditNoteApplication`` will always be a payment of the full amount of its parent credit note. If this payment overpays the invoice it's being applied to, a credit note for the residual will be generated.
Refunding credit notes
~~~~~~~~~~~~~~~~~~~~~
It is possible to release a credit note back to the original payment platform. To do so, you attach an instance of ``CreditNoteRefund`` to the original ``CreditNote``:
.. autoclass :: CreditNoteRefund
You'll usually want to make a subclass of ``CreditNoteRefund`` for your own purposes, usually so that you can tie Registrasion's internal representation of the refund to a concrete refund on the side of your payment platform.
Note that you can only release a credit back to the payment platform for the full amount of the credit.
Manual payments
---------------
Registrasion provides a *manual payments* feature, which allows for staff members to manually report payments into the system. This is the only payment facility built into Registrasion, but it's not intended as a reference implementation.
The main use case for manual payments is to record the receipt of funds from bank transfers or cheques sent on behalf of attendees.
It's not intended as a reference implementation is because it does not perform validation of the cart before the payment is applied to the invoice.
This means that it's possible for a staff member to apply a payment with a specific invoice reference into the invoice matching that reference. Registrasion will generate a credit note if the invoice is not able to receive payment (e.g. because it has since been voided), you can use that credit note to pay into a valid invoice if necessary.
It is possible for staff to enter a negative amount on a manual payment. This will be treated as a refund. Generally, it's preferred to issue a credit note to an invoice rather than enter a negative amount manually.

View file

@ -180,7 +180,20 @@ class LineItem(models.Model):
@python_2_unicode_compatible
class PaymentBase(models.Model):
''' The base payment type for invoices. Payment apps should subclass this
class to handle implementation-specific issues. '''
class to handle implementation-specific issues.
Attributes:
invoice (inventory.Invoice): The invoice that this payment applies to.
time (datetime): The time that this payment was generated. Note that
this will default to the current time when the model is created.
reference (str): A human-readable reference for the payment, this will
be displayed alongside the invoice.
amount (Decimal): The amount the payment is for.
'''
class Meta:
ordering = ("time", )
@ -280,7 +293,18 @@ class CreditNoteApplication(CleanOnSave, PaymentBase):
class CreditNoteRefund(CleanOnSave, models.Model):
''' Represents a refund of a credit note to an external payment.
Credit notes may only be refunded in full. How those refunds are handled
is left as an exercise to the payment app. '''
is left as an exercise to the payment app.
Attributes:
parent (commerce.CreditNote): The CreditNote that this refund
corresponds to.
time (datetime): The time that this refund was generated.
reference (str): A human-readable reference for the refund, this should
allow the user to identify the refund in their records.
'''
def clean(self):
if not hasattr(self, "parent"):