Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#3005] Rework payment flow #3592

Merged
merged 4 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
Loading