didil-paypal-recurring 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +46 -0
- data/README.rdoc +144 -0
- data/Rakefile +5 -0
- data/docs/PP_NVPAPI_DeveloperGuide.pdf +0 -0
- data/docs/PP_WPP_IntegrationGuide.pdf +0 -0
- data/lib/paypal-recurring.rb +1 -0
- data/lib/paypal/recurring.rb +110 -0
- data/lib/paypal/recurring/base.rb +286 -0
- data/lib/paypal/recurring/cacert.pem +3987 -0
- data/lib/paypal/recurring/notification.rb +84 -0
- data/lib/paypal/recurring/request.rb +211 -0
- data/lib/paypal/recurring/response.rb +29 -0
- data/lib/paypal/recurring/response/base.rb +62 -0
- data/lib/paypal/recurring/response/checkout.rb +11 -0
- data/lib/paypal/recurring/response/details.rb +26 -0
- data/lib/paypal/recurring/response/manage_profile.rb +12 -0
- data/lib/paypal/recurring/response/payment.rb +24 -0
- data/lib/paypal/recurring/response/profile.rb +71 -0
- data/lib/paypal/recurring/response/refund.rb +23 -0
- data/lib/paypal/recurring/utils.rb +26 -0
- data/lib/paypal/recurring/version.rb +10 -0
- data/paypal-recurring.gemspec +27 -0
- data/spec/fixtures/checkout/failure.yml +38 -0
- data/spec/fixtures/checkout/success.yml +38 -0
- data/spec/fixtures/create_profile/failure.yml +38 -0
- data/spec/fixtures/create_profile/success.yml +38 -0
- data/spec/fixtures/details/cancelled.yml +38 -0
- data/spec/fixtures/details/failure.yml +38 -0
- data/spec/fixtures/details/success.yml +38 -0
- data/spec/fixtures/ipn/express_checkout.json +35 -0
- data/spec/fixtures/ipn/recurring_payment.json +44 -0
- data/spec/fixtures/ipn/recurring_payment_profile_created.json +31 -0
- data/spec/fixtures/ipn/recurring_payment_profile_created_with_initial_amount.json +33 -0
- data/spec/fixtures/ipn/recurring_payment_skipped.json +30 -0
- data/spec/fixtures/ipn/recurring_payment_with_initial_amount.json +44 -0
- data/spec/fixtures/notification/failure.yml +50 -0
- data/spec/fixtures/notification/success.yml +50 -0
- data/spec/fixtures/payment/failure.yml +38 -0
- data/spec/fixtures/payment/success.yml +38 -0
- data/spec/fixtures/profile/cancel/failure.yml +38 -0
- data/spec/fixtures/profile/cancel/success.yml +38 -0
- data/spec/fixtures/profile/failure.yml +38 -0
- data/spec/fixtures/profile/reactivate/failure.yml +38 -0
- data/spec/fixtures/profile/reactivate/success.yml +38 -0
- data/spec/fixtures/profile/success.yml +38 -0
- data/spec/fixtures/profile/suspend/failure.yml +38 -0
- data/spec/fixtures/profile/suspend/success.yml +38 -0
- data/spec/fixtures/refund/failure.yml +38 -0
- data/spec/fixtures/refund/success.yml +38 -0
- data/spec/fixtures/update_profile/failure.yml +38 -0
- data/spec/fixtures/update_profile/profile.yml +38 -0
- data/spec/fixtures/update_profile/success.yml +38 -0
- data/spec/paypal/notification_spec.rb +106 -0
- data/spec/paypal/recurring_spec.rb +81 -0
- data/spec/paypal/request_spec.rb +136 -0
- data/spec/paypal/response/base_spec.rb +19 -0
- data/spec/paypal/response/checkout_details_spec.rb +50 -0
- data/spec/paypal/response/checkout_spec.rb +32 -0
- data/spec/paypal/response/create_recurring_profile_spec.rb +41 -0
- data/spec/paypal/response/manage_profile_spec.rb +62 -0
- data/spec/paypal/response/profile_spec.rb +43 -0
- data/spec/paypal/response/refund_spec.rb +33 -0
- data/spec/paypal/response/request_payment_spec.rb +35 -0
- data/spec/paypal/response/update_recurring_profile_spec.rb +48 -0
- data/spec/spec_helper.rb +27 -0
- metadata +209 -0
@@ -0,0 +1,84 @@
|
|
1
|
+
module PayPal
|
2
|
+
module Recurring
|
3
|
+
class Notification
|
4
|
+
extend PayPal::Recurring::Utils
|
5
|
+
|
6
|
+
attr_reader :params
|
7
|
+
|
8
|
+
mapping({
|
9
|
+
:type => :txn_type,
|
10
|
+
:transaction_id => :txn_id,
|
11
|
+
:fee => [:mc_fee, :payment_fee],
|
12
|
+
:reference => [:rp_invoice_id, :custom, :invoice],
|
13
|
+
:payment_id => :recurring_payment_id,
|
14
|
+
:amount => [:amount, :mc_gross, :payment_gross],
|
15
|
+
:currency => :mc_currency,
|
16
|
+
:status => :payment_status,
|
17
|
+
:pending_reason => :pending_reason,
|
18
|
+
:profile_status => :profile_status,
|
19
|
+
:payment_date => :payment_date,
|
20
|
+
:time_created => :time_created,
|
21
|
+
:seller_id => :receiver_id,
|
22
|
+
:email => :receiver_email,
|
23
|
+
:initial_amount => :initial_payment_amount,
|
24
|
+
:payer_email => :payer_email
|
25
|
+
})
|
26
|
+
|
27
|
+
def initialize(params = {})
|
28
|
+
self.params = params
|
29
|
+
end
|
30
|
+
|
31
|
+
def params=(params)
|
32
|
+
@params = params.inject({}) do |buffer, (name,value)|
|
33
|
+
buffer.merge(name.to_sym => value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def express_checkout?
|
38
|
+
type == "express_checkout"
|
39
|
+
end
|
40
|
+
|
41
|
+
def recurring_payment?
|
42
|
+
type == "recurring_payment"
|
43
|
+
end
|
44
|
+
|
45
|
+
def recurring_payment_profile?
|
46
|
+
type == "recurring_payment_profile_created"
|
47
|
+
end
|
48
|
+
|
49
|
+
def request
|
50
|
+
@request ||= PayPal::Recurring::Request.new.tap do |request|
|
51
|
+
request.uri = URI.parse("#{PayPal::Recurring.site_endpoint}?cmd=_notify-validate")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def response
|
56
|
+
@response ||= request.post(params.merge(:cmd => "_notify-validate"))
|
57
|
+
end
|
58
|
+
|
59
|
+
def valid?
|
60
|
+
completed? && verified? && email == PayPal::Recurring.email && seller_id == PayPal::Recurring.seller_id
|
61
|
+
end
|
62
|
+
|
63
|
+
def completed?
|
64
|
+
status == "Completed"
|
65
|
+
end
|
66
|
+
|
67
|
+
def next_payment_date
|
68
|
+
self.class.convert_to_time(params[:next_payment_date]) if params[:next_payment_date]
|
69
|
+
end
|
70
|
+
|
71
|
+
def paid_at
|
72
|
+
self.class.convert_to_time(payment_date) if payment_date
|
73
|
+
end
|
74
|
+
|
75
|
+
def created_at
|
76
|
+
self.class.convert_to_time(time_created) if time_created
|
77
|
+
end
|
78
|
+
|
79
|
+
def verified?
|
80
|
+
response.body == "VERIFIED"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
module PayPal
|
2
|
+
module Recurring
|
3
|
+
class Request
|
4
|
+
METHODS = {
|
5
|
+
:checkout => "SetExpressCheckout",
|
6
|
+
:payment => "DoExpressCheckoutPayment",
|
7
|
+
:details => "GetExpressCheckoutDetails",
|
8
|
+
:create_profile => "CreateRecurringPaymentsProfile",
|
9
|
+
:profile => "GetRecurringPaymentsProfileDetails",
|
10
|
+
:manage_profile => "ManageRecurringPaymentsProfileStatus",
|
11
|
+
:update_profile => "UpdateRecurringPaymentsProfile",
|
12
|
+
:refund => "RefundTransaction"
|
13
|
+
}
|
14
|
+
|
15
|
+
INITIAL_AMOUNT_ACTIONS = {
|
16
|
+
:cancel => "CancelOnFailure",
|
17
|
+
:continue => "ContinueOnFailure"
|
18
|
+
}
|
19
|
+
|
20
|
+
ACTIONS = {
|
21
|
+
:cancel => "Cancel",
|
22
|
+
:suspend => "Suspend",
|
23
|
+
:reactivate => "Reactivate"
|
24
|
+
}
|
25
|
+
|
26
|
+
PERIOD = {
|
27
|
+
:daily => "Day",
|
28
|
+
:weekly => "Weekly",
|
29
|
+
:monthly => "Month",
|
30
|
+
:yearly => "Year"
|
31
|
+
}
|
32
|
+
|
33
|
+
TRIAL_PERIOD = {
|
34
|
+
:daily => "Day",
|
35
|
+
:weekly => "Weekly",
|
36
|
+
:monthly => "Month",
|
37
|
+
:yearly => "Year"
|
38
|
+
}
|
39
|
+
|
40
|
+
OUTSTANDING = {
|
41
|
+
:next_billing => "AddToNextBilling",
|
42
|
+
:no_auto => "NoAutoBill"
|
43
|
+
}
|
44
|
+
|
45
|
+
REFUND_TYPE = {
|
46
|
+
:full => "Full",
|
47
|
+
:partial => "Partial",
|
48
|
+
:external => "ExternalDispute",
|
49
|
+
:other => "Other"
|
50
|
+
}
|
51
|
+
|
52
|
+
ATTRIBUTES = {
|
53
|
+
:action => "ACTION",
|
54
|
+
:amount => ["PAYMENTREQUEST_0_AMT", "AMT"],
|
55
|
+
:billing_type => "L_BILLINGTYPE0",
|
56
|
+
:cancel_url => "CANCELURL",
|
57
|
+
:currency => ["PAYMENTREQUEST_0_CURRENCYCODE", "CURRENCYCODE"],
|
58
|
+
:description => ["DESC", "PAYMENTREQUEST_0_DESC", "L_BILLINGAGREEMENTDESCRIPTION0"],
|
59
|
+
:note => "NOTE",
|
60
|
+
:item_category => "L_PAYMENTREQUEST_0_ITEMCATEGORY0",
|
61
|
+
:item_name => "L_PAYMENTREQUEST_0_NAME0",
|
62
|
+
:item_amount => "L_PAYMENTREQUEST_0_AMT0",
|
63
|
+
:item_quantity => "L_PAYMENTREQUEST_0_QTY0",
|
64
|
+
:email => "EMAIL",
|
65
|
+
:failed => "MAXFAILEDPAYMENTS",
|
66
|
+
:frequency => "BILLINGFREQUENCY",
|
67
|
+
:initial_amount => "INITAMT",
|
68
|
+
:initial_amount_action => "FAILEDINITAMTACTION",
|
69
|
+
:ipn_url => ["PAYMENTREQUEST_0_NOTIFYURL", "NOTIFYURL"],
|
70
|
+
:locale => "LOCALECODE",
|
71
|
+
:method => "METHOD",
|
72
|
+
:no_shipping => "NOSHIPPING",
|
73
|
+
:outstanding => "AUTOBILLOUTAMT",
|
74
|
+
:password => "PWD",
|
75
|
+
:payer_id => "PAYERID",
|
76
|
+
:payment_action => "PAYMENTREQUEST_0_PAYMENTACTION",
|
77
|
+
:period => "BILLINGPERIOD",
|
78
|
+
:profile_id => "PROFILEID",
|
79
|
+
:reference => ["PROFILEREFERENCE", "PAYMENTREQUEST_0_CUSTOM", "PAYMENTREQUEST_0_INVNUM"],
|
80
|
+
:refund_type => "REFUNDTYPE",
|
81
|
+
:return_url => "RETURNURL",
|
82
|
+
:signature => "SIGNATURE",
|
83
|
+
:start_at => "PROFILESTARTDATE",
|
84
|
+
:token => "TOKEN",
|
85
|
+
:transaction_id => "TRANSACTIONID",
|
86
|
+
:trial_amount => "TRIALAMT",
|
87
|
+
:trial_frequency => "TRIALBILLINGFREQUENCY",
|
88
|
+
:trial_length => "TRIALTOTALBILLINGCYCLES",
|
89
|
+
:trial_period => "TRIALBILLINGPERIOD",
|
90
|
+
:username => "USER",
|
91
|
+
:version => "VERSION",
|
92
|
+
:brand_name => "BRANDNAME",
|
93
|
+
:page_style => "PAGESTYLE",
|
94
|
+
}
|
95
|
+
|
96
|
+
CA_FILE = File.dirname(__FILE__) + "/cacert.pem"
|
97
|
+
|
98
|
+
attr_accessor :uri
|
99
|
+
|
100
|
+
# Do a POST request to PayPal API.
|
101
|
+
# The +method+ argument is the name of the API method you want to invoke.
|
102
|
+
# For instance, if you want to request a new checkout token, you may want
|
103
|
+
# to do something like:
|
104
|
+
#
|
105
|
+
# response = request.run(:express_checkout)
|
106
|
+
#
|
107
|
+
# We normalize the methods name. For a list of what's being covered, refer to
|
108
|
+
# PayPal::Recurring::Request::METHODS constant.
|
109
|
+
#
|
110
|
+
# The params hash can use normalized names. For a list, check the
|
111
|
+
# PayPal::Recurring::Request::ATTRIBUTES constant.
|
112
|
+
#
|
113
|
+
def run(method, params = {})
|
114
|
+
params = prepare_params(params.merge(:method => METHODS.fetch(method, method.to_s)))
|
115
|
+
response = post(params)
|
116
|
+
Response.process(method, response)
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
#
|
121
|
+
def request
|
122
|
+
@request ||= Net::HTTP::Post.new(uri.request_uri).tap do |http|
|
123
|
+
http["User-Agent"] = "PayPal::Recurring/#{PayPal::Recurring::Version::STRING}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
#
|
129
|
+
def post(params = {})
|
130
|
+
request.form_data = params
|
131
|
+
client.request(request)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Join params and normalize attribute names.
|
135
|
+
#
|
136
|
+
def prepare_params(params) # :nodoc:
|
137
|
+
normalize_params default_params.merge(params)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Parse current API url.
|
141
|
+
#
|
142
|
+
def uri # :nodoc:
|
143
|
+
@uri ||= URI.parse(PayPal::Recurring.api_endpoint)
|
144
|
+
end
|
145
|
+
|
146
|
+
def client
|
147
|
+
@client ||= begin
|
148
|
+
Net::HTTP.new(uri.host, uri.port).tap do |http|
|
149
|
+
http.use_ssl = true
|
150
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
151
|
+
http.ca_file = CA_FILE
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def default_params
|
157
|
+
{
|
158
|
+
:username => PayPal::Recurring.username,
|
159
|
+
:password => PayPal::Recurring.password,
|
160
|
+
:signature => PayPal::Recurring.signature,
|
161
|
+
:version => PayPal::Recurring.api_version
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
def normalize_params(params)
|
166
|
+
params.inject({}) do |buffer, (name, value)|
|
167
|
+
attr_names = [ATTRIBUTES[name.to_sym]].flatten.compact
|
168
|
+
attr_names << name if attr_names.empty?
|
169
|
+
|
170
|
+
attr_names.each do |attr_name|
|
171
|
+
buffer[attr_name.to_sym] = respond_to?("build_#{name}") ? send("build_#{name}", value) : value
|
172
|
+
end
|
173
|
+
|
174
|
+
buffer
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def build_period(value) # :nodoc:
|
179
|
+
PERIOD.fetch(value.to_sym, value) if value
|
180
|
+
end
|
181
|
+
|
182
|
+
def build_trial_period(value)
|
183
|
+
TRIAL_PERIOD.fetch(value.to_sym, value) if value
|
184
|
+
end
|
185
|
+
|
186
|
+
def build_start_at(value) # :nodoc:
|
187
|
+
value.respond_to?(:strftime) ? value.strftime("%Y-%m-%dT%H:%M:%SZ") : value
|
188
|
+
end
|
189
|
+
|
190
|
+
def build_outstanding(value) # :nodoc:
|
191
|
+
OUTSTANDING.fetch(value.to_sym, value) if value
|
192
|
+
end
|
193
|
+
|
194
|
+
def build_refund_type(value) # :nodoc:
|
195
|
+
REFUND_TYPE.fetch(value.to_sym, value) if value
|
196
|
+
end
|
197
|
+
|
198
|
+
def build_action(value) # :nodoc:
|
199
|
+
ACTIONS.fetch(value.to_sym, value) if value
|
200
|
+
end
|
201
|
+
|
202
|
+
def build_initial_amount_action(value) # :nodoc:
|
203
|
+
INITIAL_AMOUNT_ACTIONS.fetch(value.to_sym, value) if value
|
204
|
+
end
|
205
|
+
|
206
|
+
def build_locale(value) # :nodoc:
|
207
|
+
value.to_s.upcase
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module PayPal
|
2
|
+
module Recurring
|
3
|
+
module Response
|
4
|
+
autoload :Base, "paypal/recurring/response/base"
|
5
|
+
autoload :Checkout, "paypal/recurring/response/checkout"
|
6
|
+
autoload :Details, "paypal/recurring/response/details"
|
7
|
+
autoload :Payment, "paypal/recurring/response/payment"
|
8
|
+
autoload :ManageProfile, "paypal/recurring/response/manage_profile"
|
9
|
+
autoload :Profile, "paypal/recurring/response/profile"
|
10
|
+
autoload :Refund, "paypal/recurring/response/refund"
|
11
|
+
|
12
|
+
RESPONDERS = {
|
13
|
+
:checkout => "Checkout",
|
14
|
+
:details => "Details",
|
15
|
+
:payment => "Payment",
|
16
|
+
:profile => "Profile",
|
17
|
+
:create_profile => "ManageProfile",
|
18
|
+
:manage_profile => "ManageProfile",
|
19
|
+
:update_profile => "ManageProfile",
|
20
|
+
:refund => "Refund"
|
21
|
+
}
|
22
|
+
|
23
|
+
def self.process(method, response)
|
24
|
+
response_class = PayPal::Recurring::Response.const_get(RESPONDERS[method])
|
25
|
+
response_class.new(response)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module PayPal
|
2
|
+
module Recurring
|
3
|
+
module Response
|
4
|
+
class Base
|
5
|
+
extend PayPal::Recurring::Utils
|
6
|
+
|
7
|
+
attr_accessor :response
|
8
|
+
|
9
|
+
mapping(
|
10
|
+
:token => :TOKEN,
|
11
|
+
:ack => :ACK,
|
12
|
+
:version => :VERSION,
|
13
|
+
:build => :BUILD,
|
14
|
+
:correlaction_id => :CORRELATIONID,
|
15
|
+
:requested_at => :TIMESTAMP
|
16
|
+
)
|
17
|
+
|
18
|
+
def initialize(response = nil)
|
19
|
+
@response = response
|
20
|
+
end
|
21
|
+
|
22
|
+
def params
|
23
|
+
@params ||= CGI.parse(response.body).inject({}) do |buffer, (name, value)|
|
24
|
+
buffer.merge(name.to_sym => value.first)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def errors
|
29
|
+
@errors ||= begin
|
30
|
+
index = 0
|
31
|
+
[].tap do |errors|
|
32
|
+
while params[:"L_ERRORCODE#{index}"]
|
33
|
+
errors << {
|
34
|
+
:code => params[:"L_ERRORCODE#{index}"],
|
35
|
+
:messages => [
|
36
|
+
params[:"L_SHORTMESSAGE#{index}"],
|
37
|
+
params[:"L_LONGMESSAGE#{index}"]
|
38
|
+
]
|
39
|
+
}
|
40
|
+
|
41
|
+
index += 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def success?
|
48
|
+
ack == "Success"
|
49
|
+
end
|
50
|
+
|
51
|
+
def valid?
|
52
|
+
errors.empty? && success?
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def build_requested_at(stamp) # :nodoc:
|
57
|
+
Time.parse(stamp)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module PayPal
|
2
|
+
module Recurring
|
3
|
+
module Response
|
4
|
+
class Details < Base
|
5
|
+
mapping(
|
6
|
+
:status => :CHECKOUTSTATUS,
|
7
|
+
:email => :EMAIL,
|
8
|
+
:email => :EMAIL,
|
9
|
+
:payer_id => :PAYERID,
|
10
|
+
:payer_status => :PAYERSTATUS,
|
11
|
+
:first_name => :FIRSTNAME,
|
12
|
+
:last_name => :LASTNAME,
|
13
|
+
:country => :COUNTRYCODE,
|
14
|
+
:currency => :CURRENCYCODE,
|
15
|
+
:amount => :AMT,
|
16
|
+
:description => :DESC,
|
17
|
+
:ipn_url => :NOTIFYURL
|
18
|
+
)
|
19
|
+
|
20
|
+
def agreed?
|
21
|
+
params[:BILLINGAGREEMENTACCEPTEDSTATUS] == "1"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module PayPal
|
2
|
+
module Recurring
|
3
|
+
module Response
|
4
|
+
class Payment < Base
|
5
|
+
mapping(
|
6
|
+
:status => :PAYMENTINFO_0_PAYMENTSTATUS,
|
7
|
+
:amount => :PAYMENTINFO_0_AMT,
|
8
|
+
:fees => :PAYMENTINFO_0_FEEAMT,
|
9
|
+
:transaction_id => :PAYMENTINFO_0_TRANSACTIONID,
|
10
|
+
:seller_id => :PAYMENTINFO_0_SECUREMERCHANTACCOUNTID,
|
11
|
+
:reference => [:PROFILEREFERENCE, :PAYMENTREQUEST_0_CUSTOM, :PAYMENTREQUEST_0_INVNUM]
|
12
|
+
)
|
13
|
+
|
14
|
+
def completed?
|
15
|
+
params[:PAYMENTINFO_0_PAYMENTSTATUS] == "Completed"
|
16
|
+
end
|
17
|
+
|
18
|
+
def approved?
|
19
|
+
params[:PAYMENTINFO_0_ACK] == "Success"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|