Link to home
Start Free TrialLog in
Avatar of Netty Admin
Netty AdminFlag for United States of America

asked on

PHP - Wordpress - Need help creating a "Submit Confirmation" for after info is inputted/submitted into an echoed iFrame that disappears when complete

We have a WordPress site and our third party credit card processor gives us a unique custom URL for each customer enrollment/checkout. The URL is populated into a custom field from the backend. We now have a working PHP script that pulls that URL and echos it to an iFrame in our desired location on the Woocommerce checkout page. We currently have zero control as to the programming/code within that iFrame. Once the info has been submitted in the iFrame it disappears leaving that area now blank to the user. I am trying to figure out a way to have that same area display a message stating "Credit Card Saved Successfully" after the iFrame disappears.

I have thought of two possible methods. This may not be the best but maybe there is a way to have the text somehow underneath the iFrame as if It was another layer and once the iFrame disappears the users sees the message. The other way I thought of would be for the PHP to check to see when the value of the iFrame becomes null then it Prints/Echos the message to the user with an if/else statement.
 
I am not a developer. I am a network admin but we only have a small internal team of two other guys working on this project and we have multiple sites to do so I was tasked with getting this done as my coworkers have missed the deadline on this project a few times already and the site is supposed to be live and in production.


Current PHP Script running as a snippet hooked in through WordPress WooCommerce:

add_action('woocommerce_review_order_before_submit', 'show_cc_iframe');

function show_cc_iframe() {
   
    $current_user = get_current_user_id();
    $url = get_user_meta($current_user, 'order_url_string', true);
    print "<h4>Credit Card info:</h4>
    <p>A credit card is required to complete your order but is not being charged at this time. Your credit card will be charged when service is completed. We accept Visa, MasterCard,                                       Discover and American Express cards.</p>";
    echo '<iframe src="' . urldecode($url) . '"></iframe>';
      
}
Avatar of Netty Admin
Netty Admin
Flag of United States of America image

ASKER

User generated image^^^This is what is displayed to the user^^^





User generated image

^^^^^This is the source when the iFrame is actively presenting the URL^^^^^





User generated image

^^^^This is after the info has been filled in correctly and "Add Credit Card" button has been clicked and the iframe contents disappear^^^
User generated image^^Screen Shot of the active PHP snippet for WordPress Woocommerce^^
Avatar of Chris Stanyon
Hi Netty,

If you look at the source of the iFrame after you've submitted the details, you'll see a lot of code in the <body> tag:

<body onload="parent.postMessage(...)"

Open in new window

What this is actually doing is sending the data from the iFrame response back up to your own site. It's doing it by calling a Javascript function called postMessage. You can use that to retrieve the info and handle it accordingly (for example: save it to the DB / hide the iFrame / display a 'Thank You' message).

You'll need to write your own JS function to handle this data and load it up on your page. The data gets passed in as JSON, so you'll need to parse that:

function postMessage(data) {
    let ccInfo = JSON.parse(data)
    console.log(ccInfo)
    console.log(ccInfo.CardID)
    console.log(ccInfo.CreditCard.NameOnCard)
}

Open in new window

If you're already loading up your own JS file (enqueing a script), then just add the function to that.
Hello Chris,

Thank you for your response. Yes the data comes back to a JS function that sends the token/data to the customer’s profile in our third party Customer Management SaaS vendor’s site. Question is what syntax would I use to to then send back a echoed response message to the same iFrame or in place of that iFrame?

I have not seen the JS Function yet. I am going to have my colleague show me where it is in our Azure backend and I will copy the code and post it here so you can possibly make a suggestion based on that.
I like the idea of parsing it, saving it to the DB, Hiding the iFrame and then displaying “Credit Card Saved Successfully” also maybe we can have it return the card number with asterisks on all numbers except for the last four just like the Post Message as I see might be what you have in that parse, although the project lead as specifically said to have it say the above message. I am going to ask him if he would like a little more detail.
SOLUTION
Avatar of Chris Stanyon
Chris Stanyon
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
If you want to use some of the data in the output, that's straight forward enough:

let ccInfo = JSON.parse(data);
...
$('#cc_frame').hide().after(`<p>Credit Card Saved Successfully<p><p>Card Number: ${ccInfo.CreditCard.AccountNumber}</p>`)

Open in new window

