On Code etc.

Posts tagged paypal

6 notes

Tussling with Paypal using Ocsigen/OCaml

Since writing my last post about Ocsigen, detailing the beginnings of a rapidly developed (but very simple) web application with the OCaml web framework, I’ve had occasion to use it in a much more complicated (though still relatively simple) application - a small store to sell mp3s. It took, from start to finish, about 5 days, spending about 8-10 hours per day working on it.

There are many possible posts about this experiment (/experience) that I could write, and I was constantly surprised by how quickly I was able to add features or change large portions of the application (probably the most shocking was writing an entire shopping cart mechanism in under an hour, thanks to Ocsigen/Eliom’s support for session data), but I wanted to focus specifically on the integration with Paypal, which was probably the most difficult part (and wasn’t too hard).

When starting out with a new API (and not being able to use someone else’s bindings, which is a predicament often faced when using less commonly used programming languages), it’s helpful if there is a simple step by step example for a minimum viable integration. Something that shows what every interaction with the bare API looks like, in a language agnostic way. Unfortunately, most of the examples that I could find are instead in PHP, ASP, or ColdFusion, or are spread among many different pages.

Even the closest page I could find seemed to assume you already knew how to use the API, and so ignored many details (which in turn leads to various error messages). At first I wondered how it could be that a company as big as Paypal which has an API that is so widely used could not have stellar documentation, but then I realized if most people are using wrapper written in the programming language they are using, then such an overview is not necessary, or at least broadly needed.

What’s more, the documentation that is important to those writing the wrappers (and therefore familiar with the API) is all the minutiae of various options, which is available, and NOT a brief introduction (which was what I, not having ever used it before and writing in a language that does not have bindings, wanted to read). I still think there must be some page somewhere, but eventually gave up looking!

So, I present a brief overview of a minimum viable integration (with Ocsigen/OCaml used for code examples).

First, some points about actually calling the API. This seems really straightforward once you know what it is, but the error messages weren’t very helpful (in the documentation for errors, very few in any of the ExpressCheckout flow have a recommendation for resolution, they just repeat the error message that the api sent you!)

  1. All api calls are HTTP POSTed to https://api-3t.sandbox.paypal.com/nvp in the testing sandbox, and https://api-3t.paypal.com/nvp in production.
  2. The post-data is in the form PARAMNAME=paramValue&PARAMNAME2=paramValue2 …
  3. The first param Must be METHOD, which determines which API function you are caling. To initiate an expresscheckout, invoke SetExpressCheckout. Otherwise it gives you an error that the method does not exist.
  4. Other required parameters are your API credentials (“USER”, “PWD”, “SIGNATURE”) and the “VERSION” of the api you are using. A recent one seems to be “51.0”, though again, the most recent one seems elusive, as different pages use different versions in their examples.
  5. The method and then credentials are followed by the params for your method, as stated in the documentation for the various methods - this was the one place where documentation seemed to be very good.

On the OCaml end of things there are not a lot of good options for making http requests - and no native ones that worked for me. I tried many options - first, I looked at Ocamlnet’s Http_client, but realized very quickly that it did not support SSL, which made it unusable (aside from that, the interface looked pretty good).

Next was Ocsigen’s http_client library (which was of course available by default), but it didn’t seem to work - and wouldn’t spit out any debugging messages for me, making it frustrating enough to use that I moved on to the next option. I then tried the OCaml bindings to Curl (ocurl), but dynamically linking it into the application with Ocsigen kept giving errors, and since I was on somewhat of a deadline I decided to come back to it if no other options were available.

I think had I taken some more time (or been more familiar with the OCaml language - I’ve only been programming it for about a month) this could have been a good option - but equally there doesn’t seem to be a simple way to use it - it seems very C-like (setting loads of imperative options before telling it to run the call - instead of having a function that you pass a lot of arguments and get back the result of the request).

The final option, and what I ended up using, could be considered the sledgehammer solution - take a program I am familiar with and know works (wget), and call out to it using Unix.open_command. It seems ugly, and indeed I wouldn’t have thought of it if I hadn’t used (and enjoyed) a Haskell library that did the exact same thing.

So, to present a little code in the post, here is a Paypal wrapper (well, that’s giving it a lot more credit than it is due) in 21 lines of OCaml (special thanks to whoever wrote the OCaml section of Rosetta Code for the implementation of syscall - provided this is an efficient way to do that, having such a function available in standard libraries would be great!):

let api_endpoint = "https://api-3t.sandbox.paypal.com/nvp"
let credentials = [("USER", "api_username_from_paypal");
                    ("PWD", "api_password_from_paypal");
                    ("SIGNATURE", "api_signature_from_paypal");
                    ("VERSION", "51.0")]
let syscall cmd =
   let ic, oc = Unix.open_process cmd in
   let buf = Buffer.create 16 in
   (try
      while true do
        Buffer.add_channel buf ic 1
      done
    with End_of_file -> ());
   let _ = Unix.close_process (ic, oc) in
   (Buffer.contents buf)
let wget u pd = syscall ("wget --quiet -O - --post-data=\"" ^     
    (Netencoding.Url.mk_url_encoded_parameters pd) ^ "\" " ^ u)
