Checkout (with Stripe SCA) ************************** .. highlight:: python - https://gitlab.com/kb/checkout - Testing_ - `To Do`_ - WIP_ Overview ======== .. image:: ./misc/checkout-overview.png .. tip:: A ``SetupIntent`` does not take the payment, it just collects card details for later use. Card Refresh ------------ The ``ObjectPaymentPlanSetupIntentCardRefreshMixin`` (``checkout.views``) requires login so we can find the ``ObjectPaymentPlan`` for the user. Some code snippets:: # url path("card/refresh/", # view class CheckoutRefreshCardView( LoginRequiredMixin, ObjectPaymentPlanSetupIntentCardRefreshMixin, BaseMixin, UpdateView, ): def get_success_url(self): return reverse( "web.checkout.refresh.card.success", args=[self.object.uuid] ) # template {% include 'checkout/_elements.html' %} {% include 'checkout/_elements.js.html' with override_do_success_action=True %} One-off Payments ---------------- - Are handled using a ``PaymentIntent``. - Are *not* linked to a Stripe ``Customer``. The customer may use a credit card for a payment and not want to update the card used for their payment plan. Payment Plans ------------- - Are started with a ``SetupIntent`` which collects the customer details. - Are linked to a Stripe ``Customer``. - The payments are taken by linking a ``PaymentIntent`` to a ``Customer``. Management Commands =================== Retrieve the payment intent and update the checkout status:: django-admin payment_intent_fulfillment .. tip:: For more info, see `Asynchronously fulfill the customer's order`_ :: django-admin.py payment_intent_on_session_fulfillment Retrieve the setup intent and update the checkout status:: django-admin.py setup_intent_fulfillment Send emails to customers asking them to login and pay (*on-session*):: django-admin.py send_on_session_payment_emails Audit Stripe setup intent (which we marked ``fail``) (`audit_failed_checkout.py`_): - For payment plans, we create a ``setup_intent`` before prompting the user to enter their card details. - If the setup intent is not *complete* within 60 minutes, we mark the linked ``Checkout`` as ``fail``. - My worry was that we marked it as ``fail``, before the user entered their card details. :: django-admin.py audit_failed_checkout Management Commands - Testing ----------------------------- To set the due date for the next instalment... .. warning:: FOR TESTING ONLY... - find your payment plan e.g. http://localhost:8000/checkout/object/payment/plan/1234/ :: django-admin.py testing_set_pending_instalment_due 1234 django-admin.py process_payments Tasks ===== The ``send_on_session_payment_emails`` task sends an email to users who need to carry out an on-session payment. The function checks to see if the user has an active user account and are able to login and pay: - If the user can login and pay, they are sent an email using the ``MAIL_TEMPLATE_ON_SESSION_PAYMENT_ANON`` template. - If the user is not able to login and pay, they are sent an email using the ``MAIL_TEMPLATE_ON_SESSION_PAYMENT_AUTH`` template. Write a ``can_login_and_pay`` function to check if the user can login and pay. Some of our systems use this to check if the customer has an overdue payment. The function is passed to the ``_user_has_active_account`` method (``ObjectPaymentPlanInstalmentManager`` class). It can be as simple as:: def can_login_and_pay(contact): return True .. note:: The ``can_login_and_pay`` must have a single parameter (a contact). Create your task, passing in the ``can_login_and_pay`` function:: @task() def send_on_session_payment_emails(): logger.info("send_on_session_payment_emails") count = ObjectPaymentPlanInstalment.objects.send_on_session_payment_emails( can_login_and_pay ) logger.info( "send_on_session_payment_emails - {} records- complete".format(count) ) return count Add to scheduled tasks (using ``myapp`` as example app name):: # run every 20 minutes "send_on_session_payment_emails": { "task": "myapp.tasks.send_on_session_payment_emails", "schedule": crontab(minute="*/20"), }, You may like to implement a ``send_on_session_payment_emails.py`` management command in your project. For an example, see Other scheduled tasks --------------------- :: "payment_intent_fulfillment": { "task": "checkout.tasks.payment_intent_fulfillment", "schedule": crontab(minute="*/16"), }, "setup_intent_fulfillment": { "task": "checkout.tasks.setup_intent_fulfillment", "schedule": crontab(minute="*/14"), }, # # and don't forget to add your ``send_on_session_payment_emails`` task Template ======== From `Viewport meta tag requirements`_: .. code-block:: html {% block meta_extra %} {% endblock meta_extra %} Add our ``_elements.html`` template: .. code-block:: html {% block content %}
{% include 'checkout/_elements.html' %}
{% endblock %} Add our ``_elements.js.html`` template: .. code-block:: html {% block script_extra %} {{ block.super }} {% include 'checkout/_elements.js.html' with override_do_success_action=True %} {% endblock script_extra %} .. note:: I am not clear on why we need ``override_do_success_action``. Here are the notes from 14 Nov 2019, 18:31: *The issue with the pay on session page is that it does not have a checkout form* (``id_checkout_form``) *it only has the stripe form* (``payment-form``) *so obviously the submit button on the checkout form never gets pressed and therefore the* ``successUrl`` *variable is never set. I've amended* ``_elements.js.html`` *to allow the* ``doSuccessAction`` *function to be overridden and defined a* ``doSuccessAction`` *function in* ``checkout_pay_on_session.html`` *in the ... project*. Testing ======= .. tip:: See the ``testing_set_pending_instalment_due`` management command (above). No authentication (default U.S. card):: 4242424242424242 To test the ``decline_code`` of ``authentication_required``, use the card number:: 4000002760003184 .. warning:: This card requires authentication on all transactions, regardless of how the card is set up. Other card numbers:: 4000056655665556 Visa (debit) 5555555555554444 Mastercard 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:: 4000058260000005 .. tip:: Don't forget to run the `Management Commands`_... Testing Payments for SCA: - https://stripe.com/docs/testing - https://stripe.com/docs/payments/cards/testing Changelog ========= - 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/helper.py``) 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``:: Checkout.objects.all() fails on the institute database with the error AttributeError: 'NoneType' object has no attribute '_base_manager' WIP === 12/11/2019 ---------- The email template for pay on session should check to see if the user has an account and has logged in within the last X months before deciding which template to use. 11/11/2019 ---------- To find the pay on session instalments, use the ``pay_on_session_for_email`` method... We need to create a redirect view which generates a checkout URL e.g:: checkout = Checkout.objects.create_checkout_pay_on_session( instalment, AnonymousUser() ) url = reverse( "web.checkout.pay.on.session", args=[checkout.uuid] ) .. note:: This code is from ``send_on_session_payment_emails``... 11/08/2019 ---------- 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: https://stripe.com/docs/payments/cards/saving-cards#save-payment-method-without-payment 04/07/2019 ---------- Working on: 1. https://stripe.com/docs/payments/cards/saving-cards#saving-card-after-payment 2. https://stripe.com/docs/payments/cards/saving-cards#saving-card-without-payment 03/07/2019 ---------- 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... 17/06/2019 ---------- 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) .. _`Asynchronously fulfill the customer's order`: https://stripe.com/docs/payments/payment-intents/quickstart#fulfillment .. _`audit_failed_checkout.py`: https://gitlab.com/kb/checkout/-/blob/master/checkout/management/commands/audit_failed_checkout.py .. _`Automatic card updates`: https://stripe.com/docs/saving-cards#automatic-card-updates .. _`Separate authorization and capture`: https://stripe.com/docs/charges#auth-capture .. _`Viewport meta tag requirements`: https://stripe.com/docs/payments/payment-intents/quickstart#viewport-meta-requirements .. _checkout: https://www.kbsoftware.co.uk/docs/app/checkout/kb/index.html