As usual the PayPal documentation is quite messy and disorganised and thus I thought I’d document how I managed to get PayPal Adaptive working on a marketplace style website.
Warning!
Please use this post as a guide as to how the paypal API connects with your rails app. As Jamesw in the comments below points out, I have not created an adequate way of recording all details of each transaction; something that is no doubt required by law. So perhaps take a look at his comment after reading this. Hopefully you can work out a way to do this
Setup
After some searching I found that the best gem to use right now is paypal_adaptive. ActiveMerchant currently does not support PayPal Adaptive (there is a gem that add’s it in but it does not seem to be maintained.)
gem 'paypal_adaptive'
How it works
PayPal Adaptive is relatively simple, yet the messy documentation can make it appear daunting. Put simply, this is how I did it:
- create paypal_adaptive.yml in your config folder and add your details
- create a resource to handle payments – I created a “Purchases” resource (model: Purchase)
- when the user clicks a buy button it invokes an action I called “buy” in my PurchasesController
- in the “buy” action I firstly create a new Purchase item (Purchase.new) and then add in some details like user_id, product_id, amount etc.
- I then invoke a model method called “pay” on my newly created Purchase object (purchase.pay)
- in my Purchase model I define the “pay” method and inside it I finally make use of the paypal_adaptive gem by creating its Request class (PaypalAdaptive::Request.new)
- I add all the necessary data I want to ship off to PayPal into a hash, and then invoke the Request.pay method, passing in that hash of data. This makes an API call to PayPal, who then replies with a confirmation that this request is successful
- If the API call is successful I redirect the user off to PayPal to sign in and confirm the payment
- Once the user makes the payment, PayPal sends an IPN (instant payment notification) to your specified IPN address – which I routed to a method in my PurchasesController
- In this method, I find the the Purchase object and mark it as paid (purchase.paid = true) and then it’s done!
paypal_adaptive.yml
Go here to create a sandbox account (you will need it). Once logged in go to “Create a preconfigured account”. Create two accounts – one buyer and one seller. If you are using chained or parallel payments (payments that are split amongst more than one person) then create some more accounts.
Click on Api Credentials in the left hand side panel.
Now fill out your paypal_adaptive.yml using those credentials (also use the application_id I provide below – this is the testing application_id provided by www.x.com
environment: "sandbox"
username: "platts_xxxxxxxx_biz_api1.gmail.com"
password: "xxxxxxxxxxxx"
signature: "xxxxxxx"
application_id: "APP-80W284485P519543T"
test:
environment: "sandbox"
username: "platts_xxxxxxxx_biz_api1.gmail.com"
password: "xxxxxxxx"
signature: "xxxxxxxx"
application_id: "APP-80W284485P519543T"
production:
environment: "production"
username: "my_production_username"
password: "my_production_password"
signature: "my_production_signature"
application_id: "my_production_app_id"
Create controller action to handle a buy request
Here you only really need the amount of money to be paid and a list of the emails you want that money to go to. So write your logic to work that out and then make a call to PayPal to setup the purchase.
data = {
"returnUrl" => return_url,
"requestEnvelope" => {"errorLanguage" => "en_US"},
"currencyCode" => "USD",
"receiverList" =>
{ "receiver" => [
{"email" => "platts_xxxxxxxx_biz@gmail.com", "amount"=> amount}
]},
"cancelUrl" => cancel_url,
"actionType" => "PAY",
"ipnNotificationUrl" => ipn_url
}
#To do chained payments, just add a primary boolean flag:{“receiver”=> [{"email"=>"PRIMARY", "amount"=>"100.00", "primary" => true}, {"email"=>"OTHER", "amount"=>"75.00", "primary" => false}]}
pay_response = pay_request.pay(data)
if pay_response.success?
# Send user to paypal
redirect_to pay_response.approve_paypal_payment_url
else
puts pay_response.errors.first['message']
redirect_to "/", notice: "Something went wrong. Please contact support."
end
Handling the IPN call
I route my IPN call from PayPal to this method:
ipn = PaypalAdaptive::IpnNotification.new
ipn.send_back(request.raw_post)
if ipn.verified?
logger.info "IT WORKED"
else
logger.info "IT DIDNT WORK"
end
render nothing: true
end
Unfortunately if you are on localhost, PayPal can’t send you the IPN, and hence there is a problem with testing this whole process. Ryan Bates’ solution is to use curl to mimic an IPN request. However as you can see in the code above, we make another request to PayPal confirming that the IPN is real. So even with curl sending a fake IPN, we run into problems. I’m going to go hunt for solutions now, but please comment if you have any ideas.
Tagged: paypal_adaptive, ruby on rails 3
