After finishing the majority of the development on DrawbridgeApp.com, a project proposal creator and manager, I realized other freelancers and small companies were also running into the same issues with managing the proposals they needed to deliver to clients. I decided to deliver Drawbridge to the public at a low monthly rate, jumping on the "software as a service" train. The issue with running software in return for a monthly payment is having the ability to take and, more importantly, save credit card numbers. These obviously must be saved through a third-party merchant account. After researching many options, I decided to go with PayPal — which may or may not have been the best solution, but this is about implementing the solutions. At the time I was developing Drawbridge there was plenty of documentation written about implementing a shopping cart and collecting single payments with PayPal through various means (active merchant) but there wasn't anything written on integrating recurring payments with PayPal since PayPal had just launched their recurring payment option (active merchant did not have PayPal recurring payment integration at the time). After finding and dissecting the PayPal SDK for Ruby on Rails I decided to use the callers in the SDK and tie them into the recurring payments. Download and Install the PayPal SDK You can download the full PayPal SDK to get an idea of how the basic transaction process works with Ruby on Rails, but you really only need the PayPalSDK Plugin (I couldn't find any type of gem, so I have posted the files on my server.) Onc you have downloaded the plugin, you will see three files in the "lib" folder: caller.rb – which handles the request and transactions profile.rb – which handles authentication information and end points utils.rb – which includes logging utilities The only thing I changed in these files was to move the credentials and endpoints out of profile.rb and into initializers/globals.rb, this way I can control where the transactions go depending on the environment — development environment to the PayPal sandbox for testing and the production environment to my real PayPal account and live server. Update vendor/plugins/PayPalSDK/lib/profile.rb
# specify the 3-token values.
@@credentials = {"USER" => API_USER, "PWD" => API_PASS, "SIGNATURE" => API_SIG }
# endpoint of PayPal server against which call will be made.
@@endpoints = {"SERVER" => API_SERVER, "SERVICE" => "/nvp/"}
# Proxy information of the client environment.
@@proxy_info = {"USE_PROXY" => false, "ADDRESS" => nil, "PORT" => nil, "USER" => nil, "PASSWORD" => nil }
# Information needed for tracking purposes.
@@client_info = { "VERSION" => "50.0", "SOURCE" => "PayPalRubySDKV1.2.0"}Add this to config/initializers/globals.rb
# ==== PAYPAL INFORMATION ===== # Depending on environment, either send to PayPal sandbox or real account # ENVIRONMENT: DEVELOPMENT if ENV["RAILS_ENV"] == 'production' API_USER = "YOUR PAYPAL API USER NAME" API_PASS = "YOUR PAYPAL API PASSWORD" API_SIG = "YOUR PAYPAL API SIGNATURE" API_SERVER = "api-3t.paypal.com" # ENVIRONMENT: PRODUCTION else API_USER = "YOUR SANDBOX API USER NAME" API_PASS = "YOUR SANDBOX API PASSWORD" API_SIG = "YOUR SANDBOX API SIGNATURE" API_SERVER = "api-3t.beta-sandbox.paypal.com" end
CreateRecurringPaymentsProfile Now you can include caller.rb in your transactions controller (require 'caller') which will allow you to use the "CreateRecurringPaymentsProfile" method in the "call" method. There are many options that can be included in this hash that can be found here, but the only required items for creating a new recurring profile are (or see full PayPal API list):
- CREDITCARDTYPE
- ACCT (credit card number)
- EXPDATE
- FIRSTNAME
- LASTNAME
- PROFILESTARTDATE
- BILLINGPERIOD
- BILLINGFREQUENCY
- AMT
You can also include a trial period. I use this with Drawbridge to allow new users to have a 30 day free trial. This means they will not be charged the monthly price until after the first 30 days (see full list here
).
- TRIALBILLINGPERIOD
- TRIALBILLINGFREQUENCY
- TRIALAMT
- TRIALTOTALBILLINGCYCLES
There is much more information of what you can do on the PayPal Recurring Payments with PayPal Payments Pro page. Using the Call method Once you are familiar with what PayPal requires to create a recurring profile, using the call method in Ruby on Rails is pretty easy. Here is an example of how I use the call method and the variables I send. Obviously some of these items are sent from the form on DrawbridgeApp.com, but some are set in the hash.
def do_payment
@caller = PayPalSDKCallers::Caller.new(false)
@transaction = @caller.call(
{
:PROFILEREFERENCE => @account.id,
:method => 'CreateRecurringPaymentsProfile',
:amt => @price,
:currencycode => 'USD',
:paymentaction => "Sale",
:creditcardtype => @creditCardType,
:acct => @creditCardNumber,
:firstname => @first_name,
:lastname => @last_name,
:email => @email,
:zip => @zip,
:countrycode => 'US',
:expdate => @expDate,
:cvv2 => @cvv2Number,
:ProfileStartDate => Time.now.strftime('%Y-%m-%d %H:%M:%S'),
:BillingFrequency => 1,
:BillingPeriod => "Month",
:desc => "Drawbride Quote Manager",
:note => "A note about the transaction",
:TRIALBILLINGPERIOD => "Month",
:TRIALBILLINGFREQUENCY => "1",
:TRIALAMT => "0",
:TRIALTOTALBILLINGCYCLES => "1"
}
)
endYou can check whether the transaction was successful by doing
if @transaction.success? # Do whatever end
If everything is successful, save the returned PayPal recurring profile id to be used when referencing the account's PayPal transaction history. If there is an error, PayPal returns the reason in it's response. You can save and grab this message in a session by doing this:
# Get response from PayPal session[:paypal_error] = @transaction.response @response = session[:paypal_error] @longmessage = @response["L_LONGMESSAGE0"] # Send errors flash.now[:warning] = @response["L_LONGMESSAGE0"] render :action => "index"
Change Credit Card Numbers What happens when a user needs to update their credit card number? Since PayPal doesn't allow a recurring payment profile to change it's credit card number, a new profile must be created for that account. This is quite unpleasant, but understandable. Here is the process I decided was the safest and most efficient. Suspend the Current PayPal Profile First suspend the current PayPal profile by using "ManageRecurringPaymentsProfileStatus" to have an action of "Suspend"
:
@caller = PayPalSDKCallers::Caller.new(false)
@suspend = @caller.call(
{
:method => 'ManageRecurringPaymentsProfileStatus',
:profileid => @account.paypal_id,
:action => 'Suspend',
:note => 'A note about what you are doing'
}
)Create a New PayPal Profile Then create a new PayPal profile for the user, using their new credit card information. It is VERY important that you update the PayPal profile id in your database and also start the new recurring date the same as the suspended account, otherwise you will charge the user again. First check that the account was suspended and then create a new PayPal profile by doing:
if @suspend.success?
# NOW WE CREATE A NEW RECURRING PAYMENT PROFILE
# *NOTE: USE THE CURRENT BILLING DATE AS TO NOT BILL MORE THAN ONCE IN THE MONTH
@caller = PayPalSDKCallers::Caller.new(false)
@transaction = @caller.call(
{
:PROFILEREFERENCE => @account.id,
:method => 'CreateRecurringPaymentsProfile',
:amt => @price,
:currencycode => 'USD',
:paymentaction => "Sale",
:creditcardtype => @creditCardType,
:acct => @creditCardNumber,
:firstname => @first_name,
:lastname => @first_name,
:email => @email,
:zip => @zip,
:countrycode => 'US',
:expdate => @expDateYear,
:cvv2 => @cvv2Number,
:ProfileStartDate => @account.nextPayPalBillingDate.strftime('%Y-%m-%d %H:%M:%S'),
:BillingFrequency => 1,
:BillingPeriod => "Month",
:desc => "Drawbride Quote Manager",
:note => "UPDATED CREDIT CARD"
}
)
endTo do the above ":ProfileStartDate", you will need to get the next billing date. I created a method in models/account.rb (where I track my PayPal profile):
def nextPayPalBillingDate
# The PayPal next billing date is based on when the account was created
# It assumes that this was when the credit card was charged for the first time
curdate = Time.now
unless paypal_created_at.nil?
accountdate = self.paypal_created_at
new_start_date = Time.parse("#{curdate.strftime('%Yspan<span class="punctuation punctuation_definition punctuation_definition_string punctuation_definition_string_end punct
