Badminton on Rails
RSS icon Home icon
  • PayPal Recurring Billing with ActiveMerchant in Ruby on Rails

    Posted on February 20th, 2009 Raymond Law 42 comments

    ActiveMerchant is great and PayPal is easy to use. A lot of us already have a PayPal account for EBay. It works great in auctions where you pay once. However, ActiveMerchant currently does not support PayPal recurring billing. One requirement of a recent project is to be able to charge for a monthly or yearly subscription plan with PayPal. Therefore, I did some googling and found that Jon Baker has already extended ActiveMerchant to add this functionality using PayPal’s Name-Value Pair (NVP) API. However, as Cody Fauser pointed out, the NVP API was taken out from ActiveMerchant, so I had to implement that with PayPal’s SOAP API.

    First, download this file and put it in vendor/plugins/active_merchant/lib/active_merchant/billing/gateways/. Use the GitHub from now because maintaining a separate file download is too troublesome. Alternatively, there’s a fork on GitHub at http://github.com/rayvinly/active_merchant/.

    In your controller, after a user selects one of your subscription plan, the form goes to the checkout action:

      def checkout
        response = gateway.setup_agreement(:description => description, :return_url => return_url, :cancel_return_url => cancel_return_url)
        redirect_to gateway.redirect_url_for(response.token)
      end

    This redirects the user to PayPal so he can login and read the description you provided. After he confirms, he is redirected back to your application’s return_url, which I set it to be the complete action below. If he cancels, he is redirected back to the cancel_return_url. You can set cancel_return_url to be the plan selection page where he can choose a different plan.

    If he confirms, here’s the complete action:

      def complete
        token = params[:token]
        response = gateway.create_profile(token, :description => description, :start_date => start_date, :frequency => frequency_in_months, :amount => amount_in_dollars, :auto_bill_outstanding => true)
     
        # Save this profile_id in your transactions table.  This is used to cancel/modify the plan in the future.
        profile_id = response.params["profile_id"]
     
        if response.success?
          flash[:notice] = "Your PayPal account was successfully set up for the <strong>#{description}</strong> payment plan."
          redirect_to login_path
        else
          flash.now[:notice] = "There was a problem setting up your PayPal account for the <strong>#{description}</strong> payment plan"
          render cancel_url
        end
      end

    I default the frequency to be in months and turn on auto_bill_outstanding because that is what I need, but you can look at the file you downloaded and the PayPal documentation to see what other options are available. In particular, read these two PDFs:

    If the user wants to cancel the payment plan, a cancel action would look like:

      def cancel
        response = gateway.cancel_profile(paypal_profile_id, :note => 'Payment plan was canceled by user')
        flash[:notice] = 'You have successfully canceled your membership'
      end

    paypal_profile_id is the profile_id you saved in the complete action from above. Keeping this profile_id is very handy.

    That’s it. Enjoy making money!

    UPDATE 2009-02-04

    I updated the extension to include a credit_card hash. You will probably capture the credit card details submitted by the user in a form as in params[:credit_card], but I will just create a hash to show as an example. To make recurring payment with a credit card, just pass nil for the token (this is required, because PayPal would use the token in preference to the credit card). Pass the credit_card hash as follows:

        credit_card = Hash.new
        credit_card[:type] = 'Visa'
        credit_card[:number] = '1234567812345678'
        credit_card[:exp_month] = '07'
        credit_card[:exp_year] = '2016'
        credit_card[:cvv2] = '123'
        credit_card[:card_owner] = 'John Doe'
        response = gateway.create_profile(nil, :credit_card => credit_card, :description => description, :start_date => start_date, :frequency => frequency, :amount => amount, :auto_bill_outstanding => true)

    However, I got the famous DPRP is disabled for this merchant error message when I tested it. I did a little googling but I haven’t found the cause. There may just be something that you have to turn on in your PayPal developer account. I also read that it works for newer accounts only. But since I don’t have a need for this, I didn’t look further. If you guys know, please let me know. I will try to update the extension as soon as possible to make it work with a credit card.

     

    42 responses to “PayPal Recurring Billing with ActiveMerchant in Ruby on Rails”

    1. I’ve been looking for help with implementing recurring billing using Paypal. Thanks for the information. Is there a way to setup recurring billing that can accept credit cards?

      Thanks,
      Justin

    2. Justin,

      I updated the extension to include a credit_card hash. Please see the update in the post.

    3. Does paypal contact your site each month to say you have been paid, or do you get a cancellation message?

    4. Hi Raymond,

      Thanks for doing this. Rather than duplicate work, I will use your port of the Recurring Billings code myself and update my blog to send readers here. Thank you for your hard work rewriting this to use SOAP rather than NVP..

      Jon

    5. [...] UPDATE: ActiveMerchant recently removed the functionality to use PayPal’s NVP API and so this code will no longer work with the latest ActiveMerchant. Raymond Law has kindly ported the code to use the SOAP API and you can find out more information and usage on his blog. [...]

    6. @Anthony Ball

      I thought that this would be the best way to do it too originally, but actually from experience a more robust (Although I have as of yet had no issues on the live servers I have had no end of problems in the Sandbox) way to do it, is to forget about IPN Notification (where PayPal update you as an when a payment has been made) and instead run a monthly process using the PayPal API to check payment status, you can then check the status of the subscription profile, the status of the last payment and re-charge any missed payments if necessary. A much cleaner solution!

      Raymond’s file includes a get_profile_details method to do this.

      Jon

    7. I seem to have the actual recurring payment system working. The problem is trying to get the details_for to work (for grabbing the customer address, etc). I’ve tried details_response = gateway.details_for(token) and details_response = gateway.details_for(params[:token]), but both end up with an Invalid Token responce.

      Any ideas or an example?

    8. Raymond: I’ve added Trial Periods, you may want to patch your code, here is the added bit:

      xml.tag! 'n2:ScheduleDetails' do
      	xml.tag! 'n2:Description', options[:description]
      	xml.tag! 'n2:PaymentPeriod' do
      		xml.tag! 'n2:BillingPeriod', options[:period] || 'Month'
      		xml.tag! 'n2:BillingFrequency', options[:frequency]
      		xml.tag! 'n2:TotalBillingCycles', options[:cycles] unless options[:cycles].blank?
      		xml.tag! 'n2:Amount', options[:amount], 'currencyID' =&gt; options[:currency] || 'USD'
      	end
      	if !options[:trialamount].blank?
      		xml.tag! 'n2:TrialPeriod' do
      			xml.tag! 'n2:BillingPeriod', options[:trialperiod] || 'Month'
      			xml.tag! 'n2:BillingFrequency', options[:trialfrequency]
      			xml.tag! 'n2:TotalBillingCycles', options[:trialcycles] || 1
      			xml.tag! 'n2:Amount', options[:trialamount], 'currencyID' =&gt; options[:currency] || 'USD'
      		end	
      	end		
      	xml.tag! 'n2:AutoBillOutstandingAmount', options[:auto_bill_outstanding] ? 'AddToNextBilling' : 'NoAutoBill'
      end
    9. Arh – code doesn’t post properly! I’ll email it to you…

    10. Raymond:
      I think the reason why you can’t use SetExpressCheckout (you are using CreateRecurringPaymentsProfile) is because this requires PayPal API v54 and above, active merchant sets the API to v52 in paypal_common_api.rb

    11. Anthony – could you possibly send it this way to?

      mattman @gmail . com

      would also be interested to see how you’ve implemented it if you can.

    12. Anthony,

      I updated the file to include the TrialPeriod.

      The method describe by Jon should work if you want to periodically check payment status. I’ve not messed with PayPal IPN myself.

      When you call details_for, make sure you pass in the token you receive back from PayPal as it is. When you call get_profile_details, it takes a profile_id, not the token. I’ve shown how you get the profile_id in the post.

      I’ve tried forcing the API version to 54 with SetExpressCheckout but it didn’t work… But it might be a temporary thing with PayPal.

      Ray,

    13. Thanks for adding credit cards and thanks to Anthony for adding the trial period. It seems like this code would be a good candidate for addition to active_merchant. Has anyone tried forking the project and then requesting a pull? I’m willing to help out there if you need.

    14. Justin,

      Just forked at http://github.com/rayvinly/active_merchant/ and send a pull request. It is up to Cody now.

    15. Justin: Thanks for the thanks.

      I just got it working with SetExpressCheckout (api >= v54).

      Sent the new code to Cody, Raymon & Matt…

      Anyone have any ideas on how to use the IPN and this stuff?

    16. I’m having crazy problems with sandbox IPN - the PayPal simulator says that my URL is bad – I’ve tried it with a normal browser and an external URL ‘tester’ and all seems ok. Is there something specific that PayPal have done that make the IPN simulator system not work in sandbox? Or is it a problem with Active_Merchant and the reply?

      I’m using InstantRails, but my router appears to be doing the port conversion correctly (I’ve even tried with IPN and the port set to 3000, rather than the default. Could this be the problem somehow?

    17. Argghhh!!!

      I finally figured out what was causing the problem with IPN notification!

      I needed to disable rails protion from forgery with the ipn routine…

      Putting this at the top of my controller did it:

      protect_from_forgery :except => [:ipn]

      Now the IPN simulator works perfect.

      Hopes this helps anyone else out there with the same problem.

    18. Hi Raymond

      Thanks again for doing this I am just in the process of porting our code over to your class so that we can upgrade ActiveMerchant and in turn Rails. One thing I noticed was that you have not wrapped your options[:amount] with ActiveMerchant’s amount() method. I am not sure if there is a reason for this but by doing so you get the benefit of passing either an integer or money value as the amount (see some of the other classes in AM)

      i.e. xml.tag! ‘n2:Amount’, amount(options[:amount]), ‘currencyID’ => options[:currency] || ‘USD’

      Best regards,

      Jon

    19. Jon,

      Thanks for pointing that out. I’ve updated the ActiveMerchant fork on GitHub.

      Ray,

    20. Is the extension code somewhere, without a full fork of activemerchant?

    21. Hi Raymond,

      Please could you add the following onto build_create_profile_request and build_change_profile_request:

      xml.tag! ‘n2:MaxFailedPayments’, options[:max_failed_payments] unless options[:max_failed_payments].blank?

      In both cases it can be added directly above the line:

      xml.tag! ‘n2:AutoBillOutstandingAmount’, options[:auto_bill_outstanding] ? ‘AddToNextBilling’ : ‘NoAutoBill’

      Many thanks,

      @Alex N – You can find the class you are looking for in github under the following path:

      lib/active_merchant/billing/gateways/paypal_express_recurring.rb

      Jon

    22. Jon,

      I’ve just added that.

    23. Kennon Ballou

      Is anyone else having problems with Invalid Token from the sandbox today? I’m using the current ravinly github fork, and the initial setup is working fine but when I return from paypal with a token and then try to use the token, it keeps saying Invalid Token.

      I tried updating the API_VERSION to 54.0, but it still doesn’t work.

      Any suggestions?

    24. Kennon Ballou

      Also, is there a way to disable showing the shipping address with recurring billing? I tried using the NoShipping xml item like for regular express payments, but it still seems to be showing it. Anyone had any luck disabling this for express recurring?

    25. I really wish someone would create a sample app, it’s just too complicated and i feel like i am guessing my way around..

    26. [...] work with the latest ActiveMerchant. Raymond Law has kindly ported the code to use the SOAP API and you can find out more information and usage on his blog. [...]

    27. Just successfully created a subscription using direct payments by passing an instance of CreditCard in rather than a hash of details.

      I think also you can’t just use any old numbers for the credit card, etc as they have to pass various validations.

      If you take the credit card portion out of a standard ActiveMerchant payment tutorial you should be good.

      Cheers,

      Jon

    28. I had to add the following lines to Anthony’s code for my purposes (after the ‘AutoBillOutstandingAmount’ line):

      if !options[:initialamount].blank?
      xml.tag! 'n2:ActivationDetails' do
      xml.tag! 'n2:InitialAmount', amount(options[:initialamount]), 'currencyID' =&gt; options[:currency] || 'USD'
      end
      end

      Hope that this code will be formatted correctly…

    29. Got a sample app for this?

    30. Which PayPal account type does this work with? Express Checkout, Website Payments Stanard, Website Payments Pro, etc?

    31. Mark,

      You can use the code snippets in the post above to craft out a new rails sample app. And yes, it works with Express Checkout.

    32. I think the “end” 2 lines after “def complete” above is an error.

    33. Is there any work in progress to bring paypal_express_recurring.rb in the native shopify/activemerchant tree?

    34. @vtypal

      I haven’t talked to the original ActiveMerchant maintainer about it, but it seems like a good idea as both PayPal and ActiveMerchant are popular and we are seeing a lot of SaaS now. Feel free to raise it to them.

    35. Hi Raymond.

      I tested in my sandbox(development) the express checkout recurring payments using your fork from github, but i get the following UpdateBillingAgreement error from paypal after applying the gateway.create_profile method:

      #############################################
      10204 User’s account is closed or restricted
      #############################################

      With the same test seller and buyer i can complete one-off transcations

      Please could you help me with this?

      Best Regards,

    36. After some investigations i found out the origins of the error code is another one:

      10409
      You’re not authorized to access this info.
      Express Checkout token was issued for a merchant account other than yours.

      I use a sandbox seller w/ business merchant account.
      I cannot understand why this fails. A correction, the error returns from paypal in the first return from sandbox – so after the confirmation of the agreement and before the profile’s creation

      In my code detail_response.success? returns `false`

    37. details_response = gateway.details_for(request.params["token"])

    38. Hey,
      Could this work for Website payment standards?

    39. @vtypal

      I just came across the same issue with Error 10409. What appears to have happened was there was already a recurring profile setup for the email I was testing with.

      I fixed this by going through my sandbox account and just canceling all of the active recurring payment profiles.

      Hope that helps!

    40. @Shreya

      does it ? would like to know…

    41. Thanks a lot Raymond.
      It’s working like a charm!

    42. I installed github fork as a plugin from http://github.com/rayvinly/active_merchant/

      I have a sandbox account setup but it seems not working with the sandbox environment. I get the gateway response as:

      #”Security header is not valid”, “timestamp”=>”2010-07-20T08:31:26Z”, “correlation_id”=>”d0316c3eccb1d”, “token”=>nil, “build”=>”1399980″, “error_codes”=>”10002″, “version”=>”59.0″, “ack”=>”Failure”}, @message=”Security header is not valid”, @success=false, @cvv_result={“message”=>nil, “code”=>nil}, @fraud_review=false, @avs_result={“message”=>nil, “code”=>nil, “street_match”=>nil, “postal_match”=>nil}, @test=false>

      can someone please guide me what are the requirements in order to accept recurring billing as a merchant.

      1) Paypal account type (Standard, Pro)
      2) Does sandbox env. works for above?

      thank you very much.

    Leave a reply