This app replaces the old pay

from checkout.forms import CheckoutForm

class JobCheckoutForm(CheckoutForm):

    class Meta:
        model = Job
        fields = (

from checkout.views import CheckoutMixin

class JobCheckoutView(
    PageFormMixin, CheckoutMixin, BaseMixin, UpdateView):

    model = Job
    form_class = JobCheckoutForm

In the html template:

{% include '_form.html' with submit='Checkout' form_id='id_checkout_form' %}

{% block script %}
  {{ block.super }}
  {% include 'checkout/_stripe.js.html' %}
{% endblock script %}

In your .private file, add your test keys for Stripe:

export STRIPE_PUBLISH_KEY="pk_test_123"
export STRIPE_SECRET_KEY="sk_test_456"


from checkout.tests.helper import check_checkout

def test_checkout():
    obj = JobFactory()

Payment Plan Instalment

If a user has marked a payment plan instalment as paid (using Mark Paid) and you want to make it due again.

Find the primary key of the ObjectPaymentPlanInstalment which is due:

from checkout.models import ObjectPaymentPlanInstalment
content_object = ObjectPaymentPlanInstalment.objects.get(pk=3164)

Check the payment plan is for the correct person:

# <Enrol: Mr P Kimber>

Create a Checkout object and fail the payment:

from checkout.models import Checkout, CheckoutAction
from django.contrib.auth.models import User

user = User.objects.get(username="patrick.kimber")
checkout = Checkout.objects.create_checkout(CheckoutAction.objects.manual, content_object, user)


To set-up Stripe, tick Payment reviews in Emails (Settings). Stripe will send email notifications for new payments placed in the review queue.



To mock stripe.Charge.create and stripe.Customer.create:

import attr
from unittest.mock import patch

class StripeCustomer:
    id = attr.ib()

@patch("stripe.Customer.create", return_value=StripeCustomer(id="xyz"))
def test_payment(mock_charge, mock_customer, client):
    assert mock_charge.called is True
    assert mock_customer.called is True


If you are using a copy of the live data set and you want to run test payments, then you might get a No such customer error. This is because the customer numbers on the live system will not match the customer numbers on your test system.

To remove the customer records on the live system:

from checkout.models import Customer

Payment Plan

To test the payment plans…

Find a valid customer ID on the Stripe dashboard e.g:: cus_DsK5YbWqKHP9jj.


from datetime import date
from checkout.models import Customer, ObjectPaymentPlanInstalment

Update all customers on your data set to point to this customer e.g:


Find an instalment which is due:

next_due = date(2018, 11, 1)
instalment = ObjectPaymentPlanInstalment.objects.filter(due=next_due).first()

Set the date on the instalment to today:

instalment.due =

Check the instalment is due (and meets all other conditions):


Run the payment run management command: process_payments

Secure Customer Authentication (SCA)


  • We are not using the CustomerPayment model. It was used to take money from a customer. It probably needs to be updated to send an email to a customer asking them to authorise payment. I think CustomerCardRefreshFormView does something similar.

  • We have removed CustomerCheckoutRefreshUpdateView. It is used to update card details for a customer. I think we should probably send an email to the customer asking them to update their card details. CustomerCardRefreshFormView might do this (see below).

  • We have removed CustomerCardRefreshFormView. I am not sure what this view would achieve? Would it create a setup_intent? What would the customer expect us to do with the payment method?

  • Not sure if we need refresh_card_expiry_dates or report_card_expiry_dates. How would they work with the requirement for on-session authentication?

To Do

  • Write a task (+ management command) to iterate through payment plans with a CheckoutState of PAY_ON_SESSION. Send the user an email asking them to login and authenticate the payment. Make sure we send just one email… 27/08/2019, Complete (see ticket)

  • Test the transition from the old system (saving cards) to the new system (payment_methods). 27/08/2019, done without any issues. It just seems to work :)

To Do on MD’s 4075-stripe-sca-modal branch

  • Re-instated checkout_actions, checkout_can_charge (see checkout/tests/ and test_check_checkout (for the Customer). I was hoping we wouldn’t need these for the SCA version of checkout (search for PJK 04/09/2019).

  • Re-instated tests for is_expiring on the Customer model e.g. test_is_expiring_future. I was hoping we wouldn’t need these for the SCA version of checkout.

  • CustomerPayment has been re-instated. I was hoping we wouldn’t need these for the SCA version of checkout.

  • CustomerChargeCreateView has been re-instated. I was hoping we wouldn’t need these for the SCA version of checkout.

  • Write a test for Checkout, __str__. It fails with the following error when the checkout has no content_object:

    fails on the institute database with the error AttributeError:
    'NoneType' object has no attribute '_base_manager'

Management Commands

Retrieve the payment intent and update the checkout status: payment_intent_fulfillment

Retrieve the setup intent and update the checkout status: setup_intent_fulfillment

Send emails to customers asking them to login and pay (on-session): send_on_session_payment_emails


From Viewport meta tag requirements:

{% block meta_extra %}
  <meta name="viewport" content="width=device-width, initial-scale=1" />
{% endblock meta_extra %}

Add our _elements.html template:

{% block content %}
  <div class="pure-g">
    <div class="pure-u-1">
      {% include 'checkout/_elements.html' %}
{% endblock %}


Testing Payments for SCA

To test the decline_code of authentication_required, use the card number:



This card requires authentication on all transactions, regardless of how the card is set up.

When testing Elements, you will be asked to enter a ZIP code. To allow entry of a UK postcode, use a UK/GB debit card number:




  1. Our example SalesLedgerSessionRedirectView calls create_checkout.

  2. create_checkout also creates a payment intent and saves it’s ID with the Checkout object.

  3. The SalesLedgerCheckoutView inherits from CheckoutMixin.

  4. The CheckoutMixin adds the client_secret from the payment intent to the template context.

  5. The Stripe JavaScript code in the _elements.js.html template will collect the payment.

  6. The stripe_intent_fulfillment method (and management command) will update the state of pending checkout objects with the status of the intent.

Working on…

Step 5: Attach the PaymentMethod to a Customer after success:


The current checkout view allows users to choose payment, payment plan or invoice. Our customer site has an initial page where the user can choose how they want to pay - so our new checkout page should already know…


Current Models

  • Checkout (keep track of a payment request)

  • CheckoutAction (charge, card refresh, payment plan)

  • CheckoutAdditional (additional information e.g. address)

  • CheckoutSettings (default payment plan)

  • CheckoutState (pending, request, success)

  • Customer (Stripe customer - email address is the primary key)

  • CustomerPayment (payment taken from a customer) What is this for?

  • ObjectPaymentPlan (payment plan for a content_object)

  • ObjectPaymentPlanInstalment (payment plan instalment for a payment plan)

  • PaymentPlan (template)

  • PaymentRun (date of payment run)

  • PaymentRunItem (instalment for payment run)