atpay_tokens 0.0.7
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/.gitignore +5 -0
- data/.travis.yml +4 -0
- data/Gemfile +12 -0
- data/README.md +105 -0
- data/Rakefile +13 -0
- data/atpay_tokens.gemspec +14 -0
- data/bin/atpay-client.rb +216 -0
- data/config/credentials.yml +4 -0
- data/lib/atpay/config.rb +55 -0
- data/lib/atpay/security_key.rb +108 -0
- data/lib/atpay/session.rb +38 -0
- data/lib/atpay/tokenator.rb +186 -0
- data/lib/atpay_tokens.rb +5 -0
- data/spec/atpay/config_spec.rb +63 -0
- data/spec/atpay/security_key_spec.rb +60 -0
- data/spec/atpay/session_spec.rb +40 -0
- data/spec/atpay/tokenator_spec.rb +142 -0
- data/spec/helper.rb +46 -0
- metadata +94 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 629a0137006e92782fffe3b0012381272b1f9dbf
|
4
|
+
data.tar.gz: d06442cc7083d78c21d230b1494026404b63381a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 58779323c57e6b2ca635d38a69cf6748912dc890c788724e559bcb9ae2ccb8132933a44d80c795b8d8c6e466c80d837be0c56469ed190458e647e79bd99c5621
|
7
|
+
data.tar.gz: a187a35f082ce3f9a7a96bb656dc24f8af3345427f88f428491ab43c3735fef64dfa4b9935478622e2c4ed9051742d6a7e89004ff66c9a82358f37ca90e31f9d
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
gem 'rspec'
|
6
|
+
gem 'rspec-mocks'
|
7
|
+
gem 'rbnacl', :github => "cryptosphere/rbnacl", :ref => "907bf0c07dc058ad0efbdef47ebc431444c2f696"
|
8
|
+
gem 'ruby-prof'
|
9
|
+
gem 'simplecov', :require => false, :group => :test
|
10
|
+
gem 'bundler'
|
11
|
+
gem 'rake'
|
12
|
+
gem 'coveralls', require: false
|
data/README.md
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# @Pay API Client
|
2
|
+
|
3
|
+
[](https://travis-ci.org/atpay/atpay-client)
|
4
|
+
|
5
|
+
|
6
|
+
Client interface for the @Pay API and key generation for
|
7
|
+
performance optimization. This library is designed for advanced
|
8
|
+
implementation of the @Pay API, with the primary purpose
|
9
|
+
of enhancing performance for high-traffic, heavily utilized
|
10
|
+
platforms.
|
11
|
+
|
12
|
+
Interfaces here are implemented after receiving OAuth 2.0
|
13
|
+
privileges for the partner/user record. You cannot authenticate
|
14
|
+
directly to the API with this library at this moment.
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add the 'atpay-client' gem to your Gemfile:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
#Gemfile
|
22
|
+
|
23
|
+
gem 'atpay', :github => "atpay/atpay-client"
|
24
|
+
```
|
25
|
+
|
26
|
+
## Configuration
|
27
|
+
|
28
|
+
With the `keys` scope, authenticate with OAuth, and make a request
|
29
|
+
to the 'keys' endpoint (see the api documentation at
|
30
|
+
https://developer.atpay.com) to receive the partner_id,
|
31
|
+
public key and private key.
|
32
|
+
|
33
|
+
Apply these values to your configuration
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
session = AtPay::Session.new({
|
37
|
+
:environment => :sandbox, # Either :sandbox or :production
|
38
|
+
:partner_id => 1234, # Integer value partner id
|
39
|
+
:public_key => "XXX", # Provided public key
|
40
|
+
:private_key => "YYY" # Provided private key
|
41
|
+
})
|
42
|
+
```
|
43
|
+
|
44
|
+
## Usage
|
45
|
+
|
46
|
+
In order for an @Pay user to make a purchase via email, they'll
|
47
|
+
need to send a specially crafted key to @Pay's address. You can
|
48
|
+
either use the OAuth API endpoints to generate buttons and keys,
|
49
|
+
or you can generate the keys yourself. In a high traffic
|
50
|
+
environment you'll want to generate keys locally.
|
51
|
+
|
52
|
+
Let's assume you have a member with an @Pay account, and you
|
53
|
+
would like to include a $20.00 purchase in an email to them:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
@key = session.security_key(:amount => 20.00, :email => "test@example.com")
|
57
|
+
```
|
58
|
+
|
59
|
+
Now, include the `@key` in a mailto link within the email
|
60
|
+
addressed to transaction@payments.atpay.com. Your user will
|
61
|
+
make the purchase by clicking the mailto and sending the
|
62
|
+
email.
|
63
|
+
|
64
|
+
## Expiration
|
65
|
+
|
66
|
+
Keys will expire by default after two weeks. To extend or
|
67
|
+
shorten the expiration time of your offer, just use the
|
68
|
+
expires option and provide a unix timestamp representing the
|
69
|
+
desired expiration:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
@key = session.security_key({
|
73
|
+
:amount => 20.00,
|
74
|
+
:email => "test@example.com",
|
75
|
+
:expires => (Time.now.to_i + (3600 * 5)) # Expire in 5 hours
|
76
|
+
})
|
77
|
+
```
|
78
|
+
|
79
|
+
## Key Groups
|
80
|
+
|
81
|
+
You can generate groups of key values for a user that will automatically
|
82
|
+
invalidate all members of the group when one key is processed. This
|
83
|
+
is useful when sending out multiple keys via email when only one key should ever
|
84
|
+
be processed:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
@keys = session.security_key({
|
88
|
+
:amount => [20.00, 30.00, 40.00],
|
89
|
+
:email => "test@example.com"
|
90
|
+
})
|
91
|
+
|
92
|
+
# returns array length == 3
|
93
|
+
```
|
94
|
+
|
95
|
+
## User Data
|
96
|
+
|
97
|
+
You can pass in arbitrary data that will be returned to you upon the successful parsing of a token in @Pay's system. There is a limit of 2500 characters on this argument. It is expected to be a string beyond that any formatting should be returned as it was received.
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
@key = session.security_key({
|
101
|
+
:amount => 20.00,
|
102
|
+
:email => 'email@address',
|
103
|
+
:user_data => "{ sku: '82', cid: '3', notes: 'expedited' } "
|
104
|
+
})
|
105
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "rake/clean"
|
2
|
+
require "rbnacl/rake_tasks"
|
3
|
+
|
4
|
+
file "lib/libsodium.so" => :build_libsodium do
|
5
|
+
cp $LIBSODIUM_PATH, "lib/libsodium.so"
|
6
|
+
end
|
7
|
+
|
8
|
+
task "ci:sodium" => "lib/libsodium.so"
|
9
|
+
|
10
|
+
task :ci => %w(ci:sodium spec)
|
11
|
+
|
12
|
+
|
13
|
+
CLEAN.add "lib/libsodium.*"
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'atpay_tokens'
|
3
|
+
s.version = '0.0.7'
|
4
|
+
s.date = '2013-07-25'
|
5
|
+
s.summary = "@Pay Token Generator"
|
6
|
+
s.description = "Client interface for the @Pay API, key generation for performance optimization"
|
7
|
+
s.authors = ["James Kassemi", "Glen Holcomb"]
|
8
|
+
s.email = 'james@atpay.com'
|
9
|
+
s.files = `git ls-files`.split($/)
|
10
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
11
|
+
s.homepage = "https://atpay.com"
|
12
|
+
s.add_dependency "rbnacl"
|
13
|
+
s.add_runtime_dependency 'ffi'
|
14
|
+
end
|
data/bin/atpay-client.rb
ADDED
@@ -0,0 +1,216 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'atpay'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
|
7
|
+
class Arguments
|
8
|
+
attr_reader :values
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@values = YAML.load_file(File.expand_path('../../config/credentials.yml', __FILE__))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module Usage
|
16
|
+
USAGE = <<-EOS
|
17
|
+
|
18
|
+
The @Pay Client will generate @Pay Tokens for you. You can use this tool to generate both email
|
19
|
+
and site tokens. There are quite a few arguments but only a handfull are required:
|
20
|
+
|
21
|
+
|
22
|
+
Examples:
|
23
|
+
ruby atpay-client.rb email_token targets bob@bob.com amount 8.0 expires 1454673223
|
24
|
+
ruby atpay-client.rb email_token targets bob@bob.com group "8.0 9.5 23.28"
|
25
|
+
ruby atpay-client.rb email_token cards OGQ3OWE0OWNhMFFTL4mMpQA= amount 12.0
|
26
|
+
ruby atpay-client.rb email_token cards OGQ3OWE0OWNhMFFTL4mMpQA= targets bob@bob.com amount 12.0
|
27
|
+
|
28
|
+
ruby atpay-client.rb site_token cards OGQ3OWE0OWNhMFFTL4mMpQA= amount 5.0 user_agent "curl/7.9.8" lang "en-US,en;q=0.8" charset "utf8" addr 173.163.242.213
|
29
|
+
|
30
|
+
|
31
|
+
Required:
|
32
|
+
site_token|email_token You must specify the type of token you wish to generate
|
33
|
+
cards|targets You must at least specify a card or email address. For a site token
|
34
|
+
you must provide a card. If you are building multiple tokens ensure
|
35
|
+
that you provide a card token for each email and that the order is
|
36
|
+
correct.
|
37
|
+
amount|group You also need to specify either a single amount or a group of amounts.
|
38
|
+
|
39
|
+
|
40
|
+
Required (for site token):
|
41
|
+
user_agent The HTTP_USER_AGENT from the client's request header.
|
42
|
+
lang The HTTP_ACCEPT_LANGUAGE from the client's request header.
|
43
|
+
charset The HTTP_ACCEPT_CHARSET from the client's request header.
|
44
|
+
addr The IP address corresponding to the requesting source (The user's IP)
|
45
|
+
|
46
|
+
|
47
|
+
Optional:
|
48
|
+
expires This is the expiration date. This should be an integer value of
|
49
|
+
seconds since epoch.
|
50
|
+
|
51
|
+
EOS
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
class ClientRunner
|
56
|
+
OPTIONS = %w(expires group amount)
|
57
|
+
HEADERS = %w(HTTP_USER_AGENT HTTP_ACCEPT_LANGUAGE HTTP_ACCEPT_CHARSET)
|
58
|
+
EMAIL = /[\w\d\.]+@[\w\.]+[\w]+/
|
59
|
+
CARD = /.*=$/
|
60
|
+
|
61
|
+
def initialize(args)
|
62
|
+
@config = Arguments.new
|
63
|
+
@session = AtPay::Session.new(@config.values)
|
64
|
+
@options = {}
|
65
|
+
@site_params = [{}]
|
66
|
+
@targets = []
|
67
|
+
|
68
|
+
parse args
|
69
|
+
end
|
70
|
+
|
71
|
+
# Parse our arguments for all of our values
|
72
|
+
def parse(args)
|
73
|
+
OPTIONS.each do |option|
|
74
|
+
@options[option.to_sym] = args[args.index(option) + 1] unless args.grep(option).empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
site_params args if ARGV[0] == 'site_token'
|
78
|
+
convert_amounts
|
79
|
+
convert_expiration
|
80
|
+
get_emails args
|
81
|
+
get_cards args
|
82
|
+
|
83
|
+
unless @options[:card] or @options[:email]
|
84
|
+
puts USAGE and exit
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Build our list of email addresses to generate tokens for
|
89
|
+
def get_emails(args)
|
90
|
+
return unless args.index('targets')
|
91
|
+
|
92
|
+
args[args.index('targets') + 1].split(' ')[0].match(EMAIL) ? @options[:email] = args[(args.index('targets') + 1)].split(' ') : @options[:email] = File.read(args[args.index('targets') + 1]).split("\n")
|
93
|
+
end
|
94
|
+
|
95
|
+
# Grab all the card tokens
|
96
|
+
def get_cards(args)
|
97
|
+
return unless args.index('cards')
|
98
|
+
|
99
|
+
args[args.index('cards') + 1].split(' ')[0].match(CARD) ? @options[:card] = args[(args.index('cards') + 1)].split(' ') : @options[:card] = File.read(args[args.index('cards') + 1]).split("\n")
|
100
|
+
end
|
101
|
+
|
102
|
+
# Need expiration as an integer
|
103
|
+
def convert_expiration
|
104
|
+
@options[:expires] = @options[:expires].to_i if @options[:expires]
|
105
|
+
end
|
106
|
+
|
107
|
+
# Convert any amounts to floats.
|
108
|
+
def convert_amounts
|
109
|
+
@options[:amount] = @options[:amount].to_f if @options[:amount]
|
110
|
+
@options[:amount] = @options[:group].split(' ').map(&:to_f) if @options[:group]
|
111
|
+
|
112
|
+
@options.delete :group
|
113
|
+
end
|
114
|
+
|
115
|
+
# Check our input for site params.
|
116
|
+
def site_params(args)
|
117
|
+
if args.grep('addr').empty?
|
118
|
+
puts "You must provide an IP Address for a site token.\n" + USAGE
|
119
|
+
exit
|
120
|
+
end
|
121
|
+
|
122
|
+
@site_params[1] = args[args.index('addr') + 1]
|
123
|
+
|
124
|
+
headers(args)
|
125
|
+
end
|
126
|
+
|
127
|
+
def headers(args)
|
128
|
+
if args.grep('user_agent').empty? or args.grep('charset').empty? or args.grep('lang').empty?
|
129
|
+
puts "You must provide a user_agent, charset, and lang for a site token.\n" + USAGE
|
130
|
+
exit
|
131
|
+
end
|
132
|
+
|
133
|
+
@site_params[0][HEADERS[0]] = args[args.index('user_agent') + 1]
|
134
|
+
@site_params[0][HEADERS[1]] = args[args.index('lang') + 1]
|
135
|
+
@site_params[0][HEADERS[2]] = args[args.index('charset') + 1]
|
136
|
+
end
|
137
|
+
|
138
|
+
# Generate multiple site tokens
|
139
|
+
def site_tokens
|
140
|
+
options = @options.clone
|
141
|
+
|
142
|
+
@options[:card].each_with_index do |card, index|
|
143
|
+
options[:card] = card
|
144
|
+
options[:email] = @options[:email][index] if @options[:email]
|
145
|
+
|
146
|
+
site_token options
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Generate a site token
|
151
|
+
def site_token(options = @options)
|
152
|
+
if options[:card].is_a? Array
|
153
|
+
site_tokens
|
154
|
+
return
|
155
|
+
end
|
156
|
+
|
157
|
+
keys = security_key(options)
|
158
|
+
|
159
|
+
if keys.is_a? Array
|
160
|
+
puts keys.map { |key| key.site_token @site_params[1], @site_params[0] }
|
161
|
+
else
|
162
|
+
puts keys.site_token @site_params[1], @site_params[0]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Generate multiple email tokens
|
167
|
+
def email_tokens
|
168
|
+
options = @options.clone
|
169
|
+
|
170
|
+
@options[:email].each_with_index do |email, index|
|
171
|
+
options[:email] = email
|
172
|
+
options[:card] = @options[:card][index] if @options[:card]
|
173
|
+
|
174
|
+
email_token options
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Generate an email token
|
179
|
+
def email_token(options = @options)
|
180
|
+
if options[:email].is_a? Array
|
181
|
+
email_tokens
|
182
|
+
return
|
183
|
+
end
|
184
|
+
|
185
|
+
keys = security_key(options)
|
186
|
+
|
187
|
+
if keys.is_a? Array
|
188
|
+
puts keys.map { |key| key.email_token }
|
189
|
+
else
|
190
|
+
puts keys.email_token
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Get a security key object we can ask to generate keys for us.
|
195
|
+
def security_key(options = @options)
|
196
|
+
@session.security_key(options)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
operation = ARGV[0]
|
202
|
+
arguments = ARGV[1..-1]
|
203
|
+
|
204
|
+
unless operation
|
205
|
+
puts Usage::USAGE
|
206
|
+
exit
|
207
|
+
end
|
208
|
+
|
209
|
+
unless arguments.length > 0 and arguments.length % 2 == 0
|
210
|
+
puts Usage::USAGE
|
211
|
+
exit
|
212
|
+
end
|
213
|
+
|
214
|
+
runner = ClientRunner.new arguments
|
215
|
+
|
216
|
+
runner.send operation
|
data/lib/atpay/config.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module AtPay
|
4
|
+
class Config
|
5
|
+
class << self
|
6
|
+
def base64_decoding_attr_accessor(*names)
|
7
|
+
names.each do |name|
|
8
|
+
attr_reader name
|
9
|
+
|
10
|
+
define_method "#{name}=" do |v|
|
11
|
+
instance_variable_set("@#{name}", Base64.decode64(v))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :partner_id
|
18
|
+
|
19
|
+
base64_decoding_attr_accessor :private_key,
|
20
|
+
:public_key,
|
21
|
+
:atpay_private_key,
|
22
|
+
:atpay_public_key
|
23
|
+
|
24
|
+
def initialize(options)
|
25
|
+
options.each do |k,v|
|
26
|
+
self.send("#{k.to_s}=", v)
|
27
|
+
end
|
28
|
+
|
29
|
+
unless options[:environment] or (atpay_private_key and atpay_public_key)
|
30
|
+
self.environment = :sandbox
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def partner_id=(v)
|
35
|
+
@partner_id = v
|
36
|
+
end
|
37
|
+
|
38
|
+
def environment=(v)
|
39
|
+
@environment = v
|
40
|
+
|
41
|
+
raise ValueError unless [:production, :sandbox, :development, :test].include? v
|
42
|
+
|
43
|
+
@atpay_public_key = Base64.decode64({
|
44
|
+
production: "QZuSjGhUz2DKEvjule1uRuW+N6vCOoMuR2PgCl57vB0=",
|
45
|
+
sandbox: "x3iJge6NCMx9cYqxoJHmFgUryVyXqCwapGapFURYh18=",
|
46
|
+
development: "x3iJge6NCMx9cYqxoJHmFgUryVyXqCwapGapFURYh18=",
|
47
|
+
test: '8LkeQ7BDO8+e+WRFLWV6Ac4Aq8Ev0odtWOiR1adDYyI='
|
48
|
+
}[v])
|
49
|
+
|
50
|
+
if @environment == :test
|
51
|
+
@atpay_private_key ||= Base64.decode64('bSyQWtGrWsYfJSZisrZ5eKHKcjtZQv1RO299tJ9bqIg=')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'rbnacl'
|
2
|
+
require 'base64'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module AtPay
|
6
|
+
class SecurityKey
|
7
|
+
def initialize(session, options)
|
8
|
+
raise ArgumentError.new("User Data can't exceed 2500 characters.") if options[:user_data] and options[:user_data].length > 2500
|
9
|
+
raise ArgumentError.new("email") unless options[:email].nil? or options[:email] =~ /.+@.+/
|
10
|
+
raise ArgumentError.new("amount") unless options[:amount].is_a? Float
|
11
|
+
raise ArgumentError.new("card or email or member required") if options[:email].nil? and options[:card].nil? and options[:member].nil?
|
12
|
+
|
13
|
+
@session = session
|
14
|
+
@options = options
|
15
|
+
end
|
16
|
+
|
17
|
+
def email_token
|
18
|
+
"@#{version}#{Base64.strict_encode64([nonce, partner_frame, body_frame].join)}"
|
19
|
+
ensure
|
20
|
+
@nonce = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def site_token(remote_addr, headers)
|
24
|
+
raise ArgumentError.new("card or member required for site tokens") if @options[:card].nil? and @options[:member].nil?
|
25
|
+
"@#{version}#{Base64.strict_encode64([nonce, partner_frame, site_frame(remote_addr, headers), body_frame].join)}"
|
26
|
+
ensure
|
27
|
+
@nonce = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
email_token
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
private
|
36
|
+
def version
|
37
|
+
@options[:version] ? (Base64.strict_encode64([@options[:version]].pack("Q>")) + '-') : nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def partner_frame
|
41
|
+
[@session.config.partner_id].pack("Q>")
|
42
|
+
end
|
43
|
+
|
44
|
+
def site_frame(remote_addr, headers)
|
45
|
+
message = boxer.box(nonce, Digest::SHA1.hexdigest([
|
46
|
+
headers["HTTP_USER_AGENT"],
|
47
|
+
headers["HTTP_ACCEPT_LANGUAGE"],
|
48
|
+
headers["HTTP_ACCEPT_CHARSET"],
|
49
|
+
remote_addr
|
50
|
+
].join))
|
51
|
+
|
52
|
+
[[message.length].pack("l>"), message,
|
53
|
+
[remote_addr.length].pack("l>"), remote_addr].join
|
54
|
+
end
|
55
|
+
|
56
|
+
def body_frame
|
57
|
+
boxer.box(nonce, crypted_frame)
|
58
|
+
end
|
59
|
+
|
60
|
+
def crypted_frame
|
61
|
+
if user_data = user_data_frame
|
62
|
+
[target, options_group, '/', options_frame, '/', user_data].flatten.compact.join
|
63
|
+
else
|
64
|
+
[target, options_group, "/", options_frame].flatten.compact.join
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def options_frame
|
69
|
+
[@options[:amount], expires].pack("g l>")
|
70
|
+
end
|
71
|
+
|
72
|
+
def user_data_frame
|
73
|
+
@options[:user_data].to_s if @options[:user_data]
|
74
|
+
end
|
75
|
+
|
76
|
+
def options_group
|
77
|
+
":#{@options[:group]}" if @options[:group]
|
78
|
+
end
|
79
|
+
|
80
|
+
def target
|
81
|
+
card_format || member_format || email_format
|
82
|
+
end
|
83
|
+
|
84
|
+
def card_format
|
85
|
+
"card<#{@options[:card]}>" if @options[:card]
|
86
|
+
end
|
87
|
+
|
88
|
+
def member_format
|
89
|
+
"member<#{@options[:member]}>" if @options[:member]
|
90
|
+
end
|
91
|
+
|
92
|
+
def email_format
|
93
|
+
"email<#{@options[:email]}>" if @options[:email]
|
94
|
+
end
|
95
|
+
|
96
|
+
def expires
|
97
|
+
@options[:expires] || (Time.now.to_i + 3600 * 24 * 7)
|
98
|
+
end
|
99
|
+
|
100
|
+
def boxer
|
101
|
+
@session.boxer
|
102
|
+
end
|
103
|
+
|
104
|
+
def nonce
|
105
|
+
@nonce ||= SecureRandom.random_bytes(24)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'atpay/config'
|
2
|
+
require 'atpay/security_key'
|
3
|
+
|
4
|
+
module AtPay
|
5
|
+
class Session
|
6
|
+
attr_accessor :config
|
7
|
+
|
8
|
+
def initialize(options)
|
9
|
+
@config = Config.new(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def security_keys(options)
|
13
|
+
options = options.clone
|
14
|
+
|
15
|
+
options[:group] ||= "#{SecureRandom.uuid.gsub("-", "")}-#{Time.now.to_i}"
|
16
|
+
|
17
|
+
keys = []
|
18
|
+
|
19
|
+
options[:amount].each do |amount|
|
20
|
+
keys << security_key(options.update(:amount => amount))
|
21
|
+
end
|
22
|
+
|
23
|
+
keys
|
24
|
+
end
|
25
|
+
|
26
|
+
def security_key(options)
|
27
|
+
if options[:amount].is_a? Array
|
28
|
+
security_keys(options)
|
29
|
+
else
|
30
|
+
SecurityKey.new(self, options.update(:amount => options[:amount]))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def boxer
|
35
|
+
@boxer ||= Crypto::Box.new(config.atpay_public_key, config.private_key)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
module AtPay
|
2
|
+
class Tokenator
|
3
|
+
attr_reader :token,
|
4
|
+
:payload,
|
5
|
+
:partner_id,
|
6
|
+
:source,
|
7
|
+
:amount,
|
8
|
+
:expires,
|
9
|
+
:group,
|
10
|
+
:site_frame,
|
11
|
+
:ip,
|
12
|
+
:user_data
|
13
|
+
|
14
|
+
# A bit clunky but useful for testing token decomposition.
|
15
|
+
# If you provide a test session then the config values there
|
16
|
+
# will be used so that decryption will function without @Pay's
|
17
|
+
# private key.
|
18
|
+
def initialize(token, session = nil)
|
19
|
+
@token = token
|
20
|
+
@session = session
|
21
|
+
strip_version
|
22
|
+
|
23
|
+
@checksum = Digest::SHA1.hexdigest(token) # Before or after removing version?
|
24
|
+
end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
# Get the version of the given token.
|
28
|
+
def token_version(token)
|
29
|
+
token.scan('-').empty? ? 0 : unpack_version(token.split('-')[0])
|
30
|
+
end
|
31
|
+
|
32
|
+
# Check and make sure we haven't seen this token before.
|
33
|
+
# NOTE: This is really for internal use by @Pay.
|
34
|
+
def find_by_checksum(token)
|
35
|
+
checksum = Digest::SHA1.hexdigest(token)
|
36
|
+
|
37
|
+
SecurityKey.find_by_encoded_key(checksum) || ValidationToken.find_by_encoded_key(checksum)
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Unpack the actual version value.
|
44
|
+
def unpack_version(version)
|
45
|
+
Base64.decode64(version[1..-1]).unpack("Q>")[0]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
# We want to pull the header out of the token. This means we
|
51
|
+
# grab the nonce and the partner id from the token. The version
|
52
|
+
# frame should be removed before calling header.
|
53
|
+
def header
|
54
|
+
decode
|
55
|
+
nonce
|
56
|
+
destination
|
57
|
+
end
|
58
|
+
|
59
|
+
# Here we parse the body of the token. All the useful shit
|
60
|
+
# comes out of this.
|
61
|
+
def body(key)
|
62
|
+
payload(nonce, key, @token)
|
63
|
+
part_out_payload
|
64
|
+
end
|
65
|
+
|
66
|
+
# With a site token you want to call this after header. It will
|
67
|
+
# pull the ip address and header sha out of the token.
|
68
|
+
def browser_data(key)
|
69
|
+
length = @token.slice!(0, 4).unpack("l>")[0]
|
70
|
+
@site_frame = boxer(key).open(nonce, @token.slice!(0, length))
|
71
|
+
|
72
|
+
length = @token.slice!(0, 4).unpack("l>")[0]
|
73
|
+
@ip = @token.slice!(0, length)
|
74
|
+
end
|
75
|
+
|
76
|
+
def source
|
77
|
+
{email: @email, card: @card, member: @member}
|
78
|
+
end
|
79
|
+
|
80
|
+
# Return parts in a hash structure, handy for ActiveRecord.
|
81
|
+
def to_h
|
82
|
+
{
|
83
|
+
sale_price: @amount,
|
84
|
+
expires_at: Time.at(@expires),
|
85
|
+
group: @group,
|
86
|
+
encoded_key: @checksum
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# Strip the version frame from the token.
|
94
|
+
def strip_version
|
95
|
+
@token = @token.split('-').last
|
96
|
+
end
|
97
|
+
|
98
|
+
# Fix our Base64 problems
|
99
|
+
def decode
|
100
|
+
@token = Base64.decode64 @token
|
101
|
+
end
|
102
|
+
|
103
|
+
# Extract our entropy
|
104
|
+
def nonce
|
105
|
+
@nonce ||= @token.slice!(0, 24)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Find the recipient of the betokened transaction
|
109
|
+
def destination
|
110
|
+
nonce unless @nonce
|
111
|
+
|
112
|
+
#@partner ||= OpportunityMap.find(@token.slice!(0, 8).unpack("Q>")[0]).opportunity
|
113
|
+
@partner_id ||= @token.slice!(0, 8).unpack("Q>")[0]
|
114
|
+
end
|
115
|
+
|
116
|
+
def boxer(key)
|
117
|
+
if @session
|
118
|
+
Crypto::Box.new(key, @session.config.atpay_private_key)
|
119
|
+
else
|
120
|
+
Crypto::Box.new(key, ENCRYPTION[:security_key_sec])
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Decrypt the payload.
|
125
|
+
def payload(nonce, key, decoded)
|
126
|
+
@payload = boxer(key).open(nonce, decoded)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Break the payload out into it's constituent logical parts.
|
130
|
+
def part_out_payload
|
131
|
+
# TARGET:GROUP/AMOUNTEXPIRATION/USERDATA
|
132
|
+
# TARGET:GROUP/AMOUNTEXPIRATION
|
133
|
+
# TARGET:/AMOUNTEXPIRATION (?)
|
134
|
+
# TARGET/AMOUNTEXPIRATION/USERDATA
|
135
|
+
# TARGET/AMOUNTEXPIRATION
|
136
|
+
if @payload.match '>:'
|
137
|
+
raw_target, @group = @payload.split(':', 2)
|
138
|
+
else
|
139
|
+
raw_target = @payload
|
140
|
+
end
|
141
|
+
|
142
|
+
target raw_target
|
143
|
+
|
144
|
+
if @group
|
145
|
+
@group = @payload.slice!(0, @group.index("/"))
|
146
|
+
@payload.slice!(0, 1)
|
147
|
+
end
|
148
|
+
|
149
|
+
@amount = parse_amount!
|
150
|
+
@expiration = parse_expiration!
|
151
|
+
@user_data = parse_user_data!
|
152
|
+
end
|
153
|
+
|
154
|
+
def parse_amount!
|
155
|
+
@amount = @payload.slice!(0, 4).unpack("g")[0]
|
156
|
+
end
|
157
|
+
|
158
|
+
def parse_expiration!
|
159
|
+
@expires = @payload.slice!(0, 4).unpack("l>")[0]
|
160
|
+
end
|
161
|
+
|
162
|
+
def parse_user_data!
|
163
|
+
@user_data = @payload[1..-1]
|
164
|
+
@payload = nil
|
165
|
+
return @user_data
|
166
|
+
end
|
167
|
+
|
168
|
+
# Find the target of the token. This could be a Credit Card
|
169
|
+
# Email Address or Member UUID.
|
170
|
+
def target(target)
|
171
|
+
case target
|
172
|
+
when /card<(.*?)>/
|
173
|
+
@card = $1
|
174
|
+
@payload.slice!(0, ($1.length + 7))
|
175
|
+
when /email<(.*?)>/
|
176
|
+
@email = $1
|
177
|
+
@payload.slice!(0, ($1.length + 8))
|
178
|
+
when /member<(.*?)>/
|
179
|
+
@member = $1
|
180
|
+
@payload.slice!(0, ($1.length + 9))
|
181
|
+
else
|
182
|
+
raise "No target found"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
data/lib/atpay_tokens.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe AtPay::Config do
|
4
|
+
let(:config) {
|
5
|
+
AtPay::Config.new({
|
6
|
+
:partner_id => partner_id,
|
7
|
+
:private_key => private_key,
|
8
|
+
:public_key => public_key,
|
9
|
+
:environment => :sandbox
|
10
|
+
})
|
11
|
+
}
|
12
|
+
|
13
|
+
describe "overview" do
|
14
|
+
it "accepts multiple arguments" do
|
15
|
+
AtPay::Config.any_instance.should_receive(:partner_id=).with(partner_id)
|
16
|
+
AtPay::Config.any_instance.should_receive(:private_key=).with(private_key)
|
17
|
+
AtPay::Config.any_instance.should_receive(:public_key=).with(public_key)
|
18
|
+
AtPay::Config.any_instance.should_receive(:environment=).with(:sandbox)
|
19
|
+
|
20
|
+
AtPay::Config.new({
|
21
|
+
:partner_id => partner_id,
|
22
|
+
:private_key => private_key,
|
23
|
+
:public_key => public_key,
|
24
|
+
:environment => :sandbox
|
25
|
+
})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "values" do
|
30
|
+
it "accepts partner id" do
|
31
|
+
config.partner_id = partner_id
|
32
|
+
config.partner_id.should eq(partner_id)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "accepts private key" do
|
36
|
+
config.private_key = private_key
|
37
|
+
config.private_key.should_not be_empty
|
38
|
+
end
|
39
|
+
|
40
|
+
it "accepts public key" do
|
41
|
+
config.public_key = public_key
|
42
|
+
config.public_key.should_not be_empty
|
43
|
+
end
|
44
|
+
|
45
|
+
it "accepts environment" do
|
46
|
+
config.environment = :sandbox
|
47
|
+
config.atpay_public_key.should_not be_empty
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "environment" do
|
52
|
+
it "required to be production or sandbox" do
|
53
|
+
expect {
|
54
|
+
AtPay::Config.new :environment => :none
|
55
|
+
}.to raise_error
|
56
|
+
end
|
57
|
+
|
58
|
+
it "defaults to sandbox" do
|
59
|
+
AtPay::Config.any_instance.should_receive(:environment=).with(:sandbox)
|
60
|
+
config = AtPay::Config.new({})
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# TODO: Decode utilities for further testing
|
2
|
+
|
3
|
+
require 'atpay'
|
4
|
+
require 'rbnacl'
|
5
|
+
require 'base64'
|
6
|
+
require 'helper'
|
7
|
+
|
8
|
+
describe AtPay::SecurityKey do
|
9
|
+
let(:session){
|
10
|
+
AtPay::Session.new({
|
11
|
+
:partner_id => partner_id,
|
12
|
+
:private_key => private_key,
|
13
|
+
:public_key => public_key,
|
14
|
+
:environment => :sandbox
|
15
|
+
})
|
16
|
+
}
|
17
|
+
|
18
|
+
describe "#initialize" do
|
19
|
+
it "fails when no email given" do
|
20
|
+
expect {
|
21
|
+
AtPay::SecurityKey.new(session, {
|
22
|
+
:amount => 25,
|
23
|
+
:email => nil
|
24
|
+
})
|
25
|
+
}.to raise_error
|
26
|
+
end
|
27
|
+
|
28
|
+
it "fails when no amount given" do
|
29
|
+
expect {
|
30
|
+
AtPay::SecurityKey.new(session, {
|
31
|
+
:amount => nil,
|
32
|
+
:email => "test@example.com"
|
33
|
+
})
|
34
|
+
}.to raise_error
|
35
|
+
end
|
36
|
+
|
37
|
+
it "fails when amount not float" do
|
38
|
+
expect {
|
39
|
+
AtPay::SecurityKey.new(session, {
|
40
|
+
:amount => "25",
|
41
|
+
:email => "test@example.com"
|
42
|
+
})
|
43
|
+
}.to raise_error
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#to_s" do
|
48
|
+
it "returns a valid key" do
|
49
|
+
key = AtPay::SecurityKey.new(session, {:email => "james@atpay.com", :amount => 25.00}).to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
it "returns a key with a group" do
|
53
|
+
key = AtPay::SecurityKey.new(session, {:email => "james@atpay.com", :amount => 25.00, :group => "1234"}).to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
it "returns a key with user_data" do
|
57
|
+
key = AtPay::SecurityKey.new(session, {:email => "glen@atpay.com", :amount => 25.00, :user_data => 'bacon and eggs'}).to_s
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe AtPay::Session do
|
4
|
+
let(:session) {
|
5
|
+
AtPay::Session.new({
|
6
|
+
:partner_id => partner_id,
|
7
|
+
:private_key => private_key,
|
8
|
+
:public_key => public_key,
|
9
|
+
:environment => :sandbox
|
10
|
+
})
|
11
|
+
}
|
12
|
+
|
13
|
+
it "Uses the configuration options" do
|
14
|
+
AtPay::Config.any_instance.should_receive(:initialize).with({})
|
15
|
+
AtPay::Session.new({})
|
16
|
+
end
|
17
|
+
|
18
|
+
it "Generates security key" do
|
19
|
+
session.security_key(:amount => 20.00,
|
20
|
+
:email => "james@example.com").to_s.should_not be_empty
|
21
|
+
end
|
22
|
+
|
23
|
+
it "Generates site token" do
|
24
|
+
session.security_key(:amount => 20.00,
|
25
|
+
:card => "test").site_token("0.0.0.0", {
|
26
|
+
"HTTP_USER_AGENT" => "0",
|
27
|
+
"HTTP_ACCEPT_LANGUAGE" => "1",
|
28
|
+
"HTTP_ACCEPT_CHARSET" => "2"
|
29
|
+
}).should_not be_empty
|
30
|
+
end
|
31
|
+
|
32
|
+
it "Generates multiple security keys" do
|
33
|
+
session.security_key(:amount => [20.00, 30.00],
|
34
|
+
:email => "james@example.com").length.should eq(2)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "Returns a box and caches it" do
|
38
|
+
session.boxer.object_id.should eq(session.boxer.object_id)
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'atpay'
|
2
|
+
require 'rbnacl'
|
3
|
+
require 'base64'
|
4
|
+
require 'helper'
|
5
|
+
|
6
|
+
describe AtPay::Tokenator do
|
7
|
+
let(:headers) { {'HTTP_USER_AGENT' => 'agent', 'HTTP_ACCEPT_LANGUAGE' => 'lang', 'HTTP_ACCEPT_CHARSET' => 'charset'} }
|
8
|
+
let(:ip) { '1.1.1.1' }
|
9
|
+
|
10
|
+
let(:payment) { AtPay::Tokenator.new token(50.00, [:email_token], {email: 'email@address'}), build_session }
|
11
|
+
let(:site) { AtPay::Tokenator.new token(50.00, [:site_token, ip, headers], {card: 'OGQ3OWE0OWNhMFFTL4mMpQA='}), build_session }
|
12
|
+
let(:user_data) { AtPay::Tokenator.new token(50.00, [:email_token], {email: 'email@address', user_data: 'lots of pills, paying forever'}), build_session }
|
13
|
+
let(:version) { AtPay::Tokenator.new token(50.00, [:email_token], {email: 'email@address', version: 2}), build_session }
|
14
|
+
let(:member) { AtPay::Tokenator.new token(50.00, [:email_token], {member: '4DF08A79-C16C-4842-AA1B-AE878C9C6C2C'}), build_session }
|
15
|
+
let(:group) { AtPay::Tokenator.new token(50.00, [:email_token], {member: '4DF08A79-C16C-4842-AA1B-AE878C9C6C2C', group: '18', user_data: 'hello from data'}), build_session }
|
16
|
+
|
17
|
+
describe "Parsing" do
|
18
|
+
it "Uses the key specified in ENCRYPTION if not given a session" do
|
19
|
+
tokenator = AtPay::Tokenator.new token(50.0, [:email_token], {email: 'email@address'})
|
20
|
+
|
21
|
+
expect { tokenator.send(:boxer, Base64.decode64(public_key)) }.to raise_error
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "Payment Tokens" do
|
25
|
+
it "Extracts the partner" do
|
26
|
+
payment.header
|
27
|
+
|
28
|
+
payment.instance_eval { @partner_id }.should eq(123)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "Extracts the body" do
|
32
|
+
payment.header
|
33
|
+
payment.body(Base64.decode64(public_key))
|
34
|
+
|
35
|
+
payment.source[:email].should eq('email@address')
|
36
|
+
payment.amount.should eq(50.0)
|
37
|
+
payment.expires.should_not eq(nil)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "Presents token values as a hash" do
|
41
|
+
payment.header
|
42
|
+
payment.body(Base64.decode64(public_key))
|
43
|
+
|
44
|
+
payment.to_h.should be_a(Hash)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "Processes a member token" do
|
48
|
+
member.header
|
49
|
+
member.body(Base64.decode64(public_key))
|
50
|
+
|
51
|
+
member.source[:member].should eq('4DF08A79-C16C-4842-AA1B-AE878C9C6C2C')
|
52
|
+
end
|
53
|
+
|
54
|
+
it "Processes a token with a group" do
|
55
|
+
group.header
|
56
|
+
group.body(Base64.decode64(public_key))
|
57
|
+
|
58
|
+
group.group.should eq('18')
|
59
|
+
group.user_data.should eq('hello from data')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "Exceptions" do
|
64
|
+
it "Raises target not found if there is no valid target" do
|
65
|
+
expect { payment.send :target, 'mom' }.to raise_error
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "Site Tokens" do
|
70
|
+
it "Extracts the Partner" do
|
71
|
+
site.header
|
72
|
+
|
73
|
+
site.instance_eval { @partner_id }.should eq(123)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "Extracts the site frame" do
|
77
|
+
site.header
|
78
|
+
site.browser_data(Base64.decode64(public_key))
|
79
|
+
sha = Digest::SHA1.hexdigest((headers.values + [ip]).join)
|
80
|
+
|
81
|
+
site.instance_eval { @site_frame }.should eq(sha)
|
82
|
+
site.instance_eval { @ip }.should eq(ip)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "Extracts payload after extracting site frame" do
|
86
|
+
site.header
|
87
|
+
site.browser_data(Base64.decode64(public_key))
|
88
|
+
site.body(Base64.decode64(public_key))
|
89
|
+
|
90
|
+
site.source[:card].should eq('OGQ3OWE0OWNhMFFTL4mMpQA=')
|
91
|
+
site.amount.should eq(50.0)
|
92
|
+
site.expires.should_not eq(nil)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "User Data" do
|
97
|
+
it "Extracts supplied User Data" do
|
98
|
+
user_data.header
|
99
|
+
user_data.body(Base64.decode64(public_key))
|
100
|
+
|
101
|
+
user_data.user_data.should eq('lots of pills, paying forever')
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "Versioning" do
|
106
|
+
let(:versioned) { token(50.00, [:email_token], {email: 'email@address', version: 2}) }
|
107
|
+
|
108
|
+
it "Extracts the version" do
|
109
|
+
AtPay::Tokenator.token_version(versioned).should eq(2)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "Returns 0 when there is no version" do
|
113
|
+
test_token = token(50.00, [:email_token], {email: 'email@address'})
|
114
|
+
|
115
|
+
AtPay::Tokenator.token_version(test_token).should eq(0)
|
116
|
+
end
|
117
|
+
|
118
|
+
it "Behaves as a normal token when versioned" do
|
119
|
+
version.header
|
120
|
+
version.body(Base64.decode64(public_key))
|
121
|
+
|
122
|
+
version.amount.should eq(50.0)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "Checksum lookup" do
|
127
|
+
before do
|
128
|
+
class AtPay::SecurityKey; end
|
129
|
+
class AtPay::ValidationToken; end
|
130
|
+
|
131
|
+
AtPay::SecurityKey.should_receive(:find_by_encoded_key)
|
132
|
+
AtPay::ValidationToken.should_receive(:find_by_encoded_key)
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should look for a token with a matching checksum" do
|
136
|
+
token = token(50.0, [:email_token], {email: 'email@address'})
|
137
|
+
|
138
|
+
AtPay::Tokenator.find_by_checksum(token)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
require 'coveralls'
|
3
|
+
|
4
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
5
|
+
SimpleCov::Formatter::HTMLFormatter,
|
6
|
+
Coveralls::SimpleCov::Formatter
|
7
|
+
]
|
8
|
+
|
9
|
+
SimpleCov.start
|
10
|
+
|
11
|
+
require 'rubygems'
|
12
|
+
require 'bundler/setup'
|
13
|
+
require 'atpay'
|
14
|
+
require 'rspec/core/shared_context'
|
15
|
+
|
16
|
+
module Setup
|
17
|
+
extend RSpec::Core::SharedContext
|
18
|
+
|
19
|
+
let(:partner_id) { 123 }
|
20
|
+
let(:keys) {
|
21
|
+
sec = Crypto::PrivateKey.generate
|
22
|
+
pub = sec.public_key
|
23
|
+
[pub.to_bytes, sec.to_bytes]
|
24
|
+
}
|
25
|
+
let(:public_key) { Base64.strict_encode64(keys[0]) }
|
26
|
+
let(:private_key) { Base64.strict_encode64(keys[1]) }
|
27
|
+
|
28
|
+
def token(amount, type, options = {})
|
29
|
+
build_session.security_key({
|
30
|
+
amount: amount,
|
31
|
+
}.merge(options)).send(*type).to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
def build_session
|
35
|
+
AtPay::Session.new({
|
36
|
+
public_key: public_key,
|
37
|
+
private_key: private_key,
|
38
|
+
partner_id: partner_id,
|
39
|
+
environment: :test
|
40
|
+
})
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
RSpec.configure do |r|
|
45
|
+
r.include Setup
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: atpay_tokens
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.7
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James Kassemi
|
8
|
+
- Glen Holcomb
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-07-25 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rbnacl
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - '>='
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - '>='
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: ffi
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - '>='
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
description: Client interface for the @Pay API, key generation for performance optimization
|
43
|
+
email: james@atpay.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- .gitignore
|
49
|
+
- .travis.yml
|
50
|
+
- Gemfile
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- atpay_tokens.gemspec
|
54
|
+
- bin/atpay-client.rb
|
55
|
+
- config/credentials.yml
|
56
|
+
- lib/atpay/config.rb
|
57
|
+
- lib/atpay/security_key.rb
|
58
|
+
- lib/atpay/session.rb
|
59
|
+
- lib/atpay/tokenator.rb
|
60
|
+
- lib/atpay_tokens.rb
|
61
|
+
- spec/atpay/config_spec.rb
|
62
|
+
- spec/atpay/security_key_spec.rb
|
63
|
+
- spec/atpay/session_spec.rb
|
64
|
+
- spec/atpay/tokenator_spec.rb
|
65
|
+
- spec/helper.rb
|
66
|
+
homepage: https://atpay.com
|
67
|
+
licenses: []
|
68
|
+
metadata: {}
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 2.0.2
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: '@Pay Token Generator'
|
89
|
+
test_files:
|
90
|
+
- spec/atpay/config_spec.rb
|
91
|
+
- spec/atpay/security_key_spec.rb
|
92
|
+
- spec/atpay/session_spec.rb
|
93
|
+
- spec/atpay/tokenator_spec.rb
|
94
|
+
- spec/helper.rb
|