Chris,
Well actually we do not have access to that JS file as that is all handled by WorldPay so we have to write our own script or function within our PHP file that can read the new HTML body and based on that read then display the message “Credit Card Saved Successfully” , and the project lead says all he would like is the message.
Pretty sure it's not a WorldPay thing. Look at the page that gets loaded in your iFrame:

<body onload="parent.postMessage(...)"

This is telling that page to fire a function called postMessage in the Parent Page - which is your site (the parent of the iFrame is one of your pages). So you need a function on your site called postMessage. Tis function would then receive all of that data and you can do whatever you like with it.
I think you are right. We may have just discovered another task that is supposed to be handled by our end that my other colleague did not know about. I am going to look into this with my team and come back and post what I find.
No worries

Pretty much every payment gateway I've ever worked with has some kind of callback mechanism. This is fired after the user has finished visiting the payment gateway site (paypal / worldpay / sagepay etc). It's a way for the gateway site to communicate vital information back to the originating site (Javascript functions / POSTed forms etc.) Usually the site (our site), then parses that information and takes appropriate action (displays a success / failure page, updates a database, changes an order status, sends out a confirmation email etc.).

The callback mechanism in place here is by sending relevant data to a Javascript function on your own site. It's up to you to code the handlers for that.
Ok, after review with my team it was in question whether or not we were supposed to update our SaaS website with that info. I cross referenced the account I created above that had the successful credit card submission with the account within the SaaS providers online database and the card information was in fact successfully posted into that account. So that information sent back to the iFrame is sent to us for us to do with it what we please and in our case all we want to do is check the "Card ID" value and if there is something in that field then we know the card save was successful because I believe that ID is returned by our SaaS provider after the token is posted to the associated SaaS web account. If we write the function to check the Card ID and it turns out we need it to check another field I can just simply change that.

Now that we know how our company, worldpay, and our SaaS provider are communicating, do you have a suggestion as to how to get this goal accomplished? Edit the current PHP snippet? write a new one? I googled and see that PHP can parse the JSON info. I do not think we have a JavaScript plugin installed on our WordPress site at the moment and I am trying to avoid installing any more plugins so if we can get it accomplished with PHP that would be great.

Right,

I'd be very surprised if your site doesn't already have Javascript running. Javascript isn't a 'plug-in' thing - it's just a file that you load up - in the same way that you load up your CSS files. Load up your page, and look in the <head> of your document (Ctrl+U to view Source). You may also want to look right at the bottom of the source (just above the closing </body> tag. You're looking for a <script src="...."> line - that tells your site to load up the Javascript. You'll probably have quite a few - various plugins load up their own.

Probably the easiest way to do this is to just add that postMessage function to that script (js file).

You can of course use PHP to do whatever you need, but the callback from your iFrame is specifically Javascript, so you've got to handle it in Javascript in the first place - even if that's just to catch the data and forward it on to a PHP script - as you're just wanting to check a value and update the DOM (the page), there seems very little reason to push it to PHP (aside from that, PHP can't update your DOM to show/hide elements - Javascript can!)
I understand it does not need a plugin to run. I guess what I was referring to was a plugin for direct JavaScript editing on our word press sites which after thinking about it should not cause any effect on the site but I just was wanting to avoid installing anything else. After looking at the source of the page, there is a ton of JavaScript files being referenced and a bunch of emedded scripts.

User generated image^^^Scripts in relation to the CC form^^


User generated image
^^^^ A Random - Text/JavaScript within the page


I expanded that WooCommerce JavaScript and it is a function contain product number, prices, and other shopping cart related info.


User generated image
^^^I found this area to add a custom JavaScript snippet, Can we use this to hook into the same hook as the PHP script and then have it check for the "Card ID" and display the message? or just use this given "wp_head" hook or find a footer hook?
Chris,
I must not have fully refreshed because I did not see this that you wrote earlier:
A few ways to do it really. First off, I would give your iFrame an ID so it's easier to find in the DOM:
      
            
echo '<iframe id='cc_frame' src="' . urldecode($url) . '"></iframe>';

Open in new window

              Select all        Open in new window
      Now in the JS function that handles the callback, you can reference that. Here's one way to do it:
      
            
$('#cc_frame').hide().after('<p>Thank you<p>')

Open in new window

             
This seems like the direction I need to head....How do I figure out which JavaScript function is handling the callback?
Yeah - using that Snippet there should work fine. It will simply add that script tag to the <head> of all your pages.

