diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6df67c3..3eecdf9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,40 +10,84 @@ on: pull_request: workflow_dispatch: +env: + IMAGE_NAME: openformulieren/open-forms-ext-stuf-zds-payments + jobs: tests: + name: Run the Django test suite runs-on: ubuntu-latest - strategy: - matrix: - python: ['3.10', '3.11', '3.12'] - django: ['4.2'] - - name: Run the test suite (Python ${{ matrix.python }}, Django ${{ matrix.django }}) + services: + postgres: + image: postgres:14 + env: + POSTGRES_HOST_AUTH_METHOD: trust + ports: + - 5432:5432 + # Needed because the postgres container does not provide a healthcheck + options: + --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + --name postgres + redis: + image: redis:6 + ports: + - 6379:6379 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - name: Checkout Open Forms + uses: actions/checkout@v4 + with: + repository: open-formulieren/open-forms + path: open-forms + + - name: Checkout StUF-ZDS payments extension + uses: actions/checkout@v4 with: - python-version: ${{ matrix.python }} + path: extension - - name: Install dependencies - run: pip install tox tox-gh-actions + - name: Set up backend environment + uses: maykinmedia/setup-django-backend@v1.1 + with: + apt-packages: 'libxml2 libxmlsec1 libxmlsec1-openssl gettext postgresql-client gdal-bin' + python-version: '3.12' + optimize-postgres: 'yes' + pg-service: 'postgres' + setup-node: 'yes' + nvmrc-custom-dir: 'open-forms' + npm-ci-flags: '--legacy-peer-deps' + working-directory: ${{ github.workspace }}/open-forms + + - name: Make symlink in OF to the extension + run: | + ln -s ${{ github.workspace }}/extension/stuf_zds_payments ${{ github.workspace }}/open-forms/src - name: Run tests - run: tox + run: | + export OPEN_FORMS_EXTENSIONS=stuf_zds_payments + + python src/manage.py compilemessages + coverage run --source=stuf_zds_payments src/manage.py test stuf_zds_payments + coverage combine + coverage xml -o coverage-extension.xml env: - PYTHON_VERSION: ${{ matrix.python }} - DJANGO: ${{ matrix.django }} + DJANGO_SETTINGS_MODULE: openforms.conf.ci + SECRET_KEY: dummy + DB_USER: postgres + DB_PASSWORD: '' + working-directory: ${{ github.workspace }}/open-forms - name: Publish coverage report uses: codecov/codecov-action@v4 with: - token: ${{ secrets.CODECOV_TOKEN }} + root_dir: ${{ github.workspace }}/extension + working-directory: ${{ github.workspace }}/open-forms + files: ./coverage-extension.xml publish: name: Publish package to PyPI runs-on: ubuntu-latest - needs: tests + needs: + - tests environment: release permissions: id-token: write @@ -54,13 +98,11 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '3.12' - name: Build sdist and wheel run: | - pip install build --upgrade - python -m build - + pip install pip build --upgrade + python -m build --sdist --wheel - name: Publish a Python distribution to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index 0433b75..2c574fb 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -20,12 +20,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - toxenv: [isort, black, flake8, docs] + toxenv: [isort, black, flake8] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '3.12' - name: Install dependencies run: pip install tox - run: tox diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 14b81aa..51fd50c 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -7,7 +7,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.10" + python: "3.12" sphinx: configuration: docs/conf.py diff --git a/README.rst b/README.rst index ccb99aa..66841c5 100644 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ Welcome to stuf_zds_payments's documentation! :Version: 0.1.0 :Source: https://github.com/maykinmedia/stuf_zds_payments :Keywords: ```` -:PythonVersion: 3.10 +:PythonVersion: 3.12 |build-status| |code-quality| |black| |coverage| |docs| @@ -30,7 +30,7 @@ Installation Requirements ------------ -* Python 3.10 or above +* Python 3.12 or above * Django 4.2 or newer diff --git a/pyproject.toml b/pyproject.toml index 8eba109..adedff9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,12 +20,10 @@ classifiers = [ "Operating System :: Unix", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Software Development :: Libraries :: Python Modules", ] -requires-python = ">=3.10" +requires-python = ">=3.12" dependencies = [ "django>=4.2" ] @@ -45,6 +43,7 @@ tests = [ "isort", "black", "flake8", + "vcrpy", ] docs = [ "sphinx", diff --git a/stuf_zds_payments/apps.py b/stuf_zds_payments/apps.py index f512077..ec5cadd 100644 --- a/stuf_zds_payments/apps.py +++ b/stuf_zds_payments/apps.py @@ -1,5 +1,11 @@ from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ class StufZdsPaymentsConfig(AppConfig): name = "stuf_zds_payments" + label = "registration_stuf_zds_payments" + verbose_name = _("StUF-ZDS (extra payments) registration plugin") + + def ready(self): + from . import plugin # noqa diff --git a/stuf_zds_payments/client.py b/stuf_zds_payments/client.py new file mode 100644 index 0000000..7a63f1e --- /dev/null +++ b/stuf_zds_payments/client.py @@ -0,0 +1,73 @@ +from functools import partial + +from django.utils import timezone + +from openforms.logging import logevent +from stuf.constants import EndpointType +from stuf.models import StufService +from stuf.service_client_factory import ServiceClientFactory, get_client_init_kwargs +from stuf.stuf_zds.client import ( + Client as _Client, + NoServiceConfigured, + PaymentStatus, + ZaakOptions, + fmt_soap_date, +) +from stuf.stuf_zds.models import StufZDSConfig + + +class ZaakOptions(ZaakOptions): + payment_status_update_mapping: list[dict] + + +class Client(_Client): + def set_zaak_payment( + self, + zaak_identificatie: str, + partial: bool = False, + extra: dict | None = None, + ) -> dict: + data = { + "betalings_indicatie": ( + PaymentStatus.PARTIAL if partial else PaymentStatus.FULL + ), + "laatste_betaaldatum": fmt_soap_date(timezone.now()), + "extra": extra, + } + return self.partial_update_zaak(zaak_identificatie, data) + + def partial_update_zaak(self, zaak_identificatie: str, zaak_data: dict) -> None: + context = { + "zaak_identificatie": zaak_identificatie, + **zaak_data, + } + + self.execute_call( + soap_action="updateZaak_Lk01", + template="registrations/contrib/stuf_zds_payments/updateZaak.xml", + context=context, + endpoint_type=EndpointType.ontvang_asynchroon, + ) + + +def get_client(options: ZaakOptions): + config = StufZDSConfig.get_solo() + if not (service := config.service): + raise NoServiceConfigured("You must configure a service!") + return StufZDSClient(service, options) + + +def StufZDSClient(service: StufService, options: ZaakOptions) -> "Client": + factory = ServiceClientFactory(service) + init_kwargs = get_client_init_kwargs( + service, + request_log_hook=partial(logevent.stuf_zds_request, service), + ) + + return Client.configure_from( + factory, + options=options, + failure_log_callback=partial(logevent.stuf_zds_failure_response, service), + success_log_callback=partial(logevent.stuf_zds_success_response, service), + **init_kwargs, + ) diff --git a/stuf_zds_payments/plugin.py b/stuf_zds_payments/plugin.py new file mode 100644 index 0000000..914e411 --- /dev/null +++ b/stuf_zds_payments/plugin.py @@ -0,0 +1,132 @@ +import logging +from typing import Any + +from django.utils.translation import gettext_lazy as _ + +from openforms.registrations.contrib.stuf_zds.plugin import ( + StufZDSRegistration, + ZaakOptionsSerializer, +) +from openforms.registrations.contrib.stuf_zds.utils import flatten_data +from openforms.registrations.registry import register +from openforms.submissions.models import Submission +from openforms.variables.service import get_static_variables +from rest_framework import serializers +from stuf.stuf_zds.client import ZaakOptions + +from .client import get_client +from .registration_variables import register as variables_registry + +logger = logging.getLogger(__name__) + +PLUGIN_IDENTIFIER = "stuf-zds-create-zaak:ext-utrecht" + + +def default_payment_status_update_mapping() -> list[dict[str, str]]: + return [ + {"form_variable": "payment_completed", "stuf_name": "payment_completed"}, + {"form_variable": "payment_amount", "stuf_name": "payment_amount"}, + { + "form_variable": "payment_public_order_ids", + "stuf_name": "payment_public_order_ids", + }, + ] + + +def prepare_value(value: Any): + match value: + case bool(): + return "true" if value else "false" + case float(): + return str(value) + case _: + return value + + +class MappingSerializer(serializers.Serializer): + form_variable = serializers.CharField( + help_text=_("The name of the form variable to be mapped") + ) + stuf_name = serializers.CharField( + help_text=_("The name in StUF-ZDS to which the form variable should be mapped"), + label=_("StUF-ZDS name"), + ) + + +class ZaakPaymentOptionsSerializer(ZaakOptionsSerializer): + payment_status_update_mapping = MappingSerializer( + many=True, + label=_("payment status update variable mapping"), + help_text=_( + "This mapping is used to map the variable keys to keys used in the XML " + "that is sent to StUF-ZDS. Those keys and the values belonging to them in " + "the submission data are included in extraElementen." + ), + required=False, + ) + + @classmethod + def display_as_jsonschema(cls): + data = super().display_as_jsonschema() + # Workaround because drf_jsonschema_serializer does not pick up defaults + data["properties"]["payment_status_update_mapping"][ + "default" + ] = default_payment_status_update_mapping() + # To avoid duplicating the title and help text for each item + del data["properties"]["payment_status_update_mapping"]["items"]["title"] + del data["properties"]["payment_status_update_mapping"]["items"]["description"] + return data + + +@register(PLUGIN_IDENTIFIER) +class StufZDSPaymentsRegistration(StufZDSRegistration): + verbose_name = _("StUF-ZDS (payments)") + configuration_options = ZaakPaymentOptionsSerializer + + def get_extra_payment_variables( + self, submission: "Submission", options: ZaakOptions + ): + key_mapping = { + mapping["form_variable"]: mapping["stuf_name"] + for mapping in options["payment_status_update_mapping"] + } + return { + key_mapping[variable.key]: prepare_value(variable.initial_value) + for variable in get_static_variables( + submission=submission, + variables_registry=variables_registry, + ) + if variable.key + in ["payment_completed", "payment_amount", "payment_public_order_ids"] + and variable.key in key_mapping + } + + def get_extra_data( + self, submission: Submission, options: ZaakOptions + ) -> dict[str, Any]: + """ + Overridden to ensure the extra payment variables are sent as extraElementen + when creating the Zaak + """ + data = super().get_extra_data(submission, options) + payment_extra = self.get_extra_payment_variables(submission, options) + return {**data, **payment_extra} + + def update_payment_status(self, submission: "Submission", options: ZaakOptions): + extra_data = self.get_extra_data(submission, options) + # The extraElement tag of StUF-ZDS expects primitive types + extra_data = flatten_data(extra_data) + + class LangInjection: + """Ensures the first extra element is the submission language + and isn't shadowed by a form field with the same key""" + + def items(self): + yield ("language_code", submission.language_code) + yield from extra_data.items() + + with get_client(options) as client: + client.set_zaak_payment( + submission.registration_result["zaak"], + extra=LangInjection(), + ) diff --git a/stuf_zds_payments/registration_variables.py b/stuf_zds_payments/registration_variables.py new file mode 100644 index 0000000..38390a3 --- /dev/null +++ b/stuf_zds_payments/registration_variables.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from django.utils.translation import gettext_lazy as _ + +from openforms.plugins.registry import BaseRegistry +from openforms.variables.base import BaseStaticVariable +from openforms.variables.constants import FormVariableDataTypes + +if TYPE_CHECKING: + from openforms.submissions.models import Submission + + +class Registry(BaseRegistry[BaseStaticVariable]): + """ + A registry for the StUF-ZDS (payments) registration variables. + """ + + module = "stuf_zds_payments" + + +register = Registry() +"""The StUF-ZDS (payments) registration variables registry.""" + + +@register("payment_completed") +class PaymentCompleted(BaseStaticVariable): + name = _("Payment completed") + data_type = FormVariableDataTypes.boolean + + def get_initial_value(self, submission: Submission | None = None): + if submission is None: + return None + return submission.payment_user_has_paid + + +@register("payment_amount") +class PaymentAmount(BaseStaticVariable): + name = _("Payment amount") + data_type = FormVariableDataTypes.float + + def get_initial_value(self, submission: Submission | None = None): + if submission is None: + return None + if submission.price is None: + return None + return float(submission.price) + + +@register("payment_public_order_ids") +class PaymentPublicOrderIds(BaseStaticVariable): + name = _("Payment public order IDs") + data_type = FormVariableDataTypes.array + + def get_initial_value(self, submission: Submission | None = None): + if submission is None: + return None + return submission.payments.get_completed_public_order_ids() diff --git a/stuf_zds_payments/templates/registrations/contrib/stuf_zds_payments/updateZaak.xml b/stuf_zds_payments/templates/registrations/contrib/stuf_zds_payments/updateZaak.xml new file mode 100644 index 0000000..981263d --- /dev/null +++ b/stuf_zds_payments/templates/registrations/contrib/stuf_zds_payments/updateZaak.xml @@ -0,0 +1,21 @@ +{% extends "stuf/soap_envelope.xml" %}{% load stuf %} +{% block body %} +{# Parent template already defines some common namespaces #} + + + Lk01 + {% render_stuurgegevens stuurgegevens referentienummer %} + ZAK + + + W + V + + + {{ zaak_identificatie }} + {% if betalings_indicatie %}{{ betalings_indicatie }}{% endif %} + {% if laatste_betaaldatum %}{{ laatste_betaaldatum }}{% endif %} + + {% include "stuf_zds/soap/includes/extraElementen.xml" %} + +{% endblock %} diff --git a/stuf_zds_payments/tests/__init__.py b/stuf_zds_payments/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/stuf_zds_payments/tests/data/vcr_cassettes/StufZDSPaymentsRegistrationTestCase/StufZDSPaymentsRegistrationTestCase.test_register_submission_with_payment.yaml b/stuf_zds_payments/tests/data/vcr_cassettes/StufZDSPaymentsRegistrationTestCase/StufZDSPaymentsRegistrationTestCase.test_register_submission_with_payment.yaml new file mode 100644 index 0000000..f73cd6d --- /dev/null +++ b/stuf_zds_payments/tests/data/vcr_cassettes/StufZDSPaymentsRegistrationTestCase/StufZDSPaymentsRegistrationTestCase.test_register_submission_with_payment.yaml @@ -0,0 +1,230 @@ +interactions: +- request: + body: "\n\n + \ \n \n\n \n \n + \ \n\n\n + \ \n Lk01\n + \ \n zender_organisatie-0\nzender_applicatie-0\nzender_administratie-0\nzender_gebruiker-0\n\n\n\n + \ ontvanger_organisatie-0\nontvanger_applicatie-0\nontvanger_administratie-0\nontvanger_gebruiker-0\n\n\n58f3d63c-37ab-4b65-844d-fdb9da043b17\n20240805124256\n\n + \ ZAK\n \n + \ \n T\n V\n + \ \n \n + \ abc123\n \n my-form\n + \ \n \n \n \n coordinaat\n + \ \n \n + \ 52.36673378967122 4.893164274470299\n + \ \n \n \n + \ \n 20240805\n 20240805\n + \ N.v.t.\n \n\n + \ 1\n N\n + \ 20240805124256\n + \ \n\nen\n\nBuzzBazz\n\ntrue\n\n40.0\n\nfoo\n\nbar\n\n\n\n\n + \ \n + \ \n + \ \n bar\n + \ \n foo\n 20240805\n + \ \n \n \n \n \n + \ \n \n \n \n + \ 111222333\n J\n \n + \ Bar\n de\n + \ \n Foo\n + \ \n 20001231\n\n \n + \ \n \n \n + \ 20240805124256\n + \ \n \n \n \n \n \n \n + \ baz\n \n \n + \ foo\n \n + \ \n Status + gezet door verzendende applicatie.\n 20240805\n + \ \n \n \n\n \n\n" + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + Content-Length: + - '5155' + Content-Type: + - application/soap+xml + SOAPAction: + - http://www.egem.nl/StUF/sector/zkn/0310/creeerZaak_Lk01 + User-Agent: + - python-requests/2.32.2 + method: POST + uri: http://localhost/stuf-zds + response: + body: + string: "\n\n + \ \n \n \n \n Bv03\n + \ \n KING\n + \ STP\n \n + \ \n \n ORG\n + \ TTA\n \n + \ \n \n 7771605337\n + \ 20210520125824\n + \ abeab17b-03cb-477b-95af-410f07947f40\n + \ \n \n \n" + headers: + Connection: + - close + Content-Length: + - '1349' + Content-Type: + - text/xml + Date: + - Mon, 05 Aug 2024 12:42:56 GMT + Server: + - Werkzeug/3.0.3 Python/3.12.4 + status: + code: 200 + message: OK +- request: + body: "\n\n + \ \n \n\n \n \n + \ \n\n\n + \ \n Di02\n + \ \n zender_organisatie-0\nzender_applicatie-0\nzender_administratie-0\nzender_gebruiker-0\n\n\n\n + \ ontvanger_organisatie-0\nontvanger_applicatie-0\nontvanger_administratie-0\nontvanger_gebruiker-0\n\n\ne4bcac4d-5fab-4a01-a28a-76d168bd7180\n20240805124256\n\n + \ genereerDocumentidentificatie\n \n\n + \ \n\n" + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + Content-Length: + - '1347' + Content-Type: + - application/soap+xml + SOAPAction: + - http://www.egem.nl/StUF/sector/zkn/0310/genereerDocumentIdentificatie_Di02 + User-Agent: + - python-requests/2.32.2 + method: POST + uri: http://localhost/stuf-zds + response: + body: + string: "\n\n + \ \n \n \n + \ \n Du02\n + \ \n KING\n + \ STP\n \n + \ \n \n ORG\n + \ TTA\n \n + \ \n 20210520125731\n + \ genereerDocumentidentificatie\n + \ \n melding\n + \ \n + \ b3c8b5a4d32fb168\n + \ \n \n + \ \n" + headers: + Connection: + - close + Content-Length: + - '1364' + Content-Type: + - text/xml + Date: + - Mon, 05 Aug 2024 12:42:56 GMT + Server: + - Werkzeug/3.0.3 Python/3.12.4 + status: + code: 200 + message: OK +- request: + body: "\n\n + \ \n \n\n \n \n + \ \n\n\n \n + \ Lk01\n \n zender_organisatie-0\nzender_applicatie-0\nzender_administratie-0\nzender_gebruiker-0\n\n\n\n + \ ontvanger_organisatie-0\nontvanger_applicatie-0\nontvanger_administratie-0\nontvanger_gebruiker-0\n\n\n3cac727d-3782-4905-b516-8ecc109a2c6c\n20240805124256\n\n + \ EDC\n \n + \ \n T\n I\n + \ \n \n + \ b3c8b5a4d32fb168\n foo\n + \ 20240805\n 20240805\n + \ inzending\n Ingezonden + formulier\n application/pdf\n + \ nld\n definitief\n + \ 20240805\n GEHEIM\n + \ open-forms\n \n \n + \ 20240805\n \n \n + \ 20240805124256\n + \ \n + \ \n + \ abc123\n my-form\n + \ \n 20240805124256\n + \ \n \n\n \n\n" + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + Content-Length: + - '3029' + Content-Type: + - application/soap+xml + SOAPAction: + - http://www.egem.nl/StUF/sector/zkn/0310/voegZaakdocumentToe_Lk01 + User-Agent: + - python-requests/2.32.2 + method: POST + uri: http://localhost/stuf-zds + response: + body: + string: "\n\n + \ \n \n \n \n Bv03\n + \ \n KING\n + \ STP\n \n + \ \n \n ORG\n + \ TTA\n \n + \ \n \n 2301189518\n + \ 20210520125731\n + \ ea84f068-55e1-43fd-b4a0-94fc18f9b7af\n + \ \n \n \n" + headers: + Connection: + - close + Content-Length: + - '1349' + Content-Type: + - text/xml + Date: + - Mon, 05 Aug 2024 12:42:56 GMT + Server: + - Werkzeug/3.0.3 Python/3.12.4 + status: + code: 200 + message: OK +version: 1 diff --git a/stuf_zds_payments/tests/data/vcr_cassettes/StufZDSPaymentsRegistrationTestCase/StufZDSPaymentsRegistrationTestCase.test_set_zaak_payment.yaml b/stuf_zds_payments/tests/data/vcr_cassettes/StufZDSPaymentsRegistrationTestCase/StufZDSPaymentsRegistrationTestCase.test_set_zaak_payment.yaml new file mode 100644 index 0000000..78276b2 --- /dev/null +++ b/stuf_zds_payments/tests/data/vcr_cassettes/StufZDSPaymentsRegistrationTestCase/StufZDSPaymentsRegistrationTestCase.test_set_zaak_payment.yaml @@ -0,0 +1,67 @@ +interactions: +- request: + body: "\n\n + \ \n \n\n \n \n + \ \n\n\n + \ \n Lk01\n + \ \n zender_organisatie-0\nzender_applicatie-0\nzender_administratie-0\nzender_gebruiker-0\n\n\n\n + \ ontvanger_organisatie-0\nontvanger_applicatie-0\nontvanger_administratie-0\nontvanger_gebruiker-0\n\n\na62fad4a-01c0-46ad-98a7-49592c88b21e\n20240805124256\n\n + \ ZAK\n \n + \ \n W\n V\n + \ \n \n + \ 1234\n Geheel\n + \ 20240805\n\n \n\nen\n\nBuzzBazz\n\ntrue\n\n40.0\n\nfoo\n\nbar\n\n\n\n\n + \ \n\n \n\n" + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + Content-Length: + - '2171' + Content-Type: + - application/soap+xml + SOAPAction: + - http://www.egem.nl/StUF/sector/zkn/0310/updateZaak_Lk01 + User-Agent: + - python-requests/2.32.2 + method: POST + uri: http://localhost/stuf-zds + response: + body: + string: "\n\n + \ \n \n \n \n Bv03\n + \ \n KING\n + \ STP\n \n + \ \n \n ORG\n + \ TTA\n \n + \ \n \n 7771605337\n + \ 20210520125824\n + \ abeab17b-03cb-477b-95af-410f07947f40\n + \ \n \n \n" + headers: + Connection: + - close + Content-Length: + - '1349' + Content-Type: + - text/xml + Date: + - Mon, 05 Aug 2024 12:42:56 GMT + Server: + - Werkzeug/3.0.3 Python/3.12.4 + status: + code: 200 + message: OK +version: 1 diff --git a/stuf_zds_payments/tests/data/vcr_cassettes/StufZDSPaymentsRegistrationTestCase/StufZDSPaymentsRegistrationTestCase.test_set_zaak_payment_incorrect_payment_status_update_mapping.yaml b/stuf_zds_payments/tests/data/vcr_cassettes/StufZDSPaymentsRegistrationTestCase/StufZDSPaymentsRegistrationTestCase.test_set_zaak_payment_incorrect_payment_status_update_mapping.yaml new file mode 100644 index 0000000..9b9aa08 --- /dev/null +++ b/stuf_zds_payments/tests/data/vcr_cassettes/StufZDSPaymentsRegistrationTestCase/StufZDSPaymentsRegistrationTestCase.test_set_zaak_payment_incorrect_payment_status_update_mapping.yaml @@ -0,0 +1,65 @@ +interactions: +- request: + body: "\n\n + \ \n \n\n \n \n + \ \n\n\n + \ \n Lk01\n + \ \n zender_organisatie-0\nzender_applicatie-0\nzender_administratie-0\nzender_gebruiker-0\n\n\n\n + \ ontvanger_organisatie-0\nontvanger_applicatie-0\nontvanger_administratie-0\nontvanger_gebruiker-0\n\n\n59dedae3-715d-429f-ada3-e66c3b3fe3a9\n20240805124256\n\n + \ ZAK\n \n + \ \n W\n V\n + \ \n \n + \ 1234\n Geheel\n + \ 20240805\n\n \n\nen\n\nBuzzBazz\n\n40.0\n\n\n\n\n + \ \n\n \n\n" + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + Content-Length: + - '1944' + Content-Type: + - application/soap+xml + SOAPAction: + - http://www.egem.nl/StUF/sector/zkn/0310/updateZaak_Lk01 + User-Agent: + - python-requests/2.32.2 + method: POST + uri: http://localhost/stuf-zds + response: + body: + string: "\n\n + \ \n \n \n \n Bv03\n + \ \n KING\n + \ STP\n \n + \ \n \n ORG\n + \ TTA\n \n + \ \n \n 7771605337\n + \ 20210520125824\n + \ abeab17b-03cb-477b-95af-410f07947f40\n + \ \n \n \n" + headers: + Connection: + - close + Content-Length: + - '1349' + Content-Type: + - text/xml + Date: + - Mon, 05 Aug 2024 12:42:56 GMT + Server: + - Werkzeug/3.0.3 Python/3.12.4 + status: + code: 200 + message: OK +version: 1 diff --git a/stuf_zds_payments/tests/test_plugin.py b/stuf_zds_payments/tests/test_plugin.py new file mode 100644 index 0000000..104d146 --- /dev/null +++ b/stuf_zds_payments/tests/test_plugin.py @@ -0,0 +1,213 @@ +from decimal import Decimal +from pathlib import Path +from textwrap import dedent + +from django.test import TestCase + +from openforms.payments.constants import PaymentStatus +from openforms.payments.tests.factories import SubmissionPaymentFactory +from openforms.registrations.constants import RegistrationAttribute +from openforms.submissions.tests.factories import SubmissionFactory +from openforms.utils.tests.vcr import OFVCRMixin +from stuf.stuf_zds.models import StufZDSConfig +from stuf.tests.factories import StufServiceFactory + +from stuf_zds_payments.client import ZaakOptions +from stuf_zds_payments.plugin import ( + StufZDSPaymentsRegistration, + default_payment_status_update_mapping, +) + +TESTS_DIR = Path(__file__).parent.resolve() + + +class StufZDSPaymentsRegistrationTestCase(OFVCRMixin, TestCase): + VCR_TEST_FILES = TESTS_DIR / "data" + + @classmethod + def setUpTestData(cls): + super().setUpTestData() + + cls.zds_service = StufServiceFactory.create( + soap_service__url="http://localhost/stuf-zds" + ) + config = StufZDSConfig.get_solo() + config.service = cls.zds_service + config.save() + + cls.options = ZaakOptions( + payment_status_update_mapping=default_payment_status_update_mapping(), + zds_zaaktype_code="foo", + zds_zaaktype_omschrijving="bar", + zds_zaaktype_status_code="baz", + zds_zaaktype_status_omschrijving="foo", + zds_documenttype_omschrijving_inzending="foo", + zds_zaakdoc_vertrouwelijkheid="GEHEIM", + omschrijving="foo", + referentienummer="foo", + ) + cls.plugin = StufZDSPaymentsRegistration("test") + + cls.submission = SubmissionFactory.from_components( + [ + { + "key": "voornaam", + "type": "textfield", + "registration": { + "attribute": RegistrationAttribute.initiator_voornamen, + }, + }, + { + "key": "achternaam", + "type": "textfield", + "registration": { + "attribute": RegistrationAttribute.initiator_geslachtsnaam, + }, + }, + { + "key": "tussenvoegsel", + "type": "textfield", + "registration": { + "attribute": RegistrationAttribute.initiator_tussenvoegsel, + }, + }, + { + "key": "geboortedatum", + "type": "date", + "registration": { + "attribute": RegistrationAttribute.initiator_geboortedatum, + }, + }, + { + "key": "coordinaat", + "registration": { + "attribute": RegistrationAttribute.locatie_coordinaat, + }, + }, + { + "key": "extra", + }, + { + "key": "language_code", + }, + ], + form__name="my-form", + bsn="111222333", + submitted_data={ + "voornaam": "Foo", + "achternaam": "Bar", + "tussenvoegsel": "de", + "geboortedatum": "2000-12-31", + "coordinaat": [52.36673378967122, 4.893164274470299], + "extra": "BuzzBazz", + }, + language_code="en", + public_registration_reference="abc123", + registration_result={"zaak": "1234"}, + ) + # can't pass this as part of `SubmissionFactory.from_components` + cls.submission.price = Decimal("40.00") + cls.submission.save() + SubmissionPaymentFactory.create( + submission=cls.submission, + amount=Decimal("25.00"), + public_order_id="foo", + status=PaymentStatus.completed, + ) + SubmissionPaymentFactory.create( + submission=cls.submission, + amount=Decimal("15.00"), + public_order_id="bar", + status=PaymentStatus.completed, + ) + + def test_set_zaak_payment(self): + self.plugin.update_payment_status(self.submission, self.options) + + request_body = self.cassette.requests[0].body.decode("utf-8") + + expected = dedent( + """\ + + + en + + BuzzBazz + + true + + 40.0 + + foo + + bar + + """ + ) + + self.assertIn(expected, request_body) + + def test_set_zaak_payment_incorrect_payment_status_update_mapping(self): + """ + Non-existent fields in the payment_status_update_mapping should be ignored + """ + options = ZaakOptions( + payment_status_update_mapping=[ + {"form_variable": "payment_amount", "stuf_name": "paymentAmount"}, + {"form_variable": "non-existent-field", "stuf_name": "foo"}, + ], + zds_zaaktype_code="foo", + zds_zaaktype_omschrijving="bar", + zds_zaaktype_status_code="baz", + zds_zaaktype_status_omschrijving="foo", + zds_documenttype_omschrijving_inzending="foo", + zds_zaakdoc_vertrouwelijkheid="GEHEIM", + omschrijving="foo", + referentienummer="foo", + ) + self.plugin.update_payment_status(self.submission, options) + + request_body = self.cassette.requests[0].body.decode("utf-8") + expected = dedent( + """\ + + + en + + BuzzBazz + + 40.0 + + """ + ) + + self.assertIn(expected, request_body) + + def test_register_submission_with_payment(self): + """ + Assert that payment attributes are included when creating the zaak, in case + the registration is deferred until the payment is received + """ + self.plugin.register_submission(self.submission, self.options) + + request_body = self.cassette.requests[0].body.decode("utf-8") + expected = dedent( + """\ + + + en + + BuzzBazz + + true + + 40.0 + + foo + + bar + + """ + ) + + self.assertIn(expected, request_body) diff --git a/tox.ini b/tox.ini index 6d8a125..213b655 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - py{310,311,312}-django{42} + py{312}-django{42} isort black flake8 @@ -9,8 +9,6 @@ skip_missing_interpreters = true [gh-actions] python = - 3.10: py310 - 3.11: py311 3.12: py312 [gh-actions:env]