0

I am meeting a problem in a community site and cannot find any solution on my own... We are connecting the community with an external payment system (PayBox) who works like this :

  • We redirect the user to the paybox website with some information (payment amount, unique order id ...)
  • When the user has finished his payment, he is redirected by paybox to our community site with some information (unique order id, success or not...). This works OK but does not suffice to validate the order in salesforce, the redirection could have been tampered with by the user.
  • To be sure the payment was good, Paybox send another request to another url in the community site (a server to server http request - so no javascript) with the same information (unique order id, success or not ...) and some sort of token to be sure this comes from paybox.

I need to memorize the order is valid from this server to server request. I constructed a visualforce page to get the request but i'm blocked by the 'No dml in visualforce constructor' rule.

Does someone have any idea how could i resolve this issue ? Thank you

  • 2
    Possible duplicate of Why is DML not allowed in Constructor? – Raul Nov 22 '17 at 15:30
  • @RahulSharma No, I understand the why, executing dml from page call would not be secure in a standard context. But this is not relevant in my context. – Y. BETOU Nov 22 '17 at 15:35
  • Also i cannot use the solution <apex:page controller="pageController" action="{!doSomeDMLStuff}"> because it uses javascript. – Y. BETOU Nov 22 '17 at 15:36
  • 2
    Just got done posting my answer. What javascript are you talking about? It would help if you edited your question to include your visualforce page. – Derek F Nov 22 '17 at 15:41
4

I imagine that you're currently performing the validation in the constructor of your Controller/Extension, and are trying to update a checkbox (or something similar) that tells you whether or not the payment has been validated.

There's no way to perform DML in a Controller/Extension constructor, or in any methods where the call stack involves the constructor.

The answer, then, is to move your DML (at the very least) to a different location. The first idea that comes to my mind would be to make use of the action attribute of the <apex:page> tag.

I don't believe that the exact behavior of the visualforce rendering process is documented anywhere, but the method specified by the action attribute is executed after the constructors for the Controller and all Extensions (if any) are run (but before the page itself is rendered).

Salesforce discourages performing DML in a method that is called via the action attribute, but doing so is allowed. If you're worried about doing that, you could abuse a getter method (let's call it getDoVerify()) by using visualforce expression syntax to call the method somewhere in your page with {!doVerify}.

| improve this answer | |
  • Hi, This works, thank you ! I was sure that the action was launched from javascript at page load... I was wrong :) – Y. BETOU Nov 22 '17 at 16:12
-1

In order to perform DML on page load, you must migrate that method to a static void @RemoteAction method, and use JavaScript Remoting to call it on the page.

Also, use script at the bottom of the page as to make increase the user perceived performance.

global with sharing class AccountRemoter {

public String accountName { get; set; }
public static Account account { get; set; }
public AccountRemoter() { } // empty constructor

@RemoteAction
global static Account getAccount(String accountName) {
    account = [SELECT Id, Name, Phone, Type, NumberOfEmployees 
               FROM Account WHERE Name = :accountName];
    return account;
    }
}

Sample Page

<apex:page controller="AccountRemoter">


<input id="acctSearch" type="text"/>
<button onclick="getRemoteAccount()">Get Account</button>
<div id="responseErrors"></div>

<apex:pageBlock id="block">
    <apex:pageBlockSection id="blockSection" columns="2">
        <apex:pageBlockSectionItem id="firstItem">
            <span id="remoteAcctId"/>
        </apex:pageBlockSectionItem>
        <apex:pageBlockSectionItem id="secondItem">
            <apex:outputText id="acctNumEmployees"/>
        </apex:pageBlockSectionItem>
    </apex:pageBlockSection>
</apex:pageBlock>

<script type="text/javascript"> function getRemoteAccount() { var accountName = document.getElementById('acctSearch').value;

    Visualforce.remoting.Manager.invokeAction(
        '{!$RemoteAction.AccountRemoter.getAccount}',
        accountName, 
        function(result, event){
            if (event.status) {
                // Get DOM IDs for HTML and Visualforce elements like this
                document.getElementById('remoteAcctId').innerHTML = result.Id
                document.getElementById(
                    "{!$Component.block.blockSection.secondItem.acctNumEmployees}"
                    ).innerHTML = result.NumberOfEmployees;
            } else if (event.type === 'exception') {
                document.getElementById("responseErrors").innerHTML = 
                    event.message;
            } else {
                document.getElementById("responseErrors").innerHTML = event.message;
            }
        }, 
        {escape: true}
    );
}

</script>

JavaScript Remoting Documentation

| improve this answer | |
  • I cannot use javascript in this context. – Y. BETOU Nov 22 '17 at 16:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.