safecharge 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +137 -0
- data/lib/safecharge.rb +147 -0
- data/lib/safecharge/constants.rb +44 -0
- data/lib/safecharge/request.rb +187 -0
- data/lib/safecharge/response.rb +58 -0
- data/lib/safecharge/version.rb +6 -0
- data/lib/safecharge/wc_request.rb +79 -0
- data/safecharge.gemspec +27 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ff76ed515bb382871847edfff75a3338adb50cbe
|
4
|
+
data.tar.gz: 1abce3e38d9783c534ae44b8a511768f4a44ba0b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 207c9507b761fd53c213796c29bef229582d769c283bb162952c8798dde2707a0308fd8386f2f31637bfd1bb49ae33d34dc46355b0fda5f2d1f3770b1a296b68
|
7
|
+
data.tar.gz: d1204ea21290be3e93365297a2bc23f251884e52e3274f2024c3c9899d10341be942b8cadf0495d7b7b45a327df6e30a5a95536a58c8b4e5592c16f8b6f65e65
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Interactive Gaming Entertainment Pty Ltd
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
# Safecharge
|
2
|
+
|
3
|
+
A simple Ruby wrapper for the SafeCharge PPP Payment API v 3.0.0 (Revised July 2011), as well as
|
4
|
+
the SafeCharge Web Cashier API v1.0 (Revised July 2013).
|
5
|
+
|
6
|
+
## Status
|
7
|
+
|
8
|
+
This project is under active development right now, and not suitable for use.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
gem 'safecharge'
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install safecharge
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
### Public Payment Page
|
27
|
+
|
28
|
+
The SafeCharge PPP system provides a simple means for merchants to integrate credit card
|
29
|
+
payments into their site, without worrying about having to capture their customers'
|
30
|
+
credit card details.
|
31
|
+
|
32
|
+
It works like this:
|
33
|
+
|
34
|
+
Step 1) Your website provides a way for customers to choose the items they wish to buy,
|
35
|
+
say via a shopping basket, or similar.
|
36
|
+
|
37
|
+
Collate an array of items with the following information
|
38
|
+
|
39
|
+
items = [
|
40
|
+
{
|
41
|
+
'name' => 'bat',
|
42
|
+
'number' => 'sku54321',
|
43
|
+
'amount' => 25,
|
44
|
+
'quantity' => 1
|
45
|
+
},
|
46
|
+
{
|
47
|
+
'name' => 'ball',
|
48
|
+
'number' => 'sku12345',
|
49
|
+
'amount' => 15,
|
50
|
+
'quantity' => 2
|
51
|
+
}
|
52
|
+
]
|
53
|
+
|
54
|
+
and insert that items array into an array of params like so
|
55
|
+
|
56
|
+
params = {
|
57
|
+
'total_amount' => 55,
|
58
|
+
'currency' => 'USD',
|
59
|
+
'items' => items
|
60
|
+
}
|
61
|
+
|
62
|
+
Note you must supply the following environment variables for this API to work.
|
63
|
+
|
64
|
+
SAFECHARGE_SECRET_KEY, SAFECHARGE_MERCHANT_ID, SAFECHARGE_MERCHANT_SITE_ID
|
65
|
+
|
66
|
+
These will have been provided to you by Safecharge.
|
67
|
+
|
68
|
+
Step 2) You offer a `checkout` button that links to the following url.
|
69
|
+
|
70
|
+
`url = Safecharge.request_url(params)`
|
71
|
+
|
72
|
+
Step 3) The SafeCharge system will redirect the user to the Public Payment Page
|
73
|
+
and there they will enter in their credit card and other payment details as needed.
|
74
|
+
When the user confirms their payment, the Safecharge system authenticates it and
|
75
|
+
redirects the user to either a 'success', 'failure' or 'back' page. The back page
|
76
|
+
is used if the user suspends the payment processing by clicking on their browser's
|
77
|
+
back button.
|
78
|
+
|
79
|
+
### Web Cashier
|
80
|
+
|
81
|
+
The Safecharge Web Cashier extends the standard Public Payment Page with paramaters that
|
82
|
+
allow you to register and identify customers in SafeCharge's system, and enable you to
|
83
|
+
change the deposit amounts within limits you define.
|
84
|
+
|
85
|
+
It works like this:
|
86
|
+
|
87
|
+
Step 1) Your website provides a way for customers to choose the items they wish to buy,
|
88
|
+
say via a shopping basket, or similar.
|
89
|
+
|
90
|
+
Collate an array of items with the following information
|
91
|
+
|
92
|
+
items = [
|
93
|
+
{
|
94
|
+
'name' => 'deposit',
|
95
|
+
'number' => 'something',
|
96
|
+
'amount' => 0,
|
97
|
+
'quantity' => 1,
|
98
|
+
'open_amount' => 'true',
|
99
|
+
'min_amount' => 10.0,
|
100
|
+
'max_amount' => 1000.0,
|
101
|
+
},
|
102
|
+
]
|
103
|
+
|
104
|
+
and insert that items array into an array of params like so
|
105
|
+
|
106
|
+
params = {
|
107
|
+
'total_amount' => 55,
|
108
|
+
'currency' => 'USD',
|
109
|
+
'items' => items,
|
110
|
+
'user_token' => 'auto', # or 'register'
|
111
|
+
'user_token_id' => 'player_id'
|
112
|
+
}
|
113
|
+
|
114
|
+
Note you must supply the following environment variables for this API to work.
|
115
|
+
|
116
|
+
SAFECHARGE_SECRET_KEY, SAFECHARGE_MERCHANT_ID, SAFECHARGE_MERCHANT_SITE_ID
|
117
|
+
|
118
|
+
These will have been provided to you by Safecharge.
|
119
|
+
|
120
|
+
Step 2) You offer a `checkout` button that links to the following url.
|
121
|
+
|
122
|
+
`url = Safecharge.wc_request_url(params)`
|
123
|
+
|
124
|
+
Step 3) The SafeCharge system will redirect the user to the WebCashier Page
|
125
|
+
and there they will enter in their credit card and other payment details as needed.
|
126
|
+
When the user confirms their payment, the Safecharge system authenticates it and
|
127
|
+
redirects the user to either a 'success', 'failure' or 'back' page. The back page
|
128
|
+
is used if the user suspends the payment processing by clicking on their browser's
|
129
|
+
back button.
|
130
|
+
|
131
|
+
## Contributing
|
132
|
+
|
133
|
+
1. Fork it
|
134
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
135
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
136
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
137
|
+
5. Create new Pull Request
|
data/lib/safecharge.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
#!/user/bin/env ruby
|
2
|
+
#coding: utf-8
|
3
|
+
|
4
|
+
require "safecharge/version"
|
5
|
+
require "safecharge/constants"
|
6
|
+
require "safecharge/request"
|
7
|
+
require "safecharge/wc_request"
|
8
|
+
require "safecharge/response"
|
9
|
+
|
10
|
+
# The Safecharge PPP system provides a simple means for merchants to integrate credit card
|
11
|
+
# payments into their site, without worrying about having to capture their customers'
|
12
|
+
# credit card details.
|
13
|
+
#
|
14
|
+
# It works like this:
|
15
|
+
#
|
16
|
+
# Step 1) Your website provides a way for customers to choose the items they wish to buy,
|
17
|
+
# say via a shopping basket, or similar.
|
18
|
+
#
|
19
|
+
# Collate an array of items with the following information
|
20
|
+
#
|
21
|
+
# items = [
|
22
|
+
# {
|
23
|
+
# 'name' => 'bat',
|
24
|
+
# 'number' => 'sku54321',
|
25
|
+
# 'amount' => 25,
|
26
|
+
# 'quantity' => 1
|
27
|
+
# },
|
28
|
+
# {
|
29
|
+
# 'name' => 'ball',
|
30
|
+
# 'number' => 'sku12345',
|
31
|
+
# 'amount' => 15,
|
32
|
+
# 'quantity' => 2
|
33
|
+
# }
|
34
|
+
# ]
|
35
|
+
#
|
36
|
+
# and insert that items array into an array of params like so
|
37
|
+
#
|
38
|
+
# params = {
|
39
|
+
# 'total_amount' => 55,
|
40
|
+
# 'currency' => 'USD',
|
41
|
+
# 'items' => items
|
42
|
+
# }
|
43
|
+
#
|
44
|
+
# Note you must supply the following environment variables for this API to work.
|
45
|
+
#
|
46
|
+
# SAFECHARGE_SECRET_KEY, SAFECHARGE_MERCHANT_ID, SAFECHARGE_MERCHANT_SITE_ID
|
47
|
+
#
|
48
|
+
# These will have been provided to you by Safecharge.
|
49
|
+
#
|
50
|
+
# Step 2) You offer a 'checkout' button that links to the following url.
|
51
|
+
#
|
52
|
+
# url = Safecharge.request_url(params)
|
53
|
+
#
|
54
|
+
# Step 3) The Safecharge system will redirect the user to the Public Payment Page
|
55
|
+
# and there they will enter in their credit card and other payment details as needed.
|
56
|
+
# When the user confirms their payment, the Safecharge system authenticates it and
|
57
|
+
# redirects the user to either a 'success', 'failure' or 'back' page. The back page
|
58
|
+
# is used if the user suspends the payment processing by clicking on their browser's
|
59
|
+
# back button.
|
60
|
+
# Whichever page is returned, it will incude parameters from the server
|
61
|
+
# which can be decoded into a valid Response object by your server.
|
62
|
+
#
|
63
|
+
# response = Safecharge.parse_response
|
64
|
+
#
|
65
|
+
# TODO: finish these docs
|
66
|
+
#
|
67
|
+
module Safecharge
|
68
|
+
|
69
|
+
# define errors and exceptions.
|
70
|
+
class SafechargeError < RuntimeError
|
71
|
+
end
|
72
|
+
class InternalException < SafechargeError
|
73
|
+
end
|
74
|
+
class ValidationException < SafechargeError
|
75
|
+
end
|
76
|
+
|
77
|
+
# module level method to get the redirection URL given some params.
|
78
|
+
# you must explicitly set the mode to 'live' to generate the production URL.
|
79
|
+
def self.request_url(params = {}, mode = 'test')
|
80
|
+
result = nil
|
81
|
+
url = ''
|
82
|
+
case mode
|
83
|
+
when 'test'
|
84
|
+
url = Safecharge::Constants::SERVER_TEST
|
85
|
+
when 'live'
|
86
|
+
url = Safecharge::Constants::SERVER_LIVE
|
87
|
+
else
|
88
|
+
raise ArgumentError, "Invalid request mode #{mode}"
|
89
|
+
end
|
90
|
+
|
91
|
+
begin
|
92
|
+
request = Safecharge::Request.new(url, params)
|
93
|
+
return request.full_url
|
94
|
+
|
95
|
+
rescue InternalException => e
|
96
|
+
puts "Caught Internal Exception: #{e.message}"
|
97
|
+
puts e.backtrace
|
98
|
+
raise RuntimeError, "Internal server error. Please try again later."
|
99
|
+
|
100
|
+
rescue ValidationException => e
|
101
|
+
puts "Caught Validation Exception: #{e.message}"
|
102
|
+
puts e.backtrace
|
103
|
+
raise RuntimeError, "Validation error: #{e.message} Fix your data and retry."
|
104
|
+
|
105
|
+
rescue SafechargeError => e
|
106
|
+
puts "Caught General Safecharge Error: #{e.message}"
|
107
|
+
puts e.backtrace
|
108
|
+
raise RuntimeError, "Undocumented Internal error: #{e.message}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# module level method to get the redirection URL given some params.
|
113
|
+
# you must explicitly set the mode to 'live' to generate the production URL.
|
114
|
+
def self.wc_request_url(params = {}, mode = 'test')
|
115
|
+
result = nil
|
116
|
+
url = ''
|
117
|
+
case mode
|
118
|
+
when 'test'
|
119
|
+
url = Safecharge::Constants::SERVER_TEST
|
120
|
+
when 'live'
|
121
|
+
url = Safecharge::Constants::SERVER_LIVE
|
122
|
+
else
|
123
|
+
raise ArgumentError, "Invalid request mode #{mode}"
|
124
|
+
end
|
125
|
+
|
126
|
+
begin
|
127
|
+
request = Safecharge::WcRequest.new(url, params)
|
128
|
+
return request.full_url
|
129
|
+
|
130
|
+
rescue InternalException => e
|
131
|
+
puts "Caught Internal Exception: #{e.message}"
|
132
|
+
puts e.backtrace
|
133
|
+
raise RuntimeError, "Internal server error. Please try again later."
|
134
|
+
|
135
|
+
rescue ValidationException => e
|
136
|
+
puts "Caught Validation Exception: #{e.message}"
|
137
|
+
puts e.backtrace
|
138
|
+
raise RuntimeError, "Validation error: #{e.message} Fix your data and retry."
|
139
|
+
|
140
|
+
rescue SafechargeError => e
|
141
|
+
puts "Caught General Safecharge Error: #{e.message}"
|
142
|
+
puts e.backtrace
|
143
|
+
raise RuntimeError, "Undocumented Internal error: #{e.message}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/user/bin/env ruby
|
2
|
+
#coding: utf-8
|
3
|
+
|
4
|
+
module Safecharge
|
5
|
+
class Constants
|
6
|
+
API_VERSION = '3.0.0'
|
7
|
+
|
8
|
+
SERVER_TEST = ENV['SAFECHARGE_SERVER_TEST'] # provided by SafeCharge
|
9
|
+
SERVER_LIVE = ENV['SAFECHARGE_SERVER_LIVE'] # provided by SafeCharge
|
10
|
+
|
11
|
+
SECRET_KEY = ENV['SAFECHARGE_SECRET_KEY'] # provided by SafeCharge
|
12
|
+
MERCHANT_ID = ENV['SAFECHARGE_MERCHANT_ID'] # provided by SafeCharge
|
13
|
+
MERCHANT_SITE_ID = ENV['SAFECHARGE_MERCHANT_SITE_ID'] # provided by SafeCharge
|
14
|
+
MERCHANT_3D_SITE_ID = ENV['SAFECHARGE_MERCHANT_3D_SITE_ID'] # provided by SafeCharge
|
15
|
+
|
16
|
+
SG_CLIENT_PASSWORD = ENV['SAFECHARGE_SG_CLIENT_PASSWORD'] # provided by SafeCharge
|
17
|
+
SG_3D_CLIENT_PASSWORD = ENV['SAFECHARGE_SG_3D_CLIENT_PASSWORD'] # provided by SafeCharge
|
18
|
+
|
19
|
+
CPANEL_PASSWORD = ENV['SAFECHARGE_CPANEL_PASSWORD'] # provided by SafeCharge
|
20
|
+
|
21
|
+
APPROVED = 'APPROVED'
|
22
|
+
DECLINED = 'DECLINED'
|
23
|
+
ERROR = 'ERROR'
|
24
|
+
BANK_ERROR = 'BANK_ERROR'
|
25
|
+
INVALID_LOGIN = 'INVALID_LOGIN'
|
26
|
+
INVALID_IP = 'INVALID_IP'
|
27
|
+
TIMEOUT = 'TIMEOUT'
|
28
|
+
UNKNOWN_ERROR = 'UNKNOWN_ERROR'
|
29
|
+
|
30
|
+
CURRENCIES = [
|
31
|
+
'GBP', 'EUR', 'USD', 'HKD', 'YEN', 'AUD', 'CAD',
|
32
|
+
'NOK', 'ZAR', 'SEK', 'CHF', 'NIS', 'MXN', 'RUB'
|
33
|
+
]
|
34
|
+
# REQUEST_TYPE_AUTH = 'Auth'
|
35
|
+
# REQUEST_TYPE_SETTLE = 'Settle'
|
36
|
+
# REQUEST_TYPE_SALE = 'Sale'
|
37
|
+
# REQUEST_TYPE_CREDIT = 'Credit'
|
38
|
+
# REQUEST_TYPE_VOID = 'Void'
|
39
|
+
# REQUEST_TYPE_AVS = 'AVSOnly'
|
40
|
+
|
41
|
+
DEFAULT_ENCODING = 'utf-8'
|
42
|
+
DEFAULT_CURRENCY_CODE = 'EUR'
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
#!/user/bin/env ruby
|
2
|
+
#coding: utf-8
|
3
|
+
|
4
|
+
require "safecharge"
|
5
|
+
require "safecharge/constants"
|
6
|
+
|
7
|
+
module Safecharge
|
8
|
+
class Request
|
9
|
+
attr_accessor :mode, :params, :items, :url, :full_url
|
10
|
+
|
11
|
+
ALLOWED_FIELDS = {
|
12
|
+
'currency' => {:required => true, :type => 'currency_code'},
|
13
|
+
'customData' => {:required => false, :type => 'string', length: 255},
|
14
|
+
'customSiteName' => {:required => false, :type => 'string', length: 50},
|
15
|
+
'discount' => {:required => false, :type => 'currency'},
|
16
|
+
'encoding' => {:required => false, :type => 'string', length: 20},
|
17
|
+
'error_url' => {:required => false, :type => 'string', length: 300},
|
18
|
+
'handling' => {:required => false, :type => 'currency'},
|
19
|
+
'invoice_id' => {:required => false, :type => 'string', length: 400},
|
20
|
+
'merchant_id' => {:required => true, :type => 'int'},
|
21
|
+
'merchant_site_id' => {:required => true, :type => 'int'},
|
22
|
+
'merchant_unique_id' => {:required => false, :type => 'string', length: 64},
|
23
|
+
'merchantLocale' => {:required => false, :type => 'string', length: 5},
|
24
|
+
'payment_method' => {:required => false, :type => 'string', length: 256},
|
25
|
+
'pending_url' => {:required => false, :type => 'string', length: 300},
|
26
|
+
'productId' => {:required => false, :type => 'string', length: 50},
|
27
|
+
'shipping' => {:required => false, :type => 'currency'},
|
28
|
+
'skip_billing_tab' => {:required => false, :type => 'boolstring'},
|
29
|
+
'skip_review_tab' => {:required => false, :type => 'boolstring'},
|
30
|
+
'success_url' => {:required => false, :type => 'string', length: 300},
|
31
|
+
'total_amount' => {:required => true, :type => 'currency'},
|
32
|
+
'total_tax' => {:required => false, :type => 'percent'},
|
33
|
+
'userid' => {:required => false, :type => 'string', length: 50},
|
34
|
+
'version' => {:required => true, :type => 'string', length: 10},
|
35
|
+
'webMasterId' => {:required => false, :type => 'string', length: 255}
|
36
|
+
|
37
|
+
} # 'time_stamp', 'numberofitems' and 'checksum' are inserted after validation.
|
38
|
+
|
39
|
+
ALLOWED_ITEM_FIELDS = {
|
40
|
+
'name' => {:required => true, :type => 'string', length: 400},
|
41
|
+
'number' => {:required => true, :type => 'string', length: 400},
|
42
|
+
'amount' => {:required => true, :type => 'currency'},
|
43
|
+
'quantity' => {:required => true, :type => 'int'},
|
44
|
+
'discount' => {:required => false, :type => 'percent'},
|
45
|
+
'shipping' => {:required => false, :type => 'currency'},
|
46
|
+
'handling' => {:required => false, :type => 'currency'}
|
47
|
+
|
48
|
+
}
|
49
|
+
|
50
|
+
DEFAULT_PARAMS = {
|
51
|
+
'merchant_id' => Safecharge::Constants::MERCHANT_ID,
|
52
|
+
'merchant_site_id' => Safecharge::Constants::MERCHANT_SITE_ID,
|
53
|
+
'currency' => Safecharge::Constants::DEFAULT_CURRENCY_CODE,
|
54
|
+
'version' => Safecharge::Constants::API_VERSION,
|
55
|
+
'encoding' => Safecharge::Constants::DEFAULT_ENCODING
|
56
|
+
}
|
57
|
+
|
58
|
+
def initialize(url, params = {})
|
59
|
+
raise ArgumentError, "missing url" if url == nil || url.empty?
|
60
|
+
if url === Safecharge::Constants::SERVER_TEST
|
61
|
+
self.mode = 'test'
|
62
|
+
elsif url === Safecharge::Constants::SERVER_LIVE
|
63
|
+
self.mode = 'live'
|
64
|
+
else
|
65
|
+
raise ArgumentError, "invalid url #{url}"
|
66
|
+
end
|
67
|
+
self.url = url
|
68
|
+
self.full_url = url
|
69
|
+
core_params, items, extracted_items = self.extract_items(params)
|
70
|
+
raise ValidationException, "Missing array of Items." if items == nil || items.empty?
|
71
|
+
self.items = items
|
72
|
+
self.params = DEFAULT_PARAMS.merge(core_params)
|
73
|
+
self.validate_parameters(self.params)
|
74
|
+
items.each {|i| self.validate_parameters(i, self.class::ALLOWED_ITEM_FIELDS)}
|
75
|
+
self.params.merge!(extracted_items)
|
76
|
+
self.params.merge!({'numberofitems' => items.size,
|
77
|
+
'time_stamp' => Time.now.utc.strftime("%Y-%m-%d.%H:%M:%S")})
|
78
|
+
self.params.merge!({'checksum' => calculate_checksum})
|
79
|
+
self.construct_url
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
|
84
|
+
def extract_items(params)
|
85
|
+
items = params.delete('items')
|
86
|
+
return params, nil, nil if items == nil
|
87
|
+
keyed_items = {}
|
88
|
+
items.each_with_index do |item, i|
|
89
|
+
item.keys.each do |key|
|
90
|
+
new_key = "item_#{key}_#{i+1}"
|
91
|
+
keyed_items[new_key] = item[key]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
return params, items, keyed_items
|
95
|
+
end
|
96
|
+
|
97
|
+
def validate_parameters(params, against = self.class::ALLOWED_FIELDS)
|
98
|
+
self.validate_fields(params, against)
|
99
|
+
self.validate_no_extra_fields(params, against)
|
100
|
+
end
|
101
|
+
|
102
|
+
def validate_fields(params, against = self.class::ALLOWED_FIELDS)
|
103
|
+
against.each do |name, meta|
|
104
|
+
required = (meta[:required] === true)
|
105
|
+
# Check that all required parameters are present
|
106
|
+
if required && !params.keys.include?(name)
|
107
|
+
raise ValidationException, "Parameter #{name} is required, but missing from #{params.keys}"
|
108
|
+
end
|
109
|
+
if params.keys.include? name
|
110
|
+
p = params[name]
|
111
|
+
if p != nil
|
112
|
+
# Check the type of the value
|
113
|
+
correct_type = false
|
114
|
+
case meta[:type]
|
115
|
+
when 'boolstring'
|
116
|
+
correct_type = p.is_a?(String) && ['True', 'False'].include?(p)
|
117
|
+
when 'string'
|
118
|
+
correct_type = p.is_a? String
|
119
|
+
raise ValidationException, sprintf("Value '%s' in field '%s' is too long.", p, name) if p.size > meta[:length]
|
120
|
+
when 'currency_code'
|
121
|
+
correct_type = p.is_a? String
|
122
|
+
when 'currency'
|
123
|
+
correct_type = p.is_a?(Float) || p.is_a?(Integer)
|
124
|
+
when 'percent'
|
125
|
+
correct_type = p.is_a?(Float) || p.is_a?(Integer)
|
126
|
+
when 'int'
|
127
|
+
correct_type = p.is_a? Integer
|
128
|
+
end
|
129
|
+
|
130
|
+
if !correct_type
|
131
|
+
raise ValidationException, sprintf("Value '%s' in field '%s' is not of expected type '%s'", p, name, meta[:type])
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def validate_no_extra_fields(params, against = self.class::ALLOWED_FIELDS)
|
139
|
+
allowed_fields = against.keys
|
140
|
+
params.each do |key, value|
|
141
|
+
raise InternalException, "Field #{key} is not supported" if !allowed_fields.include?(key)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def calculate_checksum
|
146
|
+
codes = [Safecharge::Constants::SECRET_KEY,
|
147
|
+
self.params['merchant_id'],
|
148
|
+
self.params['currency'],
|
149
|
+
self.params['total_amount']]
|
150
|
+
self.items.each do |item|
|
151
|
+
codes << item['name']
|
152
|
+
codes << item['amount']
|
153
|
+
codes << item['quantity']
|
154
|
+
end
|
155
|
+
codes << self.params['time_stamp']
|
156
|
+
s = codes.join('')
|
157
|
+
return Digest::MD5.hexdigest(s)
|
158
|
+
end
|
159
|
+
|
160
|
+
def construct_url
|
161
|
+
uri = URI(self.url)
|
162
|
+
uri.query = URI.encode_www_form(self.params)
|
163
|
+
self.full_url = uri.to_s
|
164
|
+
return uri
|
165
|
+
end
|
166
|
+
|
167
|
+
# def calculate_item_total
|
168
|
+
# # item_amount_N - item_discount_N + item_shipping_N + item_handling_N) * item_quantity_N
|
169
|
+
# return 0.0 if self.items == nil || self.items.empty?
|
170
|
+
# result = 0
|
171
|
+
# self.items.each {|i| result += (i['amount'] +
|
172
|
+
# i['shipping'] +
|
173
|
+
# i['handling'] -
|
174
|
+
# i['discount']) * i['quantity']}
|
175
|
+
# return result
|
176
|
+
# end
|
177
|
+
#
|
178
|
+
# def calculate_total_with_tax
|
179
|
+
# # calculatedTotalAmount+=shipping+handling-discount
|
180
|
+
# tot = calculate_item_total +
|
181
|
+
# self.fields['shipping'] +
|
182
|
+
# self.fields['handling'] - self.fields['discount']
|
183
|
+
# return tot, tot * ( self.fields['total_tax'] / 100)
|
184
|
+
# end
|
185
|
+
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/user/bin/env ruby
|
2
|
+
#coding: utf-8
|
3
|
+
|
4
|
+
require "safecharge"
|
5
|
+
require "safecharge/constants"
|
6
|
+
|
7
|
+
module Safecharge
|
8
|
+
class Response
|
9
|
+
attr_accessor :params
|
10
|
+
|
11
|
+
ALLOWED_FIELDS = [
|
12
|
+
'Status', 'totalAmount', 'TransactionID', 'ClientUniqueID', 'ErrCode',
|
13
|
+
'ExErrCode', 'AuthCode', 'Reason', 'Token', 'ReasonCode',
|
14
|
+
'advanceResponseChecksum', 'ECI',
|
15
|
+
'nameOnCard', 'currency',
|
16
|
+
'total_discount', 'total_handling', 'total_shipping', 'total_tax',
|
17
|
+
'customData', 'merchant_unique_id', 'merchant_site_id',
|
18
|
+
'requestVersion', 'message', 'Error', 'PPP_TransactionID', 'UserID',
|
19
|
+
'ProductID', 'ppp_status', 'merchantLocale', 'unknownParameters', 'webMasterId'
|
20
|
+
]
|
21
|
+
|
22
|
+
def initialize(incoming_encoded_params = nil)
|
23
|
+
self.params = self.decode(incoming_encoded_params)
|
24
|
+
end
|
25
|
+
|
26
|
+
def decode(param_string)
|
27
|
+
#todo write this.
|
28
|
+
return {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.code(err, exerr)
|
32
|
+
return Safecharge::Constants::APPROVED if err == 0 && exerr == 0
|
33
|
+
return Safecharge::Constants::DECLINED if err == -1 && exerr == 0
|
34
|
+
# pending? see p 22 of the spec
|
35
|
+
return Safecharge::Constants::ERROR if err == -1100 && exerr > 0
|
36
|
+
# could be more specific. see p 22 of the spec for exerr codes
|
37
|
+
return Safecharge::Constants::BANK_ERROR if err < 0 && exerr != 0
|
38
|
+
return Safecharge::Constants::INVALID_LOGIN if err == -1001
|
39
|
+
return Safecharge::Constants::INVALID_IP if err == -1005
|
40
|
+
return Safecharge::Constants::TIMEOUT if err == -1203
|
41
|
+
return Safecharge::Constants::UNKNOWN_ERROR
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def calculate_checksum
|
47
|
+
codes = [Safecharge::Constants::SECRET_KEY,
|
48
|
+
self.params['totalAmount'],
|
49
|
+
self.params['currency'],
|
50
|
+
self.params['responseTimeStamp'],
|
51
|
+
self.params['PPP_TransactionID'],
|
52
|
+
self.params['Status'],
|
53
|
+
self.params['productId']]
|
54
|
+
s = codes.join('')
|
55
|
+
return Digest::MD5.hexdigest(s)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
#!/user/bin/env ruby
|
2
|
+
#coding: utf-8
|
3
|
+
|
4
|
+
require "safecharge"
|
5
|
+
require "safecharge/constants"
|
6
|
+
|
7
|
+
module Safecharge
|
8
|
+
class WcRequest < Request
|
9
|
+
|
10
|
+
ALLOWED_FIELDS = {
|
11
|
+
'currency' => {:required => true, :type => 'currency_code'},
|
12
|
+
'customData' => {:required => false, :type => 'string', length: 255},
|
13
|
+
'customSiteName' => {:required => false, :type => 'string', length: 50},
|
14
|
+
'discount' => {:required => false, :type => 'currency'},
|
15
|
+
'encoding' => {:required => false, :type => 'string', length: 20},
|
16
|
+
'error_url' => {:required => false, :type => 'string', length: 300},
|
17
|
+
'handling' => {:required => false, :type => 'currency'},
|
18
|
+
'invoice_id' => {:required => false, :type => 'string', length: 400},
|
19
|
+
'merchant_id' => {:required => true, :type => 'int'},
|
20
|
+
'merchant_site_id' => {:required => true, :type => 'int'},
|
21
|
+
'merchant_unique_id' => {:required => false, :type => 'string', length: 64},
|
22
|
+
'merchantLocale' => {:required => false, :type => 'string', length: 5},
|
23
|
+
'payment_method' => {:required => false, :type => 'string', length: 256},
|
24
|
+
'pending_url' => {:required => false, :type => 'string', length: 300},
|
25
|
+
'productId' => {:required => false, :type => 'string', length: 50},
|
26
|
+
'shipping' => {:required => false, :type => 'currency'},
|
27
|
+
'skip_billing_tab' => {:required => false, :type => 'boolstring'},
|
28
|
+
'skip_review_tab' => {:required => false, :type => 'boolstring'},
|
29
|
+
'success_url' => {:required => false, :type => 'string', length: 300},
|
30
|
+
'total_amount' => {:required => true, :type => 'currency'},
|
31
|
+
'total_tax' => {:required => false, :type => 'percent'},
|
32
|
+
'userid' => {:required => false, :type => 'string', length: 50},
|
33
|
+
'version' => {:required => true, :type => 'string', length: 10},
|
34
|
+
'webMasterId' => {:required => false, :type => 'string', length: 255},
|
35
|
+
'user_token' => {:required => false, :type => 'string', length: 8}, # register or auto
|
36
|
+
'user_token_id' => {:required => true, :type => 'string', length: 45}
|
37
|
+
|
38
|
+
} # 'time_stamp', 'numberofitems' and 'checksum' are inserted after validation.
|
39
|
+
|
40
|
+
ALLOWED_ITEM_FIELDS = {
|
41
|
+
'name' => {:required => true, :type => 'string', length: 400},
|
42
|
+
'number' => {:required => true, :type => 'string', length: 400},
|
43
|
+
'amount' => {:required => true, :type => 'currency'},
|
44
|
+
'quantity' => {:required => true, :type => 'int'},
|
45
|
+
'discount' => {:required => false, :type => 'percent'},
|
46
|
+
'shipping' => {:required => false, :type => 'currency'},
|
47
|
+
'handling' => {:required => false, :type => 'currency'},
|
48
|
+
'open_amount' => {:required => false, :type => 'boolstring'},
|
49
|
+
'min_amount' => {:required => false, :type => 'currency'},
|
50
|
+
'max_amount' => {:required => false, :type => 'currency'}
|
51
|
+
}
|
52
|
+
|
53
|
+
def initialize(url, params = {})
|
54
|
+
super(url, params)
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
def calculate_checksum
|
60
|
+
codes = [Safecharge::Constants::SECRET_KEY,
|
61
|
+
self.params['merchant_id'],
|
62
|
+
self.params['currency'],
|
63
|
+
self.params['total_amount']]
|
64
|
+
self.items.each do |item|
|
65
|
+
codes << item['name']
|
66
|
+
codes << item['amount']
|
67
|
+
codes << item['quantity']
|
68
|
+
codes << item['open_amount']
|
69
|
+
codes << item['min_amount']
|
70
|
+
codes << item['max_amount']
|
71
|
+
end
|
72
|
+
codes << self.params['user_token_id']
|
73
|
+
codes << self.params['time_stamp']
|
74
|
+
s = codes.join('')
|
75
|
+
return Digest::MD5.hexdigest(s)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
data/safecharge.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'safecharge/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "safecharge"
|
8
|
+
spec.version = Safecharge::VERSION
|
9
|
+
spec.authors = ["Dave Sag"]
|
10
|
+
spec.email = ["davesag@gmail.com"]
|
11
|
+
spec.description = "Implements all of the features of the SafeCharge API."
|
12
|
+
spec.summary = "A Ruby Wrapper for the SafeCharge API"
|
13
|
+
spec.homepage = "http://cv.davesag.com/"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.date = Time.now.utc.strftime("%Y-%m-%d")
|
16
|
+
|
17
|
+
spec.files = `git ls-files`.split($/) - %w(.rspec .gitignore Rakefile) - `git ls-files test_data`.split($/) - `git ls-files spec`.split($/)
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
# spec.add_dependency "ox", "~> 2.0.5"
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec", "~> 2.6"
|
26
|
+
spec.add_development_dependency "dotenv"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: safecharge
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dave Sag
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-09-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.6'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.6'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: dotenv
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Implements all of the features of the SafeCharge API.
|
70
|
+
email:
|
71
|
+
- davesag@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- Gemfile
|
77
|
+
- LICENSE.txt
|
78
|
+
- README.md
|
79
|
+
- lib/safecharge.rb
|
80
|
+
- lib/safecharge/constants.rb
|
81
|
+
- lib/safecharge/request.rb
|
82
|
+
- lib/safecharge/response.rb
|
83
|
+
- lib/safecharge/version.rb
|
84
|
+
- lib/safecharge/wc_request.rb
|
85
|
+
- safecharge.gemspec
|
86
|
+
homepage: http://cv.davesag.com/
|
87
|
+
licenses:
|
88
|
+
- MIT
|
89
|
+
metadata: {}
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 2.0.7
|
107
|
+
signing_key:
|
108
|
+
specification_version: 4
|
109
|
+
summary: A Ruby Wrapper for the SafeCharge API
|
110
|
+
test_files: []
|