atpay_tokens 0.0.7
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/.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
|
+
[![Build Status](https://travis-ci.org/atpay/atpay-client.png)](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
|