Skip to content

Commit

Permalink
Merge pull request #3592 from open-formulieren/feature/3005-add-confi…
Browse files Browse the repository at this point in the history
…g-field

[#3005] Rework payment flow
  • Loading branch information
SilviaAmAm authored Dec 12, 2023
2 parents fe955c2 + 68cac2c commit abd8f35
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/configuration/general/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ General configuration
oidc
cms_integration
virus_scan
payment_flow
20 changes: 20 additions & 0 deletions docs/configuration/general/payment_flow.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.. _configuration_general_payment_flow:

============
Payment flow
============

For forms where a payment is required, it is possible to configure when the submission should be registered.
The setting **Wait for payment to register** can be found in the admin in the **General configuration**,
under the header **Registration**.

If **Wait for payment to register** is checked, then a submission will only be sent to the registration backend when
the payment is completed. Note that if the submission should also be co-signed, then it will be registered only when
both the payment and the co-sign are completed.

If **Wait for payment to register** is NOT checked, then the submission is registered as soon as it is completed (unless
co-sign is also needed). Then, once payment is completed, the payment status will be updated in the registration backend.
This means, that for registration backends that create a zaak (StUF-ZDS and ZGW registration backends), the
status of the zaak will be updated. In the case of the email registration backend, a second email will be sent to notify
that the payment has been received. For the MSGraph backend, the ``payment_status.txt`` file will be updated to say that
the payment was received.
13 changes: 13 additions & 0 deletions docs/manual/forms/basics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,19 @@ dezelfde regels als reguliere **Logica**.
Zie ook: :ref:`configuration_payment_index`


Er zijn twee mogelijke flows om inzendingen te registreren voor formulieren waar een betaling nodig is.

1. Zodra de inzending voltooid is, wordt de inzending naar de registratiebackend gestuurd. Als het wordt betaald, wordt
de status van de betaling in de registratiebackend aangepast. In het geval van de e-mailregistratiebackend, gebeurt
dit door een extra (update-)e-mail te sturen, terwijl voor StUF-ZDS en ZGW registratiebackends, de betaalstatus van de zaak
wordt aangepast.
2. De inzending wordt naar de registratiebackend gestuurd pas ná dat de betaling voltooid is.

De flow kan ingesteld worden in de **Algemene Configuratie**.

Zie ook: :ref:`configuration_general_payment_flow`


Gegevens opschonen
------------------

Expand Down
5 changes: 4 additions & 1 deletion src/openforms/config/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,10 @@ class GlobalConfigurationAdmin(
),
(_("Search engines"), {"fields": ("allow_indexing_form_detail",)}),
(_("Plugin configuration"), {"fields": ("plugin_configuration",)}),
(_("Registration"), {"fields": ("registration_attempt_limit",)}),
(
_("Registration"),
{"fields": ("registration_attempt_limit", "wait_for_payment_to_register")},
),
(
_("Virus scan"),
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 3.2.23 on 2023-12-12 08:50

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("config", "0065_theme_uuid"),
]

operations = [
migrations.AddField(
model_name="globalconfiguration",
name="wait_for_payment_to_register",
field=models.BooleanField(
default=False,
help_text="Should a submission be processed (sent to the registration backend) only after payment has been received?",
verbose_name="wait for payment to register",
),
),
]
7 changes: 7 additions & 0 deletions src/openforms/config/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,13 @@ class GlobalConfiguration(SingletonModel):
blank=True,
default=list,
)
wait_for_payment_to_register = models.BooleanField(
verbose_name=_("wait for payment to register"),
help_text=_(
"Should a submission be processed (sent to the registration backend) only after payment has been received?"
),
default=False,
)

objects = GlobalConfigurationManager()

Expand Down
4 changes: 4 additions & 0 deletions src/openforms/logging/logevent.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,10 @@ def registration_payment_update_skip(submission: "Submission"):
_create_log(submission, "registration_payment_update_skip")


def registration_skipped_not_yet_paid(submission: "Submission"):
_create_log(submission, "registration_skipped_not_yet_paid")