As a quick test, just add the following between the <script> tags:

console("We're in the JS Snippet here");

Save it and load up your site (any page). Press F12 for the WebDev tools and look at the Console tab - you should see your message (you might need to refresh the page).

If that works, then just add in your postMessage(data) function and test it.

FYI - those scripts that are part of the CC form belong to WorldPay and they're embedded in the page shown within the iFrame - you have no access to those.
Ah - we cross-posted.

Right - the Javascript function for the callback is called postMessage (look at the content on the iFrame and you'll see this:

<body onload="parent.postMessage(...)"

So the iFrame page will try and fire a function called postMessage.

function postMessage(data) {
    let info = JSON.parse(data)
    console.log("Just Testing the iFrame Callback")
    console.log("Data From CC", info)
}

Open in new window

^^^
User generated image^^It is saying that console is not a function
Are you sure you've got console.log(...) and not just console(...) ?
oh I did not know that: I was following your post

As a quick test, just add the following between the <script> tags:
                                    
                                    console("We're in the JS Snippet here");

It was a typo, no problem, I understand how that works now and it is working, I put "Credit Card Saved Successfully"

User generated image
I know the previous one was a test. I am learning how this code syntax works. I see now you define the "let" and then you pull the values from the associated field names
Sorry about the typo - been a long day :)

Not entirely sure what's going on there. Just done a quick test with the data you've posted and I have not problems. For testing purposes, just dump out the raw data as it's received:

function postmessage(data) {
    console.log(data);
}

Open in new window

Post up what you see in the console.
User generated image^^^When trying this I got this (below)
User generated image

User generated image

^^When trying this I get (below)
User generated image


It looks like I may have needed a caps M on postMessage. Trying again. I am having to fully sign up and and enter a card everytime to get the callback
I apologize if I am taking your post comments literal and copying/pasting. I am on a deadline for this but I need to be taking what you say and applying some intuitive knowledge along with what you are teaching me to get the task accomplished. It looks like you have already given me all that I need to get this done, I just need to get the syntax correct and do what I need with it. Thank you. I will post my results after I make some more progress. They want this done by 40min but that is another story, lol, thank you.
Hey Netty,

Yeah - sorry if I keep dropping typos. I'm typing code directly into the comment box so there's no syntax checking. Mistakes happen :)

I always prefer to teach and educate, rather than give copy/paste answers, but I do understand the pressures of deadlines.

That output has thrown me completely - I have no idea where that's coming from. When you get that, if you examine the source of your page, what do you see in the onload() method of the iFrame page.
User generated image


User generated image



User generated image


^^^^^^^onload="parent.postMessage(JSON.stringify(^^^^^
Do we need to add "parent" to postMessage, for expample parent.postMessage --who would be the parent document in this, would it be on the worldpay domain or is the parent our page housing the iFrame and is any of this of any relevence to this issue? let me know your thoughts, thank you 
Hmmm - your function is firing so that's correct. What's confusing me is that what's showing in the inspector (part of the onload() call) is not what's being passed to your postMessage() function.

What you should be receiving is a string representing the data that you can see. What you're gettting is an object with a few properties - any idea where the api : app.textrequest.com is coming from?
Ok, I have the Javascript Snippet set to run on the Front End only as that was the default setting. The other PHP script is set to run everywhere. Javascript should just be front end, right? And yes that app.textrequest.com is tied to a pop up on the window for customers to request a text message/chat type scenario. I believe we are going to disable that before production as it gets in the way of the forms on mobile devices.
Maybe that postMessage callback is calling back to the parent on WorldPay and then to the SaaS. idk I am just attempting to learn and wrap my head around what is occurring here so that may he way off.
Found this from Worldpay
To capture credit cards in your application -- either for one-time charges or to store "on file" -- follow these steps.
   1.   Call one of the "Hosted Payments" API methods: /hostedPayments/charge to make a one-time charge, /hostedPayments/store to store a card on file, or hostedPayments/update to update a stored card. These methods return a URL to the Hosted Payments system.
   2.   Present that URL in your application, usually in an iframe or web browser control.
   3.   When the user submits the form, it will redirect to a "callback" method within the SaaS Web app API. This method will determine the success or failure of the transaction and update the SaaS Web App database accordingly (creating a payment or storing the card on file).
   4.   The callback will inject the following HTML into the iframe in which you presented the Hosted Payments window:
