Paypal Express Recurring Payments

Posted by obondar
on Thursday, July 03

This is the extension for activemerchant to work with recurring payments:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
include ActiveMerchant::Billing

# simple extension to ActiveMerchant for basic support of recurring payments with Express Checkout API

module ActiveMerchant #:nodoc:

  module Billing #:nodoc:

    class PaypalExpressRecurringGateway < Gateway

      include PaypalCommonAPI
      LIVE_REDIRECT_URL = 'https://www.paypal.com/cgibin/webscr?cmd=_customer-billing-agreement&token='
      TEST_REDIRECT_URL = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_customer-billing-agreement&token='
      def redirect_url
        test? ? TEST_REDIRECT_URL : LIVE_REDIRECT_URL
      end
      def redirect_url_for(token)
        "#{redirect_url}#{token}"
      end

      def setup_agreement(description, return_url, cancel_url)
        commit 'SetCustomerBillingAgreement', build_setup_request(description, return_url, cancel_url)
      end

      def create_profile(token, description, cycles, amount, next_billing_date)
        commit 'CreateRecurringPaymentsProfile', build_create_profile_request(token, description, cycles, amount, next_billing_date)
      end
  
      def update_profile(profile_id, description, amount)
        commit 'UpdateRecurringPaymentsProfile', build_change_profile_request(profile_id, description, amount)
      end

      def cancel_profile(profile_id)
        commit 'ManageRecurringPaymentsProfileStatus', manage_profile_request(profile_id, 'Cancel')
      end

      def get_profile_details(profile_id)
        commit 'GetRecurringPaymentsProfileDetails', build_get_profile_details_request(profile_id)
      end

    private
      def build_setup_request(description, return_url, cancel_url)
        xml = Builder::XmlMarkup.new :indent => 2
        xml.tag! 'SetCustomerBillingAgreementReq', 'xmlns' => PAYPAL_NAMESPACE do
          xml.tag! 'SetCustomerBillingAgreementRequest', 'xmlns:n2' => EBAY_NAMESPACE do
            xml.tag! 'n2:Version', 50
            xml.tag! 'n2:SetCustomerBillingAgreementRequestDetails' do
              xml.tag! 'n2:BillingAgreementDetails' do
                xml.tag! 'n2:BillingType', 'RecurringPayments'
                xml.tag! 'n2:BillingAgreementDescription', description
              end
              xml.tag! 'n2:ReturnURL', return_url
              xml.tag! 'n2:CancelURL', cancel_url
            end
          end
        end
        xml.target!
      end

      def build_create_profile_request(token, description, cycles, amount, billing_start_date, currency='USD')
        xml = Builder::XmlMarkup.new :indent => 2
        xml.tag! 'CreateRecurringPaymentsProfileReq', 'xmlns' => PAYPAL_NAMESPACE do
          xml.tag! 'CreateRecurringPaymentsProfileRequest', 'xmlns:n2' => EBAY_NAMESPACE do
            xml.tag! 'n2:Version', 50
            xml.tag! 'n2:CreateRecurringPaymentsProfileRequestDetails' do
              xml.tag! 'Token', token
              xml.tag! 'n2:RecurringPaymentsProfileDetails' do
                xml.tag! 'n2:BillingStartDate', billing_start_date
              end
              xml.tag! 'n2:ScheduleDetails' do
                xml.tag! 'n2:Description', description
                xml.tag! 'n2:PaymentPeriod' do
                  xml.tag! 'n2:BillingPeriod', 'Month'
                  xml.tag! 'n2:BillingFrequency', cycles
                  xml.tag! 'n2:TotalBillingCycles', 0
                  xml.tag! 'n2:Amount', amount, 'currencyID' => currency
                end
              end
            end
          end
        end
        xml.target!
      end

      def build_change_profile_request(profile_id, description, amount)
        xml = Builder::XmlMarkup.new :indent => 2
        xml.tag! 'UpdateRecurringPaymentsProfileReq', 'xmlns' => PAYPAL_NAMESPACE do
          xml.tag! 'UpdateRecurringPaymentsProfileRequest', 'xmlns:n2' => EBAY_NAMESPACE do
            xml.tag! 'n2:Version', 50
            xml.tag! 'n2:UpdateRecurringPaymentsProfileRequestDetails' do
              xml.tag! 'ProfileID', profile_id
              xml.tag! 'n2:ScheduleDetails' do
                xml.tag! 'n2:Description', description
                xml.tag! 'n2:PaymentPeriod' do
                  xml.tag! 'n2:Amount', amount
                end
              end
            end
          end
        end
        xml.target!
      end
      
      def manage_profile_request(profile_id, action)
        xml = Builder::XmlMarkup.new :indent => 2
        xml.tag! 'ManageRecurringPaymentsProfileStatusReq', 'xmlns' => PAYPAL_NAMESPACE do
          xml.tag! 'ManageRecurringPaymentsProfileStatusRequest', 'xmlns:n2' => EBAY_NAMESPACE do
            xml.tag! 'n2:Version', 50
            xml.tag! 'n2:ManageRecurringPaymentsProfileStatusRequestDetails' do
              xml.tag! 'ProfileID', profile_id
              xml.tag! 'n2:Action', action
            end
          end
        end
        xml.target!
      end

      def build_get_profile_details_request(profile_id)
        xml = Builder::XmlMarkup.new :indent => 2
        xml.tag! 'GetRecurringPaymentsProfileDetailsReq', 'xmlns' => PAYPAL_NAMESPACE do
          xml.tag! 'GetRecurringPaymentsProfileDetailsRequest', 'xmlns:n2' => EBAY_NAMESPACE do
            xml.tag! 'n2:Version', 50
            xml.tag! 'ProfileID', profile_id
          end
        end
        xml.target!
      end

      def build_response(success, message, response, options = {})
        PaypalExpressProfileResponse.new(success, message, response, options)
      end
   end

    class PaypalExpressProfileResponse < PaypalExpressResponse
      def profile_id
        @params['profile_id']
      end

      def next_billing_date
        @params['next_billing_date']
      end

      def active?
         @params["profile_status"]=="ActiveProfile"
      end
    end
  end
end
Comments

Leave a response

  1. Chris CeraJuly 07, 2008 @ 07:23 PM
    Thank you for posting this, and wish I had found this earlier. I'm not using PaypalExpress, but parts of this are still useful for Website Payments Pro (US). I hope to submit my results back to ActiveMerchant when I'm done, but if I don't have time I will just post the code as you have done. Thanks again.