Using the Callback URL and other URLs in a PaymentProvider
i Matt
Loving Vendr more than ever. We're using Vendr Checkout - you've done a great job trying to make this work for every scenario. We're currently on 2.0.x but will get it to 2.1.0 before live I think.
I'm using the ctx.Urls.CallbackUrl which is working pretty well (love the way it gets the contextual data for you), however the way GlobalPay works, is it actually renders the content of a single URL (so not async) to the browser, on their domain, after POSTing to it, so I'm having to render some Javascript to do a redirect back to the site. The only PP I've ever seen that does this.
Could you just confirm for me whether I should be sending the user back to the ctx.Urls.ContinueUrl if successful, or should I be manually updating the order to be Completed and sending them to /checkout/order-confirmation? Or /basket with an error if it failed?
For some reason, this is setting my order status to Error, but payment is Captured, so it's kind of working. If I return anything but a 200 to GlobalPay, it dies. I can't see anything that tells me why it's in Error, but I suspect because I'm not (and can't) use return CallbackResult.OK as you have in the source I've seen.
It's not that clear to me what the Continue/Cancel/Error URLs should be used for (if anything) - sorry if I'm being daft.
I could do this with a simple SurfaceController and manually update the order, but I don't want to do that if I don't need to. Feel it may well give me a bit more control though, so if it's not clear what I'm asking, I'll probably go that way.
Thank you!
Tom
var url = "?";
var qs = postDataAsNameValueCollection;
var isComplete = qs["RESULT"] == "00";
// ... other logic to ensure the payment response if valid
var response = request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent($"<html><head><script>window.location.href='{url}';</script></head><body><p>Redirecting back to the website</body></html>");
// should I complete the order manually here? Or does the consumer of the CallbackResult do that?
return new CallbackResult()
{
HttpResponse = response,
TransactionInfo = new TransactionInfo()
{
AmountAuthorized = priceFromPayment,
PaymentStatus = isComplete ? PaymentStatus.Captured : PaymentStatus.Error,
TransactionFee = 0m,
TransactionId = qs["ORDER_ID"]
},
MetaData = new Dictionary<string, string>()
{
{ "globalPayResult", qs["RESULT"] },
{ "globalPayAuthCode", qs["AUTHCODE"] },
{ "globalPayMessage", qs["MESSAGE"] },
{ "globalPayOrderId", qs["ORDER_ID"] }
}
};
So payment providers can kinda work in 2 ways, which dictate which URLs get used, and how.
Secenario 1 - Webhook Confirmation
By far the most popular approach of payment gateways is to redirect the customer to their payment portal, the customer completes the purchase, and then the payment gateways returns the customer to the site. In the background though, the payment gateway sends another request to a webhook handler to actually be notified of successful payment.
By notifying you of payment status over webhook, it ultimately means that should anything go wrong in the actual checkout flow (maybe the customer just closes the browser) you still get notified regardless.
In this scenario, you'll generally use the 3 URLs that Vendr exposes like so
ctx.Urls.CancelUrl - Where to redirect to if the user cancels out of the payment portal
ctx.Urls.ContinueUrl - Where to redirect to once they are done at the payment portal.
ctx.Urls.CallbackUrl - The webhook handler that actually checks for successful transaction.
The important thing to note here is that the continue URL is just responsible for forwarding the customer to a confirmation page. It's not actually responsible for finalizing the order, and in fact, due to the nature of webhooks this could actually happen a couple of seconds or even minutes later when the webhook arrives.
Needless to say though, the order confirmation emails etc won't be sent until the order is finalized via the webhook.
Scenario 2 - Continue URL Confirmation
In the second scenario, rather than the webhook being the thing that finalizes the order, we can actually get the Continue URL to perform this task too.
This is generally used by payment gateways that send transaction details on the querystring back on the continue URL, rather than via a separate webhook handler.
In this scenario, you'll generally use the 2 URLs that Vendr exposes like so
ctx.Urls.CancelUrl - Where to redirect to if the user cancels out of the payment portal
ctx.Urls.ContinueUrl - Where to redirect to once they are done at the payment portal, but also extract transaction info and finalize the order.
In order to get the Continue URL within Vendr to do that work of finalizing the order though (remember from scenario 1, it's normal job is just to send you to the confirmation page) you need to override the FinalizeAtContinueUrl property in your payment provider and have it return a true value.
With this set, it now means accessing the Continue URL will also trigger a call to your ProcessCallbackAsync method in which you can perform the relevant logic necessary to extract the transaction info from the request, and return to Vendr the transaction details.
Vendr will perform this task first, and then perform the redirect to the Continue URL.
So these are the 2 scenarios. I get the feeling option 2 will be what is required by your gateway.
RE Preparing Transaction
Just to touch on your issue with having to notify the payment gateway and then render some javascript to trigger the payment, I think you should be able to do this from your payment providers GenerateFormAsync
You should ultimately perform the server tasks within this method, notifying the gateway of the transaction coming, which this should then give you back the JSON you need for the js.
In those scenarios I'd say render a PaymentForm that redirects you to your on site page and have that do what it needs to do to take payment, but then when it's done, send a request back to the payment provider via it's CallbackUrl to update the transaction in Vendr and then redirect to Vendrs ContinueUrl to then continue the checkout process.
I'd say maybe in the PaymentForm render some JS out containing Vendr's continue / callback URL so you have easy reference those URLs in your JS code.
Thanks Matt,
In a future update it would be great to be able to have the checkout extension support that natively so you can just click to payment and have the form render there.
The problem with this is that pretty much every payment gateway does this kind of stuff differently and the only semi standard API they have is the hosted payment option. The hosted payment option puts much less requirements on the implementor too from a PCI complience perspective, so these are the main reasons we target the hosted options by default.
I do think we can try and make these types of checkouts easier though, so hopefuly we can review this and see what we can to do at some point 👍
Hi Matt,
Yeah. Everyone does it differently but it would be nice to be able to build say an eWay solution as we are and be able to package that up better to provide as an available solution on your site for example.
We have NAB working in most recent case but with as discussed its not something you could package up.
For you guys it would be more of the means to have a "self hosted" method, checkout being able to also handle that so you can then call your own form and process then handle the return which there is very close to being fine for a more seamless package already.
A true error "page" as well to handle that.
Then it would be the payment gateway dll and the rest view the payment provider that would generate the form and good to package up.
Amazing Matt, thank you very much. A very well supported package, this one!
Great explanation, I'll be working on this again next week so will try it out. Definitely option 2 for this scenario, but I'm sure your explanation of option 1 will help us (and others!) in future.
I'd much rather be using Stripe! No support for our little island yet though.
Would you mind if I asked how you are getting on with this? I was just about to post the same question but it sounds like you are a bit ahead of us! :)
We are have real trouble getting the Global Payments stuff to work with Vendr/Umbraco. I think like you mentioned, I can get the HPP documentation demo code working in a standalone, surface controller solution but as soon as I try to involve the Payment Provider and passing Json responses back we're hitting a brick wall.
I was wondering whether you've had any joy with this? It's a bit of a nightmare isn't it...
The main problem seems to be that, rather than any other provider, where the success URL is used as a POST or GET, what GP have decided to do is "download" the HTML from the URL after a POST, rather than POST to it. Why, I've no idea.
So if there is any kind of redirect in there, it fails with the awesome message
Your transaction has been successful but there was a problem connecting back to the merchant's web site. Please contact the merchant and advise them that you received this error message.
So I think what we're going to have to do, is have the URL that's sent in RealexHpp.redirect.init('continue', '" + url + @"', hppJson); to a SurfaceController render, which then looks at the POST data, and renders a javascript redirect to either the ContinueUrl if successful, or the CancelUrl if not.
The URLs can be passed over to GP using HostedPaymentData
var hostedPaymentData = new HostedPaymentData()
{
SupplementaryData = new Dictionary<string, string>()
{
{ ctx.Settings.SupplementaryDataKey, ctx.Order.OrderNumber },
{ "ContinueUrl", ctx.Urls.ContinueUrl },
{ "CancelUrl", ctx.Urls.CancelUrl }
},
I also don't think it likes the localhost:port URLs. I've tried using ngrok without much success so far
I'll let you know if I make any progress - this is getting quite urgent for us now, so I'm going to do my best to come up with a solution today.
Let me know if you've found a way around this weirdness.
However, when I then try to manually go to the ContinueUrl, I'm being redirected to the Error page (set up in the payment provider) but no errors are being logged anywhere, so I'm not sure what's going wrong.
I think I'm going to have to manually Finalize the order in the SurfaceController, and perform the redirect to /checkout/order-confirmation/ if successful, and back to the basket with a message if not.
Not ideal, but I can't think of any other way to work out why the ContinueUrl is failing/erroring.
Yes, we've made a bit of progress. Admittedly we are not doing much during the payment process - just credit card only on the Payment Gateway and we're using the Global Payments Hosted Payment Page solution:
So, following their code examples in the step-by-step pages, I've got this working using the Vendr Payment Provider and Payment Provider Settings. Initially I had it working with Surface Controllers but I was then running into problems trying to get native Payment Provider & Settings properties and values, so ended going back to the Payment Provider solution.
Anyway, to get it working we are:
in the Provider, setting FinalizeAtContinueUrl to true
in the Provider Generate Form method, essentially using the Global Payments HPP .net code (HostedService, HostedPaymentData etc).
Passing the JSON request back in the PaymentFormResult in a similar way to what Matt suggested above using WithJs and passing in the continue url along with it.
(We're using the embedded iframe option but I also had the lightbox version working.)
Then, upon the response return then ProcessCallback method is firing and, again, we are using the HPP .net code as a basis to handle the response Json and proceed accordingly.
It all seems to be working so far. Couple of issues testing with 3DSecure v2 stuff like ensuring a mobile number is added to the request AND that it had the international dialing code in the preferred Global Payments format with it.
I figured it would be easier to use the Full Page Redirect, but because of how GlobalPay handles the success/redirect, it's a real pain with Session management etc.
So, if I can't get this working well using a custom Surface Controller, I'll probably see if I can make it work with the embedded iframe.
Are you using a custom Checkout, or Vendr Checkout? Just interested to know where your embedded form is rendered.
Yeah, a custom checkout process. Name/billing address page (online only so no shipping page required) -> Confirmation page -> Payment Page (with embedded iframe and vendr beginpaymentform) -> Order Complete page if response successful.
If error is returned then back to Payment Page with error message displayed.
Using the Callback URL and other URLs in a PaymentProvider
i Matt
Loving Vendr more than ever. We're using Vendr Checkout - you've done a great job trying to make this work for every scenario. We're currently on 2.0.x but will get it to 2.1.0 before live I think.
We're doing a more "standard" checkout this time, using a horrible payment provider called GlobalPay. They're really quite rubbish. Docs are average: https://developer.globalpay.com/ecommerce/hosted-solution/HPP-Guide#hpp-guide
I'm using the ctx.Urls.CallbackUrl which is working pretty well (love the way it gets the contextual data for you), however the way GlobalPay works, is it actually renders the content of a single URL (so not async) to the browser, on their domain, after POSTing to it, so I'm having to render some Javascript to do a redirect back to the site. The only PP I've ever seen that does this.
Could you just confirm for me whether I should be sending the user back to the ctx.Urls.ContinueUrl if successful, or should I be manually updating the order to be Completed and sending them to /checkout/order-confirmation? Or /basket with an error if it failed?
For some reason, this is setting my order status to Error, but payment is Captured, so it's kind of working. If I return anything but a 200 to GlobalPay, it dies. I can't see anything that tells me why it's in Error, but I suspect because I'm not (and can't) use return CallbackResult.OK as you have in the source I've seen.
It's not that clear to me what the Continue/Cancel/Error URLs should be used for (if anything) - sorry if I'm being daft.
I could do this with a simple SurfaceController and manually update the order, but I don't want to do that if I don't need to. Feel it may well give me a bit more control though, so if it's not clear what I'm asking, I'll probably go that way.
Thank you!
Tom
Hey Tom,
So payment providers can kinda work in 2 ways, which dictate which URLs get used, and how.
Secenario 1 - Webhook Confirmation
By far the most popular approach of payment gateways is to redirect the customer to their payment portal, the customer completes the purchase, and then the payment gateways returns the customer to the site. In the background though, the payment gateway sends another request to a webhook handler to actually be notified of successful payment.
By notifying you of payment status over webhook, it ultimately means that should anything go wrong in the actual checkout flow (maybe the customer just closes the browser) you still get notified regardless.
In this scenario, you'll generally use the 3 URLs that Vendr exposes like so
The important thing to note here is that the continue URL is just responsible for forwarding the customer to a confirmation page. It's not actually responsible for finalizing the order, and in fact, due to the nature of webhooks this could actually happen a couple of seconds or even minutes later when the webhook arrives.
Needless to say though, the order confirmation emails etc won't be sent until the order is finalized via the webhook.
Scenario 2 - Continue URL Confirmation
In the second scenario, rather than the webhook being the thing that finalizes the order, we can actually get the Continue URL to perform this task too.
This is generally used by payment gateways that send transaction details on the querystring back on the continue URL, rather than via a separate webhook handler.
In this scenario, you'll generally use the 2 URLs that Vendr exposes like so
In order to get the Continue URL within Vendr to do that work of finalizing the order though (remember from scenario 1, it's normal job is just to send you to the confirmation page) you need to override the
FinalizeAtContinueUrl
property in your payment provider and have it return atrue
value.With this set, it now means accessing the Continue URL will also trigger a call to your
ProcessCallbackAsync
method in which you can perform the relevant logic necessary to extract the transaction info from the request, and return to Vendr the transaction details.Vendr will perform this task first, and then perform the redirect to the Continue URL.
So these are the 2 scenarios. I get the feeling option 2 will be what is required by your gateway.
RE Preparing Transaction
Just to touch on your issue with having to notify the payment gateway and then render some javascript to trigger the payment, I think you should be able to do this from your payment providers
GenerateFormAsync
You should ultimately perform the server tasks within this method, notifying the gateway of the transaction coming, which this should then give you back the JSON you need for the js.
From
GenerateFormAsync
though, you can return JS as part of this form (the Stripe payment provider does this for example https://github.com/vendrhub/vendr-payment-provider-stripe/blob/v2/dev/src/Vendr.PaymentProviders.Stripe/StripeCheckoutPaymentProvider.cs#L314-L330) which I think should handle your setup.It kind of requires you to know the ID of the button being rendered on the "review" step, but if it's just a one off provider that might be ok.
Otherwise you could always render a css hidden button and use that, but then trigger it via javascript.
Anyways, I hope this points you in the right direction.
Matt
Hi Matt, What about when you have something like eWay so you need an inline form and your handling requests and responses that way?
In those scenarios I'd say render a PaymentForm that redirects you to your on site page and have that do what it needs to do to take payment, but then when it's done, send a request back to the payment provider via it's CallbackUrl to update the transaction in Vendr and then redirect to Vendrs ContinueUrl to then continue the checkout process.
I'd say maybe in the PaymentForm render some JS out containing Vendr's continue / callback URL so you have easy reference those URLs in your JS code.
Thanks Matt, In a future update it would be great to be able to have the checkout extension support that natively so you can just click to payment and have the form render there.
Hi Liam,
The problem with this is that pretty much every payment gateway does this kind of stuff differently and the only semi standard API they have is the hosted payment option. The hosted payment option puts much less requirements on the implementor too from a PCI complience perspective, so these are the main reasons we target the hosted options by default.
I do think we can try and make these types of checkouts easier though, so hopefuly we can review this and see what we can to do at some point 👍
Hi Matt, Yeah. Everyone does it differently but it would be nice to be able to build say an eWay solution as we are and be able to package that up better to provide as an available solution on your site for example. We have NAB working in most recent case but with as discussed its not something you could package up.
For you guys it would be more of the means to have a "self hosted" method, checkout being able to also handle that so you can then call your own form and process then handle the return which there is very close to being fine for a more seamless package already. A true error "page" as well to handle that.
Then it would be the payment gateway dll and the rest view the payment provider that would generate the form and good to package up.
Amazing Matt, thank you very much. A very well supported package, this one!
Great explanation, I'll be working on this again next week so will try it out. Definitely option 2 for this scenario, but I'm sure your explanation of option 1 will help us (and others!) in future.
I'd much rather be using Stripe! No support for our little island yet though.
Hey Tom,
No worries. Really hope it helps.
And thanks for moving this to the forums. It's so much easier to support these types of questions here 🤘
Let me know how you get on next week.
Matt
Hi Tom,
Would you mind if I asked how you are getting on with this? I was just about to post the same question but it sounds like you are a bit ahead of us! :)
We are have real trouble getting the Global Payments stuff to work with Vendr/Umbraco. I think like you mentioned, I can get the HPP documentation demo code working in a standalone, surface controller solution but as soon as I try to involve the Payment Provider and passing Json responses back we're hitting a brick wall.
Thanks, Derek
Hi Derek
I'm not quite there yet, but hope to have something by the end of the week. As soon as I do I'll let you know how I achieved it, assuming I do.
Hi Derek
I was wondering whether you've had any joy with this? It's a bit of a nightmare isn't it...
The main problem seems to be that, rather than any other provider, where the success URL is used as a POST or GET, what GP have decided to do is "download" the HTML from the URL after a POST, rather than POST to it. Why, I've no idea.
So if there is any kind of redirect in there, it fails with the awesome message
So I think what we're going to have to do, is have the URL that's sent in
RealexHpp.redirect.init('continue', '" + url + @"', hppJson);
to a SurfaceController render, which then looks at the POST data, and renders a javascript redirect to either the ContinueUrl if successful, or the CancelUrl if not.The URLs can be passed over to GP using HostedPaymentData
I also don't think it likes the localhost:port URLs. I've tried using
ngrok
without much success so farI'll let you know if I make any progress - this is getting quite urgent for us now, so I'm going to do my best to come up with a solution today.
Let me know if you've found a way around this weirdness.
Thanks
Tom
I was hoping to return a javascript redirect from the surface controller, which would go to ContinueUrl if successful, CancelUrl if not.
So for now, I'm just rendering all Request.Form keys to the GP complete page, which actually renders it on GP domain...
https://pay.sandbox.realexpayments.com/card.html?guid=TEST-3308-4847-9593-94791aa4a16a
However, when I then try to manually go to the ContinueUrl, I'm being redirected to the Error page (set up in the payment provider) but no errors are being logged anywhere, so I'm not sure what's going wrong.
I think I'm going to have to manually Finalize the order in the SurfaceController, and perform the redirect to
/checkout/order-confirmation/
if successful, and back to the basket with a message if not.Not ideal, but I can't think of any other way to work out why the ContinueUrl is failing/erroring.
Hi Tom,
Yes, we've made a bit of progress. Admittedly we are not doing much during the payment process - just credit card only on the Payment Gateway and we're using the Global Payments Hosted Payment Page solution:
https://developer.globalpay.com/ecommerce/hosted-solution/HPP-overview
So, following their code examples in the step-by-step pages, I've got this working using the Vendr Payment Provider and Payment Provider Settings. Initially I had it working with Surface Controllers but I was then running into problems trying to get native Payment Provider & Settings properties and values, so ended going back to the Payment Provider solution.
Anyway, to get it working we are:
(We're using the embedded iframe option but I also had the lightbox version working.)
Then, upon the response return then ProcessCallback method is firing and, again, we are using the HPP .net code as a basis to handle the response Json and proceed accordingly.
It all seems to be working so far. Couple of issues testing with 3DSecure v2 stuff like ensuring a mobile number is added to the request AND that it had the international dialing code in the preferred Global Payments format with it.
Derek
Thanks for coming back Derek.
I figured it would be easier to use the Full Page Redirect, but because of how GlobalPay handles the success/redirect, it's a real pain with Session management etc.
So, if I can't get this working well using a custom Surface Controller, I'll probably see if I can make it work with the embedded iframe.
Are you using a custom Checkout, or Vendr Checkout? Just interested to know where your embedded form is rendered.
Cheers, Tom
Hi Tom,
Yeah, a custom checkout process. Name/billing address page (online only so no shipping page required) -> Confirmation page -> Payment Page (with embedded iframe and vendr beginpaymentform) -> Order Complete page if response successful.
If error is returned then back to Payment Page with error message displayed.
Cheers, Derek
is working on a reply...