<html> <body onload='parent.postMessage(JSON.stringify(" + message + "), \"*\")'> </body> </html>

where "message" is a JSON representation of the result of the transaction result (for a "charge") or the card stored on file (for a "store" or "update"). You should read this data and use it to communicate success or failure in your app.
I am reviewing what you wrote above before:


<body onload="parent.postMessage(...)"
                                    
                                    This is telling that page to fire a function called postMessage in the Parent Page - which is your site (the parent of the iFrame is one of your pages). So you need a function on your site called postMessage. Tis function would then receive all of that data and you can do whatever you like with it.

--------------------------------------------------------------------------------------------------------------------------------
I understand now that our page is the parent page for the iFrame and worldpay has set this up this way calling back to parent.postMessage so we can use the postMessage function to work with the data. Strange that it is not working.
Right - that's reassuring - the WordPay docs are saying to do exactly what we've been trying to do (Point 4).

As far as the iFrame goes, the 'parent' is the page that hosts the iFrame (which is your own page). So by calling parent.postMessage it's looking for the postMessage function from within your own page, which you have working - it's working because you can see the output in the console log. The bit that's baffling is that the function is recieving different data than the iFrame is sending, and I don't know why.

FYI - In wordpress, Javascript works on the frontend (the user facing side) and the backend (the admin). I'm guessing for this process, you on;y need it on the frontend as that's where the interaction is happening.
I have been researching trying to find example of reading the data from a child iFrame's  "parent.postMessage" output

I am finding more involved scripts written to read that data. I do not know if they are relevant to this case. Is there another way to read/capture that data being sent through the parent.postMessage callback other than "function postMessage" maybe a more specific way to read the message being sent from the child iFrame?
I know that it should not have matter but for once I get the script correct I would like it to only run on this particular page in case there are other postMessage callbacks on any other parts of the website so I changed the hook to the same hook where the PHP script is running and it inserts it into that specific spot on that page instead of into every header on every page. It still seems to be working the way it was. Also I removed the "request a text" app/plugin...…..Now just trying to figure out how to read this callback data, if this way is not working there must be another way to read that data or maybe a way to figure out why it is not
OK. Now you've removed that plugin and only enabled th script on that given page, have you checked to see what the output is on the console.
Yes it appears to be the same thing without the textrequest app.....I also set it to run everywhere and after I accidentally hit airplane mode on my laptop and lost connectivity I got some additional data returned to the console for name resolution and inactivity.
User generated image
Chris,
I do not think this function is firing.

function postMessage(data) {
    console.log(data);
}

Open in new window

It appears like I get the same console info when I deactivate this. I have been searching for another way to console.log the output from postMessage. When you put (data) what do you mean by that? I am trying to understand the code so I can attempt to write an alternative.
Chris,
does it matter where the JavaScript is inserted? I am attempting it at all spots to see if that will make a difference since it is waiting for the onload.
Hey Netty,

It seems odd that you still get output when you disable the function, but then we've had a few odd things going on. I'm obviously guessing at most of this as I don't know your app, and I can't see it running in a live environment.

Earlier on, before we were actually using data, you were getting the output that you'd hard-coded into the function, so I'd assumed it was firing correctly.

One thing to bear in mind with all of this. When you view the console in any site, you'll see a lot of output, regardless of what functions are running. It is possible that the info you're seeing in the console has nothing to do with this function, and it's just the normal, run-of-the-mill junk that website and browsers dump out - all the time. That screenshot that you've just posted (with the POST dump) looks like a normal console output that has nothing to do with the script.

I think what would make this a little clearer is to output an identifier, so that at least you knew what you should be look for

function postMessage(data) {
    console.log("CC Info", data);
}

Open in new window

Now in the console, you'll see the CC Info text before out expected output. Should help to identify the correct data dump.

As for (data) - when you write functions, those functions can take in parameters (or arguments). These are defined in the parenthesis. For example, if we had this:

function DoSomething(name) {
    console.log(name);
}

We would then call it with DoSomething("Chris") and it would pass "Chris" in as the name argument. In your case, if we look at the onload from the iFrame you'll see this:

parent.postMessage(..........)

That info between the brackets will get passed to the postMessage function. It's up to us what we call it:

function postMessage(someInfo) {
    console.log(someInfo)
}

Hope that makes sense :)
Netty,

