Contribution pages with extra URL parameters lead to incorrect AJAX URL constructed in buildPaymentBlock – users cannot proceed to pay
Hi,
I encountered an error while building an Engish version of a contribution page on a system that mostly operates in German. While entering translated texts for all elements, I was told that some elements (button captions, the “total” text) cannot be configured in the contribution page or the profiles and price sets it includes (for all of which I built translated copies). Instead, they are supplied by core itself so we need to signal to core that they should be produced in English, too.
By default, the language for these elements seems to be determined by the browser language, but I was interested in overriding this and was advised (by @Detlev) that I could add a (Drupal-specific) GET parameter language=en
to the URL for that purpose. This does work – up to the point where users want to choose their payment options.
My contribution page (on a Drupal 9 system) has a choice between the PayPal and SEPA processors. When users click the respective radio button, an AJAX request is issued to a URL like
<host>/civicrm/payment/form?formName=Main&is_back_office=&id=14&pre_profile_id=78&processor_id=&language=en3&payment_instrument_id=undefined¤cy=EUR&snippet=json
Notice that the value for language
has been extended from en
to en3
and the URL parameter for processor_id is empty. This leads to a popup with a complaint about a network error, and users cannot proceed. Inspecting the communication reveals that the AJAX request returns a 500 status, and the Civi log contains information about the empty value for processor_id
being the problem.
It turns out that the page has some inline JavaScript code that tries to build the processor ID into the AJAX URL in the buildPaymentBlock
function. It does this by concatenating the parameter type
to a partial URL that itself is produced using the crmURL smarty tag. On my payment page, the offending line was this:
var dataUrl = "/civicrm/payment/form?formName=Main&is_back_office=&id=14&pre_profile_id=78&processor_id=&language=en" + type;
This is rendered by the following line in templates/CRM/common/paymentBlock.tpl
:
var dataUrl = "{crmURL p='civicrm/payment/form' h=0 q="formName=`$form.formName``$urlPathVar``$isBackOfficePathVar``$profilePathVar``$contributionPageID``$preProfileID`processor_id="}" + type;
It is obviously assumed that the string produced by crmURL ends with processor_id=
. However, if extra parameters such as language=en
are present, these end up at the end of the string produced by crmURL, although the parameters in the call to the Smarty tag do not indicate this. Some further lines in the code add other parameters, leading to the full list of parameters given above. But this line is where the problem is created. The line was executed with type
set to 3 (the ID of the SEPA processor in my setup) and this is how the language
value became en3
and processor_id
remained empty.
I am going to submit a pull request with a solution with this issue. Since we apparently cannot know where type
should be inserted, the solution had to involve some kind of templating instead of string concatenation. I would have preferred some templating function provided by one of the JavaScript libraries that are already included on the page. The only candidate library I found was Lodash which provides CRM._.template
. The syntax to use would have been <%= type %>
, but that gets URL-escaped by the Smarty tag. Correcting this would become messy, so I resorted to a hand-crafted call to String.replace using __type__
as the placeholder, in the hope that there is no real risk of this string appearing somewhere else in dataURL
by accident. Edit: I have made the replacement more specific in a second commit, so the risk of it applying in the wrong place should now be vanishingly small.
I hope that this is an acceptable solution. The problem seems generic enough to affect other people too. I encountered and patched this on an old system (5.58.1, there are issues that prevent us from immediately updating), but the line in templates/CRM/common/paymentBlock.tpl
is unchanged in the current master branch and I assume that the way the crmURL Smarty tag works hasn’t changed either.