exception Paypal
let paypal method_ params =
  let ps = ["METHOD", method_]@credentials@params in
    Netencoding.Url.dest_url_encoded_parameters (wget api_endpoint ps)

So, to continue on to actually implementing the ExpressCheckout, we can review from the paypal docs on it that we are supposed to first invoke SetExpressCheckout.

The raw call with wget would look like:

wget -O - --post-data="METHOD=SetExpressCheckout&USER=...&PWD=...\
&SIGNATURE=...&VERSION=51.0&AMT=10.00&RETURNURL=someurl\
&CANCELURL=someurl" "https://api-3t.sandbox.paypal.com/nvp"

In our case setting up the transaction looks like this (as all the credentials are handled by the ‘wrapper’, and the METHOD is the first argument):

paypal "SetExpressCheckout" [("AMT", amount);
                             ("RETURNURL", returnurl);
                             ("CANCELURL", cancelurl)]

Where returnurl is the url that paypal redirects to after the transaction, with “?token=SOMEVAL&PayerID=SOMEVAL” appended to it, and cancelurl is where it sends the customer if they cancel the transaction while at paypal.

Since we are using Ocsigen, we need to use Lwt_preemptive.detach so that this doesn’t block the whole webserver for the duration of the API call (which is going to be quite long, on the scale of a web request). A full service that sets up the paypal express checkout and redirect the person to paypal follows (the documentation says you should pass AMT, RETURNURL, CANCELURL with the redirect - doing so seems to cause it not to work, so I think the documentation is in error or out of date):

let purchase_start =
  Eliom_predefmod.String_redirection.register_new_post_service
   ~fallback:checkout
   ~post_params:unit
   (fun sp () () ->
     Lwt_preemptive.detach 
      (fun () ->
        try
        let chop_params s = 
          BatString.slice ~last:(BatString.find s "?") s in
        let returnurl = 
          chop_params (string_of_uri (make_uri ~absolute:true 
                ~service:(preapply purchase_end ("", "")) ~sp ())) in
        let cancelurl = string_of_uri 
                (make_uri ~absolute:true ~service:checkout ~sp ()) in
        let amount = total (get_shopping_cart sp) in
        let init_paypal = 
          paypal "SetExpressCheckout" [("AMT", amount);
                                       ("NOSHIPPING", "1");
                                       ("RETURNURL", returnurl);
                                       ("CANCELURL", cancelurl)] in
        let token = List.assoc "TOKEN" init_paypal in
        let redir = 
          "https://www.sandbox.paypal.com/webscr?cmd=_express-checkout&" ^ 
          (Netencoding.Url.mk_url_encoded_parameters [("token", token)]) in
          uri_of_string redir
        with _ -> 
          make_uri ~service:checkout ~sp ()) ())

This of course is code that depends on other services - mainly, ‘checkout’ and ‘purchase_end’. The former is simply a place where someone can initiate the checkout process, and purchase_end is what handled what paypal sends back to us. This service also depends on having a shopping cart you can access with get_shopping_cart, but that’s a pretty minor detail. As a side note - for simplicity, a few more details have been omitted - like saving the token before you redirect (to verify when the customer is redirected back).

Also, instead of hard coding in urls for the return and cancel, I used Ocsigen’s make_uri function that can construct them based on the actual services they reference - so if I change the url of the service, it won’t affect this code at all. (Since paypal will be adding the params to the returnurl service, I chop them off of before sending the url).

So then the customer approves the payment, and is redirected back to the url specified in RETURNURL, with the GET params token and PayerID. The API says you can get call GetExpressCheckoutDetails with the token to get their email address, name, etc. This is then simply:

paypal "GetExpressCheckoutDetails" [("TOKEN", token)]

And then once you confirm the payment from them (which is a step not required technically by the API, but the ui in paypal tells them they will confirm the payment in the next step, so it is probably a good idea), you can actually make the payment happen with DoExpressCheckoutPayment, which takes both the token you’ve been using all along, the original AMT, and the PayerID that was passed back in the URL, and specifying that it is a Sale (not an authorization). The code to do this using the very basic wrapper I am using is:

paypal "DoExpressCheckoutPayment" [("TOKEN", token);
                                   ("AMT", amount);
                                   ("PAYERID", payerid);
                                   ("PAYMENTACTION", "Sale")]

That’s all for now - I leave the details of implementation of the other services for subsequent posts or the reader’s imagination. Working on this project has led me to believe that Ocsigen really does offer a new way of looking at web programming - there are still a few areas I’m not familiar with (for example: looking for the equivilant of ‘liftM’ for Lwt’s monads and not finding anything, I ended up just sticking entire sequences of non-cooperative code inside of Lwt_preemptive.detach’s, instead of using the cooperative Lwt_process module - probably not as efficient, but resulted in clean, easy to understand code in little time), but overall, writing web application is very quick and seems very stable - bugs have been rare and easy to fix. Plus, getting to write functional code while doing it is a definite added perk! Look for more posts about writing this application.

Filed under ocsigen ocaml web programming paypal