Don't forget, it's waiting for the onload of the page within the iFrame (i.e the page that shows after form submission). It's not waiting on YOUR page load. At this point, it shouldn't matter where it's loaded.

Just as a quick alternative for testing, add an alert to the function before the console.log:

function postMessage(data) {
    alert("We're about to handle the data!")
    console.log("CC Info", data)
}

Open in new window

This way there will be absolutely no doubt whether the function is firing or not - you'll get a popup message!

User generated imageI have a space after the comma, that is ok, right? does this syntax look exactly correct to you?
it gave me no results and no indication that it was fired (below)
User generated image
User generated image         

User generated image
The only time I got the hard coded message to appear was when I only put:

console.log("Credit Card Saved Successfully"

I did not have the function declared above it, on did a direct consol.log
I am reading that it may require and event listener like
window.addEventListener
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I think this is on the right path and the way to do it but I think the syntax is not correct. I am googling ways to write it and trying to get it right.

I definitely believe this is the way to go because you can see the event occur in the dev tools after the Credit Card is processed and it returns the info to the browser so we need to listen to that.
I tried yours, then a couple variations, Below is my latest attempt...it adds the word "function" before (event) and also some brackets enclosing "return". Also colons ; after the "alert" line and the "console.log" line. Also I am reading that it may be document.addEventListener because the event is originating the iframe document...….also reading possibly including event.source.postMessage 

window.addEventListener("message", function(event) => {
    if (event.origin !== "https://certtransaction.hostedpayments.com") {
        return;
 }
    alert("Credit Card Saved Successfully");
    console.log(event.data);
}, false);

Open in new window

OK.

The syntax that I posted is correct, although it uses some more advanced coding - it uses a closures for the callback instead of a named function. The code as you've shown it won't work. You don't need the function keyword adding. If you do prefer that syntax, then remove the double arrow (=>)

As for the curly brackets, they're only required if your statement contains more than one line - for single line if statements, you don't need them (return;)

In JS, semi-colons are optional. I prefer not to use them, but that's your choice.

The postMessage event is a window event, so attaching to the document won't work.

Not sure what you mean about the event.source.postMessage. The data that's been sent is in the event.data property.
Thank you Chris for that info.
I tried it exactly how you have it both with the header hook and the other checkout page hook. Tried the secondary syntax method with the double arrow removed. Still no alert and nothing in the console.
User generated imageChris, it looks like the default priority for the script is 10.

Do you think I need to change this priority and how do I determine what number to set if so? Also do I need to manually put the priority number into the script. My understanding is priority would be if there were multiple functions running within the same script and priority would determine the order at which they run. But what about all the scripts running on a page? Would there ever be a need to change this default priority it so showing here?
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
You can safely ignore the priority settings
excellent, good find, I will report back
Now the card processing seems to hang and it presents a page suggesting a redirect.

We might be catching it all before it is processed now
The script we've put in place won't have any effect on a re-direct as this would occur at the iFrame servers end.

Just tried testing again, but it's just telling me it can't display the payment page. Been a while since I used WorldPay, but I hated it then - and I hate it now ;)
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
It is working!!!
User generated image
Now just need to have it check CardID for any value and then hide the iframe and present the message...…I am going to review what you wrote above and see if I can put it together or if you have another moment to make a suggestion that would be great.
I forgot we have to parse out the "CardID" first like you had us doing before.
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thank you, you have made this a great learning experience and helped me out tremendously. I will report back what I put together and the results.

User generated image

   The Parse is working grabbing the CardID but the $ is not working. I think I need to bring Jquery in for it to allow the Jquery functions. I have found a bunch of different answers on how to do it in WordPress and all of them seem to reference editing the theme and adding a folder, etc although I am reading that Jquery in enabled at the core of WordPress so there must be a way to invoke it or add a wrapper or something to allow me to insert Jquery into this script.

Maybe something like this that I found:

jQuery(document).ready(function( $ ) {
    your_init_function();
});

Open in new window

Or if I do not use Jquery I would need to maybe do the a full JavaScript version starting with:

document.getElementById

Open in new window

Got it working with this!!

 jQuery(document).ready(function($) {
    $('#cc_frame').hide().after(`<p>Credit Card Saved Successfully</p>`)
});

Open in new window

Nice one Netty. Glad you got it sorted.

By default, in WordPress, jQuery is in 'compatibility' mode, so it use jQuery() instead of the usualy $().

Good luck with the rest of your project :)