# - - -


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{% load i18n %}
{% blocktrans trimmed with lead=log.fmt_lead %}
{{ lead }}: Skipped registration, because this submission is waiting for a payment.
{% endblocktrans %}
2 changes: 1 addition & 1 deletion src/openforms/payments/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def update_submission_payment_status(
submission = Submission.objects.select_related("auth_info").get(id=submission_id)
config = GlobalConfiguration.get_solo()

should_skip = any(
should_skip = config.wait_for_payment_to_register or any(
(
submission.registration_status != RegistrationStatuses.success,
not submission.payment_required,
Expand Down
12 changes: 12 additions & 0 deletions src/openforms/registrations/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,18 @@ def register_submission(submission_id: int, event: PostSubmissionEvents) -> None
return

config = GlobalConfiguration.get_solo()
if (
config.wait_for_payment_to_register
and submission.payment_required
and not submission.payment_user_has_paid
):
logger.debug(
"Skipping registration for submission '%s' as the payment hasn't been received yet.",
submission,
)
logevent.registration_skipped_not_yet_paid(submission)
return

if submission.registration_attempts >= config.registration_attempt_limit:
# if it fails after this many attempts we give up
logevent.registration_attempts_limited(submission)
Expand Down
186 changes: 186 additions & 0 deletions src/openforms/submissions/tests/test_post_submission_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
from django.utils.translation import gettext_lazy as _

from privates.test import temp_private_root
from testfixtures import LogCapture

from openforms.appointments.exceptions import AppointmentRegistrationFailed
from openforms.appointments.tests.utils import setup_jcc
from openforms.authentication.constants import AuthAttribute
from openforms.config.models import GlobalConfiguration
from openforms.emails.tests.factories import ConfirmationEmailTemplateFactory
from openforms.forms.tests.factories import FormDefinitionFactory
from openforms.payments.constants import PaymentStatus
Expand Down Expand Up @@ -211,6 +213,10 @@ def test_submission_completed_payment_needed(self):
patch(
"openforms.registrations.contrib.email.plugin.EmailRegistration.update_payment_status"
) as mock_payment_status_update,
patch(
"openforms.registrations.tasks.GlobalConfiguration.get_solo",
return_value=GlobalConfiguration(wait_for_payment_to_register=False),
),
):
on_post_submission_event(submission.id, PostSubmissionEvents.on_completion)

Expand Down Expand Up @@ -447,6 +453,10 @@ def test_cosign_done_payment_needed_not_done(self):
patch(
"openforms.registrations.contrib.email.plugin.EmailRegistration.update_payment_status"
) as mock_payment_status_update,
patch(
"openforms.registrations.tasks.GlobalConfiguration.get_solo",
return_value=GlobalConfiguration(wait_for_payment_to_register=False),
),
):
with self.captureOnCommitCallbacks(execute=True):
on_post_submission_event(
Expand Down Expand Up @@ -597,6 +607,10 @@ def test_payment_done_cosign_not_needed(self):
patch(
"openforms.registrations.contrib.email.plugin.EmailRegistration.update_payment_status"
) as mock_payment_status_update,
patch(
"openforms.registrations.tasks.GlobalConfiguration.get_solo",
return_value=GlobalConfiguration(wait_for_payment_to_register=False),
),
):
with self.captureOnCommitCallbacks(execute=True):
on_post_submission_event(
Expand Down Expand Up @@ -967,3 +981,175 @@ def test_cosign_not_required_but_filled_in_does_not_proceed_with_registration(se
self.assertTrue(submission.cosign_request_email_sent)
self.assertTrue(submission.confirmation_email_sent)
self.assertEqual(submission.auth_info.value, "111222333")


@temp_private_root()
@override_settings(CELERY_TASK_ALWAYS_EAGER=True)
class PaymentFlowTests(TestCase):
def test_payment_required_and_not_should_wait_for_registration(self):
"""
The payment is required and has not been completed, and the general configuration says to NOT skip registration if
the payment has not been completed.
"""
submission = SubmissionFactory.from_components(
components_list=[
{
"key": "email",
"type": "email",
"label": "Email",
"confirmationRecipient": True,
}
],
completed=True,
form__registration_backend="email",
form__registration_backend_options={"to_emails": ["[email protected]"]},
form__product__price=10,
form__payment_backend="demo",
)
SubmissionPaymentFactory.create(
submission=submission, amount=10, status=PaymentStatus.started
)

with (
patch(
"openforms.registrations.contrib.email.plugin.EmailRegistration.register_submission"
) as mock_registration,
patch(
"openforms.registrations.tasks.GlobalConfiguration.get_solo",
return_value=GlobalConfiguration(wait_for_payment_to_register=True),
),
LogCapture() as logs,
):
on_post_submission_event(submission.id, PostSubmissionEvents.on_completion)

mock_registration.assert_not_called()
logs.check_present(
(
"openforms.registrations.tasks",
"DEBUG",
f"Skipping registration for submission '{submission}' as the payment hasn't been received yet.",
)
)

def test_payment_done_and_should_wait_for_payment(
self,
):
"""
The payment is required and has been completed, so registration should not be skipped regardless of the general
configuration setting.
"""
submission = SubmissionFactory.from_components(
components_list=[
{
"key": "email",
"type": "email",
"label": "Email",
"confirmationRecipient": True,
}
],
form__registration_backend="email",
form__registration_backend_options={"to_emails": ["[email protected]"]},
form__product__price=10,
form__payment_backend="demo",
completed=True,
with_completed_payment=True,
)

with (
patch(
"openforms.registrations.contrib.email.plugin.EmailRegistration.register_submission"
) as mock_registration,
patch(
"openforms.registrations.tasks.GlobalConfiguration.get_solo",
return_value=GlobalConfiguration(wait_for_payment_to_register=True),
),
patch(
"openforms.payments.tasks.update_submission_payment_registration"
) as mock_update_payment_status,
):
on_post_submission_event(submission.id, PostSubmissionEvents.on_completion)

mock_registration.assert_called_once()
mock_update_payment_status.assert_not_called()

def test_payment_done_and_not_should_wait_for_payment(
self,
):
"""
The payment is required and has been completed, so registration should not be skipped regardless of the general
configuration setting.
"""

submission = SubmissionFactory.create(
form__registration_backend="email",
form__registration_backend_options={"to_emails": ["[email protected]"]},
form__product__price=10,
form__payment_backend="demo",
completed=True,
with_completed_payment=True,
)

with (
patch(
"openforms.registrations.contrib.email.plugin.EmailRegistration.register_submission"
) as mock_registration,
patch(
"openforms.registrations.tasks.GlobalConfiguration.get_solo",
return_value=GlobalConfiguration(wait_for_payment_to_register=False),
),
):
on_post_submission_event(submission.id, PostSubmissionEvents.on_completion)

mock_registration.assert_called_once()

def test_payment_not_required_and_should_wait_for_payment(
self,
):
"""
The payment is NOT required, so registration should not be skipped regardless of the general
configuration setting.
"""
submission = SubmissionFactory.create(
form__registration_backend="email",
form__registration_backend_options={"to_emails": ["[email protected]"]},
completed=True,
)

with (
patch(
"openforms.registrations.contrib.email.plugin.EmailRegistration.register_submission"
) as mock_registration,
patch(
"openforms.registrations.tasks.GlobalConfiguration.get_solo",
return_value=GlobalConfiguration(wait_for_payment_to_register=True),
),
):
on_post_submission_event(submission.id, PostSubmissionEvents.on_completion)

mock_registration.assert_called_once()

def test_payment_not_required_and_not_should_wait_for_payment(
self,
):
"""
The payment is NOT required, so registration should not be skipped regardless of the general
configuration setting.
"""
submission = SubmissionFactory.create(
form__registration_backend="email",
form__registration_backend_options={"to_emails": ["[email protected]"]},
completed=True,
)

with (
patch(
"openforms.registrations.contrib.email.plugin.EmailRegistration.register_submission"
) as mock_registration,
patch(
"openforms.registrations.tasks.GlobalConfiguration.get_solo",
return_value=GlobalConfiguration(wait_for_payment_to_register=False),
),
):
on_post_submission_event(submission.id, PostSubmissionEvents.on_completion)

mock_registration.assert_called_once()

0 comments on commit abd8f35

Please sign in to comment.