diff --git a/src/openforms/forms/tests/test_import_export.py b/src/openforms/forms/tests/test_import_export.py index d0cad6ee0b..6cdf1ce38e 100644 --- a/src/openforms/forms/tests/test_import_export.py +++ b/src/openforms/forms/tests/test_import_export.py @@ -304,7 +304,12 @@ def test_import(self): self.assertEqual(imported_form.authentication_backends, ["digid"]) self.assertEqual(imported_form.payment_backend, "ogone-legacy") self.assertEqual( - imported_form.payment_backend_options, {"merchant_id": merchant.id} + imported_form.payment_backend_options, + { + "merchant_id": merchant.id, + "com_template": "", + "title_template": "", + }, ) form_definitions = FormDefinition.objects.order_by("pk") diff --git a/src/openforms/js/compiled-lang/en.json b/src/openforms/js/compiled-lang/en.json index 8ca003173f..ec3df680d7 100644 --- a/src/openforms/js/compiled-lang/en.json +++ b/src/openforms/js/compiled-lang/en.json @@ -2293,6 +2293,60 @@ "value": "The data entered in this component will be removed in accordance with the privacy settings." } ], + "JHQfjZ": [ + { + "type": 0, + "value": "Optional custom template for the the description, included in the payment overviews for the backoffice. Use this to link the payment back to a particular process or form." + }, + { + "children": [ + ], + "type": 8, + "value": "br" + }, + { + "type": 0, + "value": " You can use all form variables (using their keys) and the " + }, + { + "children": [ + { + "type": 0, + "value": "public_reference" + } + ], + "type": 8, + "value": "code" + }, + { + "type": 0, + "value": " template variable. If unspecified, a default description is used." + }, + { + "children": [ + ], + "type": 8, + "value": "br" + }, + { + "type": 0, + "value": " " + }, + { + "children": [ + { + "type": 0, + "value": "Note" + } + ], + "type": 8, + "value": "strong" + }, + { + "type": 0, + "value": ": the length of the result is capped to 100 characters." + } + ], "JKq3TC": [ { "type": 0, @@ -3887,6 +3941,12 @@ "value": "The processing (\"verwerking\") for queries to the BRP Persoon API." } ], + "ZSvQcR": [ + { + "type": 0, + "value": "TITLE parameter" + } + ], "ZdZ2Mb": [ { "type": 0, @@ -4445,6 +4505,12 @@ "value": "'Save row' text" } ], + "eZJFP7": [ + { + "type": 0, + "value": "COM parameter" + } + ], "ee4oWr": [ { "type": 0, @@ -6243,6 +6309,36 @@ "value": "The content of the submission confirmation page. It can contain variables that will be templated from the submitted form data. If not specified, the global template will be used." } ], + "vikURf": [ + { + "type": 0, + "value": "Optional custom template for the title displayed on the payment page." + }, + { + "children": [ + ], + "type": 8, + "value": "br" + }, + { + "type": 0, + "value": " You can use all form variables (using their keys) and the " + }, + { + "children": [ + { + "type": 0, + "value": "public_reference" + } + ], + "type": 8, + "value": "code" + }, + { + "type": 0, + "value": " template variable. If unspecified, a default description is used." + } + ], "vlY36U": [ { "type": 0, diff --git a/src/openforms/js/compiled-lang/nl.json b/src/openforms/js/compiled-lang/nl.json index d452780cad..e28e7035e1 100644 --- a/src/openforms/js/compiled-lang/nl.json +++ b/src/openforms/js/compiled-lang/nl.json @@ -2314,6 +2314,60 @@ "value": "Gegevens opgevoerd in dit component worden geschoond volgens de privacy-instellingen." } ], + "JHQfjZ": [ + { + "type": 0, + "value": "Optional custom template for the the description, included in the payment overviews for the backoffice. Use this to link the payment back to a particular process or form." + }, + { + "children": [ + ], + "type": 8, + "value": "br" + }, + { + "type": 0, + "value": " You can use all form variables (using their keys) and the " + }, + { + "children": [ + { + "type": 0, + "value": "public_reference" + } + ], + "type": 8, + "value": "code" + }, + { + "type": 0, + "value": " template variable. If unspecified, a default description is used." + }, + { + "children": [ + ], + "type": 8, + "value": "br" + }, + { + "type": 0, + "value": " " + }, + { + "children": [ + { + "type": 0, + "value": "Note" + } + ], + "type": 8, + "value": "strong" + }, + { + "type": 0, + "value": ": the length of the result is capped to 100 characters." + } + ], "JKq3TC": [ { "type": 0, @@ -3905,6 +3959,12 @@ "value": "De waarde voor \"verwerking\" die meegestuurd wordt bij het bevragen van de BRP Personen API. Mogelijke waarden hiervoor zijn afhankelijk van je gateway-leverancier." } ], + "ZSvQcR": [ + { + "type": 0, + "value": "TITLE parameter" + } + ], "ZdZ2Mb": [ { "type": 0, @@ -4467,6 +4527,12 @@ "value": "'Groep bewaren'-tekst" } ], + "eZJFP7": [ + { + "type": 0, + "value": "COM parameter" + } + ], "ee4oWr": [ { "type": 0, @@ -6265,6 +6331,36 @@ "value": "De inhoud van bevestigingspagina na het versturen van de inzending. Dit sjabloon mag variabelen bevatten met inzendings-gegevens. Laat dit veld leeg om de standaardinstelling te gebruiken." } ], + "vikURf": [ + { + "type": 0, + "value": "Optional custom template for the title displayed on the payment page." + }, + { + "children": [ + ], + "type": 8, + "value": "br" + }, + { + "type": 0, + "value": " You can use all form variables (using their keys) and the " + }, + { + "children": [ + { + "type": 0, + "value": "public_reference" + } + ], + "type": 8, + "value": "code" + }, + { + "type": 0, + "value": " template variable. If unspecified, a default description is used." + } + ], "vlY36U": [ { "type": 0, diff --git a/src/openforms/js/components/admin/form_design/payments/ogone_legacy/OgoneLegacyOptionsForm.js b/src/openforms/js/components/admin/form_design/payments/ogone_legacy/OgoneLegacyOptionsForm.js index 7bc97308d0..74e83f3859 100644 --- a/src/openforms/js/components/admin/form_design/payments/ogone_legacy/OgoneLegacyOptionsForm.js +++ b/src/openforms/js/components/admin/form_design/payments/ogone_legacy/OgoneLegacyOptionsForm.js @@ -11,7 +11,7 @@ import { import {getChoicesFromSchema} from 'utils/json-schema'; import OptionsConfiguration from '../OptionsConfiguration'; -import {MerchantID} from './fields'; +import {ComTemplate, MerchantID, TitleTemplate} from './fields'; const OgoneLegacyOptionsForm = ({schema, formData, onSubmit}) => { const validationErrors = useContext(ValidationErrorContext); @@ -41,6 +41,8 @@ const OgoneLegacyOptionsForm = ({schema, formData, onSubmit}) => {
+ +
diff --git a/src/openforms/js/components/admin/form_design/payments/ogone_legacy/fields.js b/src/openforms/js/components/admin/form_design/payments/ogone_legacy/fields.js index 785817854b..492f3ae24f 100644 --- a/src/openforms/js/components/admin/form_design/payments/ogone_legacy/fields.js +++ b/src/openforms/js/components/admin/form_design/payments/ogone_legacy/fields.js @@ -1,8 +1,10 @@ +import {useField} from 'formik'; import PropTypes from 'prop-types'; import {FormattedMessage} from 'react-intl'; import Field from 'components/admin/forms/Field'; import FormRow from 'components/admin/forms/FormRow'; +import {TextInput} from 'components/admin/forms/Inputs'; import ReactSelect from 'components/admin/forms/ReactSelect'; export const MerchantID = ({options}) => ( @@ -21,6 +23,7 @@ export const MerchantID = ({options}) => ( defaultMessage="Which merchant should be used for payments related to this form." /> } + required > @@ -35,3 +38,73 @@ MerchantID.propTypes = { }) ).isRequired, }; + +export const TitleTemplate = () => { + const [fieldProps] = useField('titleTemplate'); + return ( + + + } + helpText={ +
+ You can use all form variables (using their keys) and the public_reference + template variable. If unspecified, a default description is used.`} + values={{ + br: () =>
, + code: chunks => {chunks}, + }} + /> + } + > + +
+
+ ); +}; + +TitleTemplate.propTypes = {}; + +export const ComTemplate = () => { + const [fieldProps] = useField('comTemplate'); + return ( + + + } + helpText={ +
+ You can use all form variables (using their keys) and the public_reference + template variable. If unspecified, a default description is used.

+ Note: the length of the result is capped to 100 characters.`} + values={{ + br: () =>
, + code: chunks => {chunks}, + strong: chunks => {chunks}, + }} + /> + } + > + +
+
+ ); +}; + +ComTemplate.propTypes = {}; diff --git a/src/openforms/js/lang/en.json b/src/openforms/js/lang/en.json index cdfe481ba4..b814ef1c8e 100644 --- a/src/openforms/js/lang/en.json +++ b/src/openforms/js/lang/en.json @@ -1079,6 +1079,11 @@ "description": "Modal title API call failed with HTTP 401", "originalDefault": "Authentication failure" }, + "JHQfjZ": { + "defaultMessage": "Optional custom template for the the description, included in the payment overviews for the backoffice. Use this to link the payment back to a particular process or form.

You can use all form variables (using their keys) and the public_reference template variable. If unspecified, a default description is used.

Note: the length of the result is capped to 100 characters.", + "description": "Ogone legacy payment options 'comTemplate' help text", + "originalDefault": "Optional custom template for the the description, included in the payment overviews for the backoffice. Use this to link the payment back to a particular process or form.

You can use all form variables (using their keys) and the public_reference template variable. If unspecified, a default description is used.

Note: the length of the result is capped to 100 characters." + }, "JKq3TC": { "defaultMessage": "Medewerker roltype", "description": "Objects API registration options 'medewerkerRoltype' label", @@ -1839,6 +1844,11 @@ "description": "Form 'BRP Personen processing header value' field help text", "originalDefault": "The processing (\"verwerking\") for queries to the BRP Persoon API." }, + "ZSvQcR": { + "defaultMessage": "TITLE parameter", + "description": "Ogone legacy payment options 'titleTemplate' label", + "originalDefault": "TITLE parameter" + }, "ZdZ2Mb": { "defaultMessage": "Set the registration backend to use for the submission", "description": "action type \"set-registration-backend\" label", @@ -2084,6 +2094,11 @@ "description": "StUF-ZDS registration options 'zdsZaaktypeStatusCode' label", "originalDefault": "Zds zaaktype status code" }, + "eZJFP7": { + "defaultMessage": "COM parameter", + "description": "Ogone legacy payment options 'comTemplate' label", + "originalDefault": "COM parameter" + }, "efsF8+": { "defaultMessage": "{label} {isPublished, select, false {(not published)} other {}}", "description": "Document type option label", @@ -2919,6 +2934,11 @@ "description": "Confirmation template help text", "originalDefault": "The content of the submission confirmation page. It can contain variables that will be templated from the submitted form data. If not specified, the global template will be used." }, + "vikURf": { + "defaultMessage": "Optional custom template for the title displayed on the payment page.

You can use all form variables (using their keys) and the public_reference template variable. If unspecified, a default description is used.", + "description": "Ogone legacy payment options 'titleTemplate' help text", + "originalDefault": "Optional custom template for the title displayed on the payment page.

You can use all form variables (using their keys) and the public_reference template variable. If unspecified, a default description is used." + }, "vng/5K": { "defaultMessage": "Set a key name before you can configure this variable", "description": "JSON editor: object entry has empty key name", diff --git a/src/openforms/js/lang/nl.json b/src/openforms/js/lang/nl.json index 3e3c7a6a61..5946b8db68 100644 --- a/src/openforms/js/lang/nl.json +++ b/src/openforms/js/lang/nl.json @@ -1088,6 +1088,11 @@ "description": "Modal title API call failed with HTTP 401", "originalDefault": "Authentication failure" }, + "JHQfjZ": { + "defaultMessage": "Optional custom template for the the description, included in the payment overviews for the backoffice. Use this to link the payment back to a particular process or form.

You can use all form variables (using their keys) and the public_reference template variable. If unspecified, a default description is used.

Note: the length of the result is capped to 100 characters.", + "description": "Ogone legacy payment options 'comTemplate' help text", + "originalDefault": "Optional custom template for the the description, included in the payment overviews for the backoffice. Use this to link the payment back to a particular process or form.

You can use all form variables (using their keys) and the public_reference template variable. If unspecified, a default description is used.

Note: the length of the result is capped to 100 characters." + }, "JKq3TC": { "defaultMessage": "Medewerkerroltype", "description": "Objects API registration options 'medewerkerRoltype' label", @@ -1857,6 +1862,11 @@ "description": "Form 'BRP Personen processing header value' field help text", "originalDefault": "The processing (\"verwerking\") for queries to the BRP Persoon API." }, + "ZSvQcR": { + "defaultMessage": "TITLE parameter", + "description": "Ogone legacy payment options 'titleTemplate' label", + "originalDefault": "TITLE parameter" + }, "ZdZ2Mb": { "defaultMessage": "Zet registratieplugin voor de inzending", "description": "action type \"set-registration-backend\" label", @@ -2104,6 +2114,11 @@ "description": "StUF-ZDS registration options 'zdsZaaktypeStatusCode' label", "originalDefault": "Zds zaaktype status code" }, + "eZJFP7": { + "defaultMessage": "COM parameter", + "description": "Ogone legacy payment options 'comTemplate' label", + "originalDefault": "COM parameter" + }, "efsF8+": { "defaultMessage": "{label} {isPublished, select, false {(concept)} other {}}", "description": "Document type option label", @@ -2940,6 +2955,11 @@ "description": "Confirmation template help text", "originalDefault": "The content of the submission confirmation page. It can contain variables that will be templated from the submitted form data. If not specified, the global template will be used." }, + "vikURf": { + "defaultMessage": "Optional custom template for the title displayed on the payment page.

You can use all form variables (using their keys) and the public_reference template variable. If unspecified, a default description is used.", + "description": "Ogone legacy payment options 'titleTemplate' help text", + "originalDefault": "Optional custom template for the title displayed on the payment page.

You can use all form variables (using their keys) and the public_reference template variable. If unspecified, a default description is used." + }, "vng/5K": { "defaultMessage": "Er moet een sleutelnaam ingesteld worden voordat deze variabele geconfigureerd kan worden", "description": "JSON editor: object entry has empty key name", diff --git a/src/openforms/payments/contrib/ogone/client.py b/src/openforms/payments/contrib/ogone/client.py index 4591932fde..27f86e536d 100644 --- a/src/openforms/payments/contrib/ogone/client.py +++ b/src/openforms/payments/contrib/ogone/client.py @@ -27,7 +27,8 @@ def get_payment_info( amount_cents: int, return_url: str, return_action_param: str, - description: str = "", + title: str = "", + com: str = "", **extra_params ) -> PaymentInfo: # base params @@ -37,8 +38,8 @@ def get_payment_info( LANGUAGE="nl_NL", ORDERID=order_id, PSPID=self.merchant.pspid, - TITLE=description, # doesnt work?? - COM=description, + TITLE=title, # unsure if this works - doesn't seem to be displayed in simulator? + COM=com, ) # add action variations to base return url url = furl(return_url) diff --git a/src/openforms/payments/contrib/ogone/plugin.py b/src/openforms/payments/contrib/ogone/plugin.py index ffba9e267d..fe6394407d 100644 --- a/src/openforms/payments/contrib/ogone/plugin.py +++ b/src/openforms/payments/contrib/ogone/plugin.py @@ -15,7 +15,10 @@ from openforms.frontend import get_frontend_redirect_url from openforms.logging import logevent from openforms.submissions.tokens import submission_status_token_generator +from openforms.template import render_from_string, sandbox_backend +from openforms.template.validators import DjangoTemplateValidator from openforms.utils.mixins import JsonSchemaSerializerMixin +from openforms.variables.service import get_variables_for_context from ...base import BasePlugin from ...constants import PAYMENT_STATUS_FINAL, UserAction @@ -36,6 +39,38 @@ class OgoneOptionsSerializer(JsonSchemaSerializerMixin, serializers.Serializer): required=True, help_text=_("Merchant to use"), ) + title_template = serializers.CharField( + required=False, + allow_blank=True, + default="", + label=_("TITLE template"), + validators=[ + DjangoTemplateValidator(backend="openforms.template.sandbox_backend") + ], + help_text=_( + "Optional custom template for the title displayed on the payment page. " + "You can use all form variables (using their keys) and the " + "`public_reference` template variable. If unspecified, a default " + "description is used." + ), + ) + com_template = serializers.CharField( + required=False, + allow_blank=True, + default="", + label=_("COM template"), + validators=[ + DjangoTemplateValidator(backend="openforms.template.sandbox_backend") + ], + help_text=_( + "Optional custom template for the description, included in the payment " + "overviews for the backoffice. Use this to link the payment back to a " + "particular process or form. You can use all form variables (using their " + "keys) and the `public_reference` template variable. If unspecified, a " + "default description is used. Note that the length of the result is capped " + "to 100 characters and only alpha-numeric characters are allowed." + ), + ) RETURN_ACTION_PARAM = "action" @@ -57,8 +92,24 @@ def start_payment( return_url = self.get_return_url(request, payment) - description = ( - f"{_('Submission')}: {payment.submission.public_registration_reference}" + public_reference = payment.submission.public_registration_reference + default_description = f"{_('Submission')}: {public_reference}" + + # evaluate custom templates, if specified + template_context = get_variables_for_context(payment.submission) + template_context["public_reference"] = public_reference + + title_value = ( + render_from_string( + title_template, template_context, backend=sandbox_backend + ) + if (title_template := options["title_template"]) + else default_description + ) + com_value = ( + render_from_string(com_template, template_context, backend=sandbox_backend) + if (com_template := options["com_template"]) + else default_description ) info = client.get_payment_info( @@ -66,7 +117,8 @@ def start_payment( amount_cents, return_url, RETURN_ACTION_PARAM, - description, + title=title_value, + com=com_value[:100], ) return info diff --git a/src/openforms/payments/contrib/ogone/tests/files/vcr_cassettes/OgoneClientTest/OgoneClientTest.test_com_title_unicode_chars.yaml b/src/openforms/payments/contrib/ogone/tests/files/vcr_cassettes/OgoneClientTest/OgoneClientTest.test_com_title_unicode_chars.yaml new file mode 100644 index 0000000000..7096f950cd --- /dev/null +++ b/src/openforms/payments/contrib/ogone/tests/files/vcr_cassettes/OgoneClientTest/OgoneClientTest.test_com_title_unicode_chars.yaml @@ -0,0 +1,198 @@ +interactions: +- request: + body: PSPID=maykinmedia&ORDERID=xyz2024%2FOF-123456%2F987654321&AMOUNT=1000&CURRENCY=EUR&LANGUAGE=nl_NL&TITLE=l%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4mp%21+l%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4mp%21+l%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4mp%21+l%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4mp%21+l%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4mp%21+l%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4mp%21+l%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4mp%21+l%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4mp%21+l%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4mp%21+l%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4%C3%A4mp%21&PMLISTTYPE=2&ACCEPTURL=http%3A%2F%2Ffoo.bar%2Freturn%3Fbazz%3Dbuzz%26action%3Daccept&DECLINEURL=http%3A%2F%2Ffoo.bar%2Freturn%3Fbazz%3Dbuzz%26action%3Dcancel&EXCEPTIONURL=http%3A%2F%2Ffoo.bar%2Freturn%3Fbazz%3Dbuzz%26action%3Dexception&CANCELURL=http%3A%2F%2Ffoo.bar%2Freturn%3Fbazz%3Dbuzz%26action%3Dcancel&BACKURL=http%3A%2F%2Ffoo.bar%2Freturn%3Fbazz%3Dbuzz%26action%3Dcancel&COM=br%C3%B8ther+i+desire+l%C3%B6%C3%B6ps&SHASIGN=EF6D7725AC3A447821DB9E5997FDC2A9B8F4A7403E091A45A25146E98950B6BF433EDEC00D1B7B542AF82C3912B64598F5A44493465C3C31070FE4DA4B72809B + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + Content-Length: + - '1570' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - python-requests/2.32.2 + method: POST + uri: https://ogone.test.v-psp.com/ncol/test/orderstandard_utf8.asp + response: + body: + string: "\r\n\r\n\r\n \r\n \r\n + \ \r\n + \ \r\n + \ Payment confirmation\r\n \r\n + \ \r\n\r\n\r\n
\r\n
\r\n \r\n
\r\n
\r\n\r\n\r\n\t\r\n\t

Overzicht van de bestelling

\r\n\t\r\n\t\t\t\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\r\n\r\n\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t
Referentie van de bestelling + :xyz2024/OF-123456/987654321
\r\n\t\t\t\t\t\t\tTotale + kostprijs :\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t10.00 + EUR\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t
Begunstigde :Maykin Media B.V.
\r\n\t\r\n\t\t\r\n\t\t\r\n\t\r\n\t\t\r\n\t\t\r\n\t\t\r\n\t\r\n\t\t\r\n\t\r\n\r\n\t\r\n\t\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\t\t\t
\r\n\t\t\t\t\t\r\n\r\n\r\n\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\r\n + \ \r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\r\n\r\n\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t
\r\n\t\t\t\t\t\t\tKies uw bank en klik op + "Ga verder" om bij uw bank met iDEAL te betalen.\r\n\t\t\t\t\t\t\t
\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\"iDEAL\"\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t
\r\n\t\t\t\t
\r\n\t\t\t\r\n\t\r\n\t\r\n\t\r\n\r\n

Bijkomende informatie / Annuleren

\r\n\r\n\t\r\n\t\t\r\n\t\t\t\t\r\n\r\n\t\t\r\n\t\t\r\n\r\n
\"ING\"\"Betaling
Over Worldline |Veiligheid| Wettelijke informatie
 
\r\n\t\t\t\t\t\t
\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t
\r\n\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t
\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\r\n\r\n\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t
\r\n\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t
\r\n\t\t\t\t\t\t
\r\n\t\t\t\t
\r\n
\r\n
\r\n\r\n" + headers: + cache-control: + - private, max-age=0 + content-length: + - '14059' + content-type: + - text/html; Charset=utf-8 + date: + - Mon, 16 Dec 2024 14:42:34 GMT + expires: + - Mon, 16 Dec 2024 14:41:34 GMT + set-cookie: + - sessionTest=a866c2ae-aa5f-4421-8eeb-ccf240ddff73; path=/ncol/test/; Secure; + HttpOnly + strict-transport-security: + - max-age=16000000; includeSubDomains; preload; + x-content-type-options: + - nosniff + status: + code: 200 + message: OK +version: 1 diff --git a/src/openforms/payments/contrib/ogone/tests/test_client.py b/src/openforms/payments/contrib/ogone/tests/test_client.py index d8a0db552d..3505592785 100644 --- a/src/openforms/payments/contrib/ogone/tests/test_client.py +++ b/src/openforms/payments/contrib/ogone/tests/test_client.py @@ -53,6 +53,23 @@ def test_payment_request_invalid_order_id(self): self.assertIn("Er is een fout opgetreden", payment_request.text) + def test_com_title_unicode_chars(self): + client = OgoneClient(self.merchant) + + info = client.get_payment_info( + "xyz2024/OF-123456/987654321", + 1000, + "http://foo.bar/return?bazz=buzz", + RETURN_ACTION_PARAM, + title="läääääääääääääämp! " * 10, # 190 chars + com="brøther i desire lööps", + ) + + payment_request = requests.post(info.url, data=info.data) + + # Response is always a 200, so we assert on the content instead + self.assertNotIn("Er is een fout opgetreden", payment_request.text) + class OgoneGetPaymentInfoTest(TestCase): maxDiff = None diff --git a/src/openforms/payments/contrib/ogone/tests/test_plugin.py b/src/openforms/payments/contrib/ogone/tests/test_plugin.py index 2ffb503e66..45ee3247c3 100644 --- a/src/openforms/payments/contrib/ogone/tests/test_plugin.py +++ b/src/openforms/payments/contrib/ogone/tests/test_plugin.py @@ -11,10 +11,13 @@ from ....registry import register from ....tests.factories import SubmissionPaymentFactory from ..constants import OgoneStatus, PaymentStatus -from ..plugin import RETURN_ACTION_PARAM +from ..plugin import RETURN_ACTION_PARAM, OgoneLegacyPaymentPlugin from ..signing import calculate_sha_out +from ..typing import PaymentOptions from .factories import OgoneMerchantFactory +factory = RequestFactory() + @override_settings( CORS_ALLOW_ALL_ORIGINS=False, @@ -43,7 +46,6 @@ def test_payment(self): plugin = register["ogone-legacy"] # we need an arbitrary request - factory = RequestFactory() request = factory.get("/foo") # start url @@ -147,7 +149,6 @@ def test_webhook(self): plugin = register["ogone-legacy"] # we need an arbitrary request - factory = RequestFactory() request = factory.get("/foo") # start url @@ -203,3 +204,47 @@ def test_apply_status(self): plugin.apply_status(payment, OgoneStatus.payment_requested, "12345") # still registered self.assertEqual(payment.status, PaymentStatus.registered) + + def test_custom_com_and_title_attributes(self): + merchant = OgoneMerchantFactory.create() + submission = SubmissionFactory.from_components( + components_list=[ + { + "type": "textfield", + "key": "inputField", + "label": "Some text input", + } + ], + submitted_data={"inputField": "bröther gib lämp"}, + with_public_registration_reference=True, + public_registration_reference="OF-1234", + form__payment_backend="ogone-legacy", + form__product__price=Decimal("11.35"), + ) + payment = SubmissionPaymentFactory.for_submission(submission) + assert submission.payment_required + assert not submission.payment_user_has_paid + plugin = OgoneLegacyPaymentPlugin("ogone-legacy") + options: PaymentOptions = { + "merchant_id": merchant, + # No length limit applies to the title + "title_template": r"Input: {{ inputField }} - ref: {{ public_reference }}", + # result must be capped at 100 chars, see + # https://support.legacy.worldline-solutions.com/en/help/parameter-cookbook + "com_template": r"Input: {{ inputField }} - ref: {{ public_reference }} " + + "A" * 90, + } + # we need an arbitrary request + request = factory.get("/foo") + + payment_info = plugin.start_payment(request, payment, options) + + assert payment_info.data is not None + self.assertEqual( + payment_info.data["TITLE"], "Input: bröther gib lämp - ref: OF-1234" + ) + self.assertEqual( + payment_info.data["COM"], + "Input: bröther gib lämp - ref: OF-1234 " + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + ) diff --git a/src/openforms/payments/contrib/ogone/typing.py b/src/openforms/payments/contrib/ogone/typing.py index c8944d68dd..1334426494 100644 --- a/src/openforms/payments/contrib/ogone/typing.py +++ b/src/openforms/payments/contrib/ogone/typing.py @@ -5,3 +5,5 @@ class PaymentOptions(TypedDict): merchant_id: OgoneMerchant # FIXME: key is badly named in the serializer + title_template: str + com_template: str diff --git a/src/openforms/variables/service.py b/src/openforms/variables/service.py index 6a1877f718..1bef547517 100644 --- a/src/openforms/variables/service.py +++ b/src/openforms/variables/service.py @@ -8,8 +8,9 @@ from .base import BaseStaticVariable from .registry import register_static_variable as static_variables_registry +from .utils import get_variables_for_context -__all__ = ["get_static_variables"] +__all__ = ["get_static_variables", "get_variables_for_context"] type VariablesRegistry = BaseRegistry[BaseStaticVariable]