pay_simple 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +35 -0
- data/LICENSE.txt +22 -0
- data/README.md +100 -0
- data/Rakefile +1 -0
- data/lib/ps.rb +32 -0
- data/lib/ps/api.rb +80 -0
- data/lib/ps/api/json.rb +78 -0
- data/lib/ps/base.rb +22 -0
- data/lib/ps/enumerations.rb +191 -0
- data/lib/ps/exceptions.rb +5 -0
- data/lib/ps/object.rb +48 -0
- data/lib/ps/objects/ach_account.rb +34 -0
- data/lib/ps/objects/credit_card_account.rb +38 -0
- data/lib/ps/objects/customer.rb +79 -0
- data/lib/ps/objects/customer_account.rb +25 -0
- data/lib/ps/objects/payment.rb +73 -0
- data/lib/ps/objects/payment_status_filter.rb +6 -0
- data/lib/ps/objects/recurring_payment.rb +60 -0
- data/lib/ps/objects/recurring_payment_filter.rb +26 -0
- data/lib/ps/objects/user.rb +10 -0
- data/lib/ps/response.rb +83 -0
- data/lib/ps/util.rb +28 -0
- data/lib/ps/util/hash.rb +17 -0
- data/lib/ps/util/state.rb +15 -0
- data/lib/ps/util/states.yml +263 -0
- data/lib/ps/util/string.rb +15 -0
- data/paysimple.gemspec +19 -0
- data/spec/config.yml.example +13 -0
- data/spec/factories/ach_account.rb +11 -0
- data/spec/factories/credit_card_accounts.rb +11 -0
- data/spec/factories/customer_accounts.rb +7 -0
- data/spec/factories/customers.rb +18 -0
- data/spec/factories/payment.rb +12 -0
- data/spec/factories/recurring_payment.rb +25 -0
- data/spec/ps/api/json_spec.rb +12 -0
- data/spec/ps/api_spec.rb +53 -0
- data/spec/ps/base_spec.rb +40 -0
- data/spec/ps/format_spec.rb +16 -0
- data/spec/ps/object_spec.rb +35 -0
- data/spec/ps/objects/ach_account_spec.rb +57 -0
- data/spec/ps/objects/credit_card_account_spec.rb +69 -0
- data/spec/ps/objects/customer_account_spec.rb +43 -0
- data/spec/ps/objects/customer_spec.rb +153 -0
- data/spec/ps/objects/payment_spec.rb +98 -0
- data/spec/ps/objects/recurring_payment_spec.rb +145 -0
- data/spec/ps/objects/user_spec.rb +14 -0
- data/spec/ps/response_spec.rb +39 -0
- data/spec/ps/util/hash_spec.rb +33 -0
- data/spec/ps/util/states_spec.rb +25 -0
- data/spec/ps/util/string_spec.rb +21 -0
- data/spec/ps/util_spec.rb +30 -0
- data/spec/spec_functions.rb +155 -0
- data/spec/spec_helper.rb +22 -0
- metadata +156 -0
data/lib/ps/object.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module PS
|
2
|
+
class Object < Base
|
3
|
+
attr_accessor :ps_reference_id
|
4
|
+
|
5
|
+
def initialize(params = {})
|
6
|
+
set_attributes(params)
|
7
|
+
end
|
8
|
+
|
9
|
+
def attributes
|
10
|
+
attributes_hash = {}
|
11
|
+
self.instance_variables.each do |v|
|
12
|
+
attributes_hash[v[1..-1].to_sym] = self.send(v[1..-1])
|
13
|
+
end
|
14
|
+
attributes_hash
|
15
|
+
end
|
16
|
+
|
17
|
+
def set_attributes(params={})
|
18
|
+
params.each do |k, v|
|
19
|
+
next unless self.class.method_defined?(k)
|
20
|
+
instance_variable_set("@#{k}", v)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
attrs = self.attributes.to_a.map { |k_v|
|
26
|
+
case k_v[1]
|
27
|
+
when Fixnum
|
28
|
+
"#{k_v[0]}: #{k_v[1]}"
|
29
|
+
when String || Time
|
30
|
+
"#{k_v[0]}: '#{k_v[1]}'" unless k_v[1].empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
}.delete_if(&:nil?).join(", ")
|
34
|
+
"#<#{self.class} #{attrs}>"
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
#this block will fail if more than one PS::Object is returned.
|
39
|
+
def update_self
|
40
|
+
Proc.new { |response| set_attributes(response.ps_object) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.instantiate_object
|
44
|
+
Proc.new { |response| PS::Util.instantiate_ps_objects(response.ps_object) }
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module PS
|
2
|
+
class AchAccount < CustomerAccount
|
3
|
+
attr_accessor :is_checking_account, :routing_number, :account_number, :bank_name
|
4
|
+
|
5
|
+
def self.create(params={})
|
6
|
+
aa = self.new(params)
|
7
|
+
aa.save
|
8
|
+
return aa
|
9
|
+
end
|
10
|
+
|
11
|
+
def save
|
12
|
+
begin
|
13
|
+
save!()
|
14
|
+
true
|
15
|
+
rescue Exception
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def save!
|
21
|
+
request("addcustomerachaccount", { :customerAccount => attributes }, &update_self)
|
22
|
+
end
|
23
|
+
|
24
|
+
def update
|
25
|
+
request("updatecustomerachaccount", { :customerAccount => attributes })
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete
|
30
|
+
request("deletecustomerachaccount", { :accountId => self.ps_reference_id })
|
31
|
+
true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module PS
|
2
|
+
class CreditCardAccount < CustomerAccount
|
3
|
+
attr_accessor :c_c_expiry, :c_c_type, :account_number
|
4
|
+
|
5
|
+
def save
|
6
|
+
begin
|
7
|
+
save!()
|
8
|
+
true
|
9
|
+
rescue Exception
|
10
|
+
false
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def save!
|
15
|
+
request("addcustomercreditcardaccount", { :customerAccount => attributes }, &update_self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def update
|
19
|
+
request("updatecustomercreditcardaccount", { :customerAccount => attributes() })
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def delete
|
24
|
+
request("deletecustomercreditcardaccount", { :accountId => self.ps_reference_id })
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.default_for_customer_id(customer_id)
|
29
|
+
request("getdefaultcreditcardaccount", { :customerId => customer_id }, &instantiate_object)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.create(params={})
|
33
|
+
cc = self.new(params)
|
34
|
+
cc.save()
|
35
|
+
return cc
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module PS
|
2
|
+
class Customer < Object
|
3
|
+
attr_accessor :first_name,:middle_name,:last_name,:email,:alt_email,:phone,:alt_phone,:fax,:web_site,:billing_address1,:billing_address2,:billing_city,:billing_state,:billing_postal_code,:billing_country_code,:shipping_same_as_billing,:shipping_address1,:shipping_address2,:shipping_city,:shipping_state,:shipping_postal_code,:shipping_country_code,:company_name,:notes,:last_modified,:created_on
|
4
|
+
|
5
|
+
def save
|
6
|
+
begin
|
7
|
+
save!()
|
8
|
+
true
|
9
|
+
rescue Exception
|
10
|
+
false
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def save!
|
15
|
+
request("addcustomer", { :customer => attributes }, &update_self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def destroy
|
19
|
+
if self.ps_reference_id then
|
20
|
+
request("deletecustomer", { :id => self.ps_reference_id } )
|
21
|
+
true
|
22
|
+
else
|
23
|
+
false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def payments
|
28
|
+
Payment.find(self.ps_reference_id)
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_default_customer_account(account_id)
|
32
|
+
CustomerAccount.find(account_id).make_default
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_customer_account
|
36
|
+
CustomerAccount.default(self.ps_reference_id)
|
37
|
+
end
|
38
|
+
|
39
|
+
def default_credit_card_account
|
40
|
+
CreditCardAccount.default_for_customer_id(self.ps_reference_id)
|
41
|
+
end
|
42
|
+
|
43
|
+
class << self
|
44
|
+
#returns [ PS::Customer, PS::CustomerAccount, PS::Payment ]
|
45
|
+
def create_and_make_cc_payment(customer={}, account={}, amount=0.0, cid="")
|
46
|
+
request("addcustomerandmakeccpayment", {
|
47
|
+
:customer => customer,
|
48
|
+
#account must have a customer_id of 0
|
49
|
+
:customerAccount => account,
|
50
|
+
:amount => amount,
|
51
|
+
:cid => cid
|
52
|
+
}, &instantiate_object)
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_and_make_ach_payment(customer={}, account={}, amount=0.0, cid="")
|
56
|
+
request("addcustomerandmakeachpayment", {
|
57
|
+
:customer => customer,
|
58
|
+
:customerAccount => account,
|
59
|
+
:amount => amount,
|
60
|
+
:cid => cid
|
61
|
+
}, &instantiate_object)
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_customer_and_default_accounts(customer_id)
|
65
|
+
request("GetCustomerAndDefaultAccounts", { :customerId => customer_id }, &instantiate_object)
|
66
|
+
end
|
67
|
+
|
68
|
+
def find(id)
|
69
|
+
request("getcustomer", { :id => id }, &instantiate_object)
|
70
|
+
end
|
71
|
+
|
72
|
+
def create(options={})
|
73
|
+
customer = self.new(options)
|
74
|
+
customer.save()
|
75
|
+
return customer
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module PS
|
2
|
+
class CustomerAccount < Object
|
3
|
+
attr_accessor :customer_id
|
4
|
+
|
5
|
+
def make_default
|
6
|
+
request("SetDefaultCustomerAccount",
|
7
|
+
{
|
8
|
+
:customerId => self.customer_id,
|
9
|
+
:customerAccountId => self.ps_reference_id
|
10
|
+
}
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def find(account_id, customer_id)
|
16
|
+
#The name of this method in Paysimple is sooo misleading...
|
17
|
+
request("GetCustomerAccountByAccountId", { :accountId => account_id, :customerId => customer_id }, &instantiate_object)
|
18
|
+
end
|
19
|
+
|
20
|
+
def default(customer_id)
|
21
|
+
request("GetDefaultCustomerAccount", { :customerId => customer_id }, &instantiate_object)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module PS
|
2
|
+
class Payment < Object
|
3
|
+
attr_accessor :ref_payment_id,:customer_id,:customer_account_id,:amount,:status,:payment_date,:invoice_id,:recurring_schedule_id,:is_debit,:payment_type,:payment_sub_type,:provider_auth_code,:trace_number,:estimate_settled_date,:actual_settled_date,:can_void_until,:invoice_number,:purchase_order_number,:order_id,:description
|
4
|
+
|
5
|
+
def cancel
|
6
|
+
request("cancelpayment", { :paymentId => self.ps_reference_id }, &update_by_find)
|
7
|
+
end
|
8
|
+
|
9
|
+
def reverse
|
10
|
+
request("reversepayment", { :paymentId => self.ps_reference_id }, &update_by_find)
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def list(customer_id, criteria=nil)
|
15
|
+
request("listpayments", { :customerId => customer_id, :criteria => criteria }, &instantiate_object)
|
16
|
+
end
|
17
|
+
|
18
|
+
def reverse_by_id(payment_id)
|
19
|
+
request("reversepayment", { :paymentId => payment_id })
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def cancel_by_id(payment_id)
|
24
|
+
request("cancelpayment", { :paymentId => payment_id })
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def make(customer_id, amount, account_id=nil, cid="", order_details=nil)
|
29
|
+
request("makepayment",
|
30
|
+
{
|
31
|
+
:customerId => customer_id,
|
32
|
+
:customerAccountId => account_id,
|
33
|
+
:amount => amount,
|
34
|
+
:cid => cid,
|
35
|
+
:detail => order_details
|
36
|
+
}, &instantiate_object)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def update_by_find
|
42
|
+
Proc.new do
|
43
|
+
i = 0
|
44
|
+
loop do
|
45
|
+
payments = self.class
|
46
|
+
.list(self.customer_id, { :page => i, :items_per_page => 200 })
|
47
|
+
case payments
|
48
|
+
when []
|
49
|
+
break
|
50
|
+
when Array
|
51
|
+
updated = false
|
52
|
+
payments.each do |payment|
|
53
|
+
if payment.ps_reference_id == self.ps_reference_id then
|
54
|
+
set_attributes(payment.attributes)
|
55
|
+
updated = true
|
56
|
+
break
|
57
|
+
end
|
58
|
+
end
|
59
|
+
break if updated
|
60
|
+
when PS::Payment
|
61
|
+
if payments.ps_reference_id == self.ps_reference_id then
|
62
|
+
set_attributes(payments.attributes)
|
63
|
+
end
|
64
|
+
# break here because if only one payment is returned, then it's
|
65
|
+
# only one that exists or it is the last one...
|
66
|
+
break
|
67
|
+
end
|
68
|
+
i += 1
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
module PS
|
2
|
+
class PaymentStatusFilter < Object
|
3
|
+
attr_accessor :all, :pending, :posted, :settled, :failed, :resubmitted, :voided, :reversed, :saved, :scheduled, :reverse_posted, :charge_back, :close_charge_back, :authorized, :returned, :reverse_charge_back, :reverse_NSF, :reverse_return, :refund_settled
|
4
|
+
|
5
|
+
end
|
6
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module PS
|
2
|
+
class RecurringPayment < Object
|
3
|
+
attr_accessor :customer_id, :customer_account_id, :schedule_type, :start_date, :has_end_date, :end_date, :billing_frequency_type, :billing_frequency_param, :payment_amount, :first_payment_done, :first_payment_amount, :first_payment_date, :total_due_amount, :total_number_of_payments, :balance_remaining, :number_of_payments_remaining, :invoice_no, :order_id, :description, :schedule_status, :number_of_payment_made, :total_amount_paid, :date_of_last_payment_made, :pause_until_date
|
4
|
+
|
5
|
+
def save
|
6
|
+
begin
|
7
|
+
save!()
|
8
|
+
true
|
9
|
+
rescue Exception
|
10
|
+
false
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def save!
|
15
|
+
request("addrecurringpayment", { :recurringPayment => attributes }, &update_self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def update
|
19
|
+
request("modifyrecurringpaymentschedule", { :paymentSchedule => attributes })
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def suspend
|
24
|
+
request("suspendrecurringpaymentschedule", { :scheduleId => self.ps_reference_id }, &update_self)
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def resume
|
29
|
+
request("resumerecurringpaymentschedule", { :scheduleId => self.ps_reference_id }, &update_self)
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def destroy
|
34
|
+
request("deleterecurringschedule", { :scheduleId => self.ps_reference_id })
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.create(params={})
|
39
|
+
recurring_payment = new(params)
|
40
|
+
recurring_payment.save()
|
41
|
+
return recurring_payment
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.list(start_date, end_date, customer_id, criteria=nil, filter={})
|
45
|
+
criteria ||= { :Page => 1, :items_per_page => 12 }
|
46
|
+
request("listrecurringpayments",
|
47
|
+
{
|
48
|
+
:startDate => start_date,
|
49
|
+
:endDate => end_date,
|
50
|
+
:customerId => customer_id,
|
51
|
+
:filter => filter,
|
52
|
+
:criteria => criteria
|
53
|
+
}, &instantiate_object)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.find(schedule_id)
|
57
|
+
request("GetRecurringPaymentSchedule", { :scheduleId => schedule_id }, &instantiate_object)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module PS
|
2
|
+
class RecurringPaymentFilter < PsObject
|
3
|
+
attr_accessor :active, :disabled, :paused, :expired, :suspended
|
4
|
+
|
5
|
+
def active?
|
6
|
+
self.active
|
7
|
+
end
|
8
|
+
|
9
|
+
def disabled?
|
10
|
+
self.disabled
|
11
|
+
end
|
12
|
+
|
13
|
+
def paused?
|
14
|
+
self.paused
|
15
|
+
end
|
16
|
+
|
17
|
+
def expired?
|
18
|
+
self.expired
|
19
|
+
end
|
20
|
+
|
21
|
+
def suspended?
|
22
|
+
self.suspended
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/lib/ps/response.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
module PS
|
2
|
+
class Response < Base
|
3
|
+
attr_accessor :is_success,:error_message,:sub_type,:ps_object,:total_items,:items_per_page,:current_page,:error_type,:exception_detail
|
4
|
+
|
5
|
+
#### Some Basic fields returned by Paysimple
|
6
|
+
# {
|
7
|
+
## '__type' => String,
|
8
|
+
## 'CurrentPage => Int,
|
9
|
+
## 'ErrorMessage => String
|
10
|
+
## 'ErrorType' => Int,
|
11
|
+
## 'IsSuccess' => boolean,
|
12
|
+
## 'itemsPerPage' => Int,
|
13
|
+
## 'PsObject' => [
|
14
|
+
### { ... },
|
15
|
+
### ...
|
16
|
+
## ],
|
17
|
+
## 'SubType' => String,
|
18
|
+
## 'TotalItems' => Int
|
19
|
+
# }
|
20
|
+
|
21
|
+
## God this is terribly worded...
|
22
|
+
# After the format classes; JSON, XML, SOAP, etc; get a response from
|
23
|
+
# Paysimple, the response should be parsed from the respective format into
|
24
|
+
# a ruby hash. That hash is then passed here, and the response is setup.
|
25
|
+
# example:
|
26
|
+
# post_response = HTTParty.post("https://api.paysimple.com/3.00/paysimpleapi", options_hash(params))
|
27
|
+
# parsed_response = post_response.parsed_response['d'] #the 'd' key is specific to the json format
|
28
|
+
# Response.new(parsed_response)
|
29
|
+
def initialize(params={})
|
30
|
+
params.each { |k,v| instance_variable_set("@#{k.to_snake_case}", v) }
|
31
|
+
successful_request?()
|
32
|
+
@ps_object ||= []
|
33
|
+
parse_ps_object()
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
##
|
39
|
+
# Checks for errors in the PsResponse.
|
40
|
+
def successful_request?
|
41
|
+
raise RequestError, @exception_detail["InnerException"]["Message"] if @exception_detail
|
42
|
+
raise RequestError, @error_message.join("; ") unless @is_success == true
|
43
|
+
end
|
44
|
+
|
45
|
+
def parse_ps_object()
|
46
|
+
snake_case_ps_object_keys()
|
47
|
+
parse_object_dates()
|
48
|
+
if @ps_object.length == 1 then
|
49
|
+
@ps_object = @ps_object.first
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Paysimple returns the attribute names in CamelCase, but the attributes use
|
55
|
+
# snake_case within the code base. The method bellow converts the attribute
|
56
|
+
# names into snake_case so that they can be more easily dynamically assigned
|
57
|
+
# to the appropriate class.
|
58
|
+
def snake_case_ps_object_keys
|
59
|
+
# this line might seem a bit odd, but it is necessary because there
|
60
|
+
# are two paysimple methods that, depending on the context, will
|
61
|
+
# return @ps_object where one of the elements is an instance of
|
62
|
+
# NilClass.
|
63
|
+
@ps_object.delete_if(&:nil?)
|
64
|
+
@ps_object.map!(&:snake_case_keys)
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# parses date fields into a rubyesque format.
|
69
|
+
def parse_object_dates
|
70
|
+
@ps_object.each_with_index do |object, i|
|
71
|
+
object.each do |key, value|
|
72
|
+
# here we are asking the format class if the value is a date in its
|
73
|
+
# format. For Example, the json format class returns dates in the
|
74
|
+
# following format: "/Date(1248908403000-0600)/"
|
75
|
+
if date?(value) then
|
76
|
+
# if so, then we now ask the format class how to parse that date.
|
77
|
+
@ps_object[i][key] = parse_date(value)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|