bitpay-client 0.1.5 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.travis.yml +0 -3
- data/LICENSE.md +1 -1
- data/README.md +20 -6
- data/Rakefile +49 -8
- data/bin/bitpay +3 -0
- data/bitpay-client.gemspec +16 -4
- data/config/capybara.rb +5 -0
- data/config/constants.rb +3 -0
- data/lib/bitpay.rb +18 -1
- data/lib/bitpay/cli.rb +63 -0
- data/lib/bitpay/client.rb +125 -41
- data/lib/bitpay/key_utils.rb +143 -0
- data/lib/bitpay/version.rb +5 -1
- data/lib/harness.rb +40 -0
- data/spec/client_spec.rb +78 -0
- data/spec/features/pair_spec.rb +28 -0
- data/spec/features/pos_spec.rb +37 -0
- data/spec/features/setup_spec.rb +13 -0
- data/spec/key_utils_spec.rb +81 -0
- data/spec/set_constants.sh +10 -0
- data/spec/spec_helper.rb +25 -0
- metadata +136 -11
- data/test/bitpay/client_test.rb +0 -70
- data/test/env.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e8c99b683ba6a20ac1e7a0739e29a654d1976cae
|
4
|
+
data.tar.gz: ba0aeebf8fdbec667e0bf76500e194eb549ab25f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3def8a32c76c98d4c43ece8785641cd8499d48bf5ce93525c67c1a6a48c0238ebc07964ae938e9144a671b28b13d3efc44c10c7cfded72dc5aa4dbf0564e7ba6
|
7
|
+
data.tar.gz: 11e62b62bbe443fb1d8a7f8ffbcbda5218ec64263dec0605444f2490193a1346bccd756003a8c0cee7827edd3d699bba55e2833c4c634e446dcebe34a3865566
|
data/.gitignore
CHANGED
data/LICENSE.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (C)
|
1
|
+
Copyright (C) 2014 BitPay
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
4
|
|
data/README.md
CHANGED
@@ -13,12 +13,26 @@ Or directly:
|
|
13
13
|
|
14
14
|
require 'bitpay'
|
15
15
|
|
16
|
+
## Configuration
|
17
|
+
|
18
|
+
The bitpay client creates a cryptographically secure connection to your server by pairing an API code with keys stored on your server. The library generates the keys as a .pem file, which is stored in `$HOME/.bitpay/bitpay.pem` or preferentially in an environment variable.
|
19
|
+
|
20
|
+
The client will generate a key when initialized if one does not already exist.
|
21
|
+
|
16
22
|
## Basic Usage
|
17
23
|
|
18
|
-
|
24
|
+
### Pairing with Bitpay.com
|
25
|
+
|
26
|
+
To pair with bitpay.com you need to have an approved merchant account.
|
27
|
+
1. Login to your account
|
28
|
+
1. Navigate to bitpay.com/api-tokens (Dashboard > My Account > API Tokens)
|
29
|
+
1. Copy an existing pairing code or create a new token and copy the pairing code.
|
30
|
+
1. Use the bitpay command line tool to pair with bitpay.com `bitpay pair <pairing_code>`
|
31
|
+
|
32
|
+
### To create an invoice with a paired client:
|
19
33
|
|
20
|
-
client = BitPay::Client.new
|
21
|
-
invoice = client.
|
34
|
+
client = BitPay::Client.new
|
35
|
+
invoice = client.create_invoice (id: <id>, price: <price>, currency: <currency>, facade: <facade>)
|
22
36
|
|
23
37
|
With invoice creation, `price` and `currency` are the only required fields. If you are sending a customer from your website to make a purchase, setting `redirectURL` will redirect the customer to your website when the invoice is paid.
|
24
38
|
|
@@ -40,7 +54,7 @@ There are many options available when creating invoices, which are listed in the
|
|
40
54
|
|
41
55
|
To get updated information on this invoice, make a get call with the id returned:
|
42
56
|
|
43
|
-
invoice = client.
|
57
|
+
invoice = client.get_public_invoice(DGrAEmbsXe9bavBPMJ8kuk)'
|
44
58
|
|
45
59
|
## Testnet Usage
|
46
60
|
|
@@ -48,11 +62,11 @@ During development and testing, take advantage of the [Bitcoin TestNet](https://
|
|
48
62
|
|
49
63
|
BitPay::Client.new("myAPIKey", {api_uri: "https://test.bitpay.com/api"})
|
50
64
|
|
51
|
-
Note that you will need a
|
65
|
+
Note that in order to pair with testnet, you will need a pairing code from test.bitpay.com and will need to use the bitpay client with the --test option.
|
52
66
|
|
53
67
|
## API Documentation
|
54
68
|
|
55
|
-
API Documentation is available on the [BitPay site](https://bitpay.com/
|
69
|
+
API Documentation is available on the [BitPay site](https://bitpay.com/api).
|
56
70
|
|
57
71
|
## RDoc/YARD Documentation
|
58
72
|
The code has been fully code documented, and the latest version is always available at the [Rubydoc Site](http://rubydoc.info/gems/bitpay-client).
|
data/Rakefile
CHANGED
@@ -1,11 +1,52 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
-
require
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'capybara'
|
4
|
+
require 'capybara/poltergeist'
|
5
|
+
require 'mongo'
|
3
6
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
require_relative 'config/constants.rb'
|
8
|
+
require_relative 'config/capybara.rb'
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new(:spec)
|
11
|
+
|
12
|
+
task :default => :spec
|
13
|
+
|
14
|
+
desc "Bitpay Tasks"
|
15
|
+
namespace :bitpay do
|
16
|
+
|
17
|
+
desc "Clear all claim codes from the test server."
|
18
|
+
task :clear_claim_codes do
|
19
|
+
puts "clearing claim codes"
|
20
|
+
Capybara.visit ROOT_ADDRESS
|
21
|
+
Capybara.click_link('Login')
|
22
|
+
Capybara.fill_in 'email', :with => TEST_USER
|
23
|
+
Capybara.fill_in 'password', :with => TEST_PASS
|
24
|
+
Capybara.click_button('loginButton')
|
25
|
+
Capybara.click_link "My Account"
|
26
|
+
Capybara.click_link "API Tokens", match: :first
|
27
|
+
while Capybara.page.has_selector?(".token-claimcode") || Capybara.page.has_selector?(".token-requiredsins-key") do
|
28
|
+
Capybara.page.find(".api-manager-actions-edit", match: :first).click
|
29
|
+
Capybara.page.find(".api-manager-actions-revoke", match: :first).click
|
30
|
+
Capybara.click_button("Confirm Revoke")
|
31
|
+
# this back and forth bit is here because no other reload mechanism worked, and without it the task errors out: either because it can't find the revoke button or it finds multiple elements at each click point
|
32
|
+
Capybara.page.go_back
|
33
|
+
Capybara.click_link "API Tokens", match: :first
|
34
|
+
end
|
35
|
+
puts "claim codes cleared"
|
36
|
+
end
|
10
37
|
|
11
|
-
|
38
|
+
desc "Clear rate limiters from local mongo host"
|
39
|
+
|
40
|
+
task :clear_rate_limiters do
|
41
|
+
puts "clearing rate limiters"
|
42
|
+
client = Mongo::MongoClient.new
|
43
|
+
db = client['bitpay-dev']
|
44
|
+
coll = db['ratelimiters']
|
45
|
+
coll.remove()
|
46
|
+
puts "rate limiters cleared"
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Run specs and clear claim codes and rate_limiters."
|
50
|
+
task :spec_clear => ['spec', 'clear_claim_codes', 'clear_rate_limiters']
|
51
|
+
|
52
|
+
end
|
data/bin/bitpay
ADDED
data/bitpay-client.gemspec
CHANGED
@@ -2,6 +2,7 @@ require './lib/bitpay/version.rb'
|
|
2
2
|
Gem::Specification.new do |s|
|
3
3
|
s.name = 'bitpay-client'
|
4
4
|
s.version = BitPay::VERSION
|
5
|
+
s.licenses = ['MIT']
|
5
6
|
s.authors = 'Bitpay, Inc.'
|
6
7
|
s.email = 'info@bitpay.com'
|
7
8
|
s.homepage = 'https://github.com/bitpay/ruby-client'
|
@@ -9,15 +10,26 @@ Gem::Specification.new do |s|
|
|
9
10
|
s.description = 'Powerful, flexible, lightweight, thread-safe interface to the BitPay developers API'
|
10
11
|
|
11
12
|
s.files = `git ls-files`.split("\n")
|
12
|
-
s.require_paths =
|
13
|
+
s.require_paths = ["lib"]
|
13
14
|
s.rubyforge_project = s.name
|
14
15
|
s.required_rubygems_version = '>= 1.3.4'
|
16
|
+
s.required_ruby_version = '~> 2.1'
|
17
|
+
s.bindir = 'bin'
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
15
19
|
|
16
20
|
s.add_dependency 'json'
|
17
21
|
s.add_dependency 'rack', '>= 0'
|
22
|
+
s.add_dependency 'ecdsa'
|
23
|
+
s.add_dependency 'commander'
|
18
24
|
|
19
25
|
s.add_development_dependency 'rake'
|
20
|
-
s.add_development_dependency 'minitest'
|
21
26
|
s.add_development_dependency 'webmock'
|
22
|
-
s.add_development_dependency '
|
23
|
-
|
27
|
+
s.add_development_dependency 'pry'
|
28
|
+
s.add_development_dependency 'pry-byebug'
|
29
|
+
s.add_development_dependency 'pry-rescue'
|
30
|
+
s.add_development_dependency 'capybara'
|
31
|
+
s.add_development_dependency 'poltergeist'
|
32
|
+
s.add_development_dependency 'airborne'
|
33
|
+
s.add_development_dependency 'rspec'
|
34
|
+
s.add_development_dependency 'mongo'
|
35
|
+
end
|
data/config/capybara.rb
ADDED
data/config/constants.rb
ADDED
data/lib/bitpay.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# license Copyright 2011-2014 BitPay, Inc., MIT License
|
2
|
+
# see http://opensource.org/licenses/MIT
|
3
|
+
# or https://github.com/bitpay/php-bitpay-client/blob/master/LICENSE
|
4
|
+
|
1
5
|
libdir = File.dirname(__FILE__)
|
2
6
|
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
3
7
|
require 'bitpay/client'
|
@@ -10,8 +14,21 @@ module BitPay
|
|
10
14
|
CA_FILE = File.join File.dirname(__FILE__), 'bitpay','cacert.pem'
|
11
15
|
|
12
16
|
# Location of API
|
13
|
-
API_URI = 'https://bitpay.com
|
17
|
+
API_URI = 'https://bitpay.com'
|
18
|
+
TEST_API_URI = 'https://test.bitpay.com'
|
19
|
+
CLIENT_REGISTRATION_PATH = '/api-access-request'
|
20
|
+
|
21
|
+
# Location for API Credentials
|
22
|
+
BITPAY_CREDENTIALS_DIR = File.join(Dir.home, ".bitpay")
|
23
|
+
PRIVATE_KEY_FILE = 'bitpay.pem'
|
24
|
+
PRIVATE_KEY_PATH = File.join(BITPAY_CREDENTIALS_DIR, PRIVATE_KEY_FILE)
|
14
25
|
|
15
26
|
# User agent reported to API
|
16
27
|
USER_AGENT = 'ruby-bitpay-client '+VERSION
|
28
|
+
|
29
|
+
MISSING_KEY = 'No Private Key specified. Pass priv_key or set ENV variable PRIV_KEY'
|
30
|
+
MISSING_PEM = 'No pem file specified. Pass pem or set ENV variable BITPAY_PEM'
|
31
|
+
|
32
|
+
class BitPayError < StandardError; end
|
33
|
+
|
17
34
|
end
|
data/lib/bitpay/cli.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# license Copyright 2011-2014 BitPay, Inc., MIT License
|
2
|
+
# see http://opensource.org/licenses/MIT
|
3
|
+
# or https://github.com/bitpay/php-bitpay-client/blob/master/LICENSE
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'commander/import'
|
7
|
+
|
8
|
+
program :name, 'BitPay Ruby Library CLI'
|
9
|
+
program :version, BitPay::VERSION
|
10
|
+
program :description, 'Official BitPay Ruby API Client. Use to securely register your client with the BitPay API endpoint. '
|
11
|
+
program :help_formatter, :compact
|
12
|
+
|
13
|
+
command :pair do |c|
|
14
|
+
c.syntax = 'bitpay pair <code>'
|
15
|
+
c.summary = "Pair the local keys to a bitpay account."
|
16
|
+
c.option '--test', "Use the bitpay test server"
|
17
|
+
c.option '--custom <custom>', "Use a custom bitpay URI"
|
18
|
+
c.option '--insecure <insecure>', "Use an insecure custom bitpay URI"
|
19
|
+
c.action do |args, options|
|
20
|
+
raise ArgumentError, "Pairing failed, please call argument as 'bitpay pair <code> [options]'" unless args.first
|
21
|
+
case
|
22
|
+
when options.test
|
23
|
+
client = BitPay::Client.new(api_uri: "https://test.bitpay.com")
|
24
|
+
message = "Paired with test.bitpay.com"
|
25
|
+
when options.custom
|
26
|
+
client = BitPay::Client.new(api_uri: options.custom)
|
27
|
+
message = "Paired with #{options.custom}"
|
28
|
+
when options.insecure
|
29
|
+
client = BitPay::Client.new(insecure: true, api_uri: options.insecure)
|
30
|
+
message = "Paired with #{options.insecure}"
|
31
|
+
else
|
32
|
+
client = BitPay::Client.new
|
33
|
+
message = "Paired with bitpay.com"
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
client.pair_pos_client args.first
|
38
|
+
puts message
|
39
|
+
rescue Exception => e
|
40
|
+
puts e.message
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
command :show_keys do |c|
|
46
|
+
c.syntax = 'bitpay show_keys'
|
47
|
+
c.summary = "Read current environment's key information to STDOUT"
|
48
|
+
c.description = ''
|
49
|
+
c.example 'description', 'command example'
|
50
|
+
c.action do |args, options|
|
51
|
+
|
52
|
+
pem = BitPay::KeyUtils.get_local_pem_file
|
53
|
+
private_key = BitPay::KeyUtils.get_private_key_from_pem pem
|
54
|
+
public_key = BitPay::KeyUtils.get_public_key_from_pem pem
|
55
|
+
client_id = BitPay::KeyUtils.generate_sin_from_pem pem
|
56
|
+
|
57
|
+
puts "Current BitPay Client Keys:\n"
|
58
|
+
puts "Private Key: #{private_key}"
|
59
|
+
puts "Public Key: #{public_key}"
|
60
|
+
puts "Client ID: #{client_id}"
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
data/lib/bitpay/client.rb
CHANGED
@@ -1,73 +1,157 @@
|
|
1
|
+
# license Copyright 2011-2014 BitPay, Inc., MIT License
|
2
|
+
# see http://opensource.org/licenses/MIT
|
3
|
+
# or https://github.com/bitpay/php-bitpay-client/blob/master/LICENSE
|
4
|
+
|
1
5
|
require 'uri'
|
2
6
|
require 'net/https'
|
3
7
|
require 'json'
|
4
8
|
|
9
|
+
require_relative 'key_utils'
|
10
|
+
|
5
11
|
module BitPay
|
6
12
|
# This class is used to instantiate a BitPay Client object. It is expected to be thread safe.
|
7
13
|
#
|
8
|
-
# @example
|
9
|
-
# # Create a client with your BitPay API key (obtained from the BitPay API access page at BitPay.com):
|
10
|
-
# client = BitPay::Client.new 'YOUR_API_KEY'
|
11
14
|
class Client
|
12
|
-
|
15
|
+
|
13
16
|
|
14
|
-
# Creates a BitPay Client object. The second parameter is a hash for overriding defaults.
|
15
|
-
#
|
16
17
|
# @return [Client]
|
17
18
|
# @example
|
18
|
-
# # Create a client with
|
19
|
-
# client = BitPay::Client.new
|
20
|
-
def initialize(
|
21
|
-
@
|
19
|
+
# # Create a client with a pem file created by the bitpay client:
|
20
|
+
# client = BitPay::Client.new
|
21
|
+
def initialize(opts={})
|
22
|
+
@pem = opts[:pem] || ENV['BITPAY_PEM'] || KeyUtils.retrieve_or_generate_pem
|
23
|
+
@key = KeyUtils.create_key @pem
|
24
|
+
@priv_key = KeyUtils.get_private_key @key
|
25
|
+
@pub_key = KeyUtils.get_public_key @key
|
26
|
+
@client_id = KeyUtils.generate_sin_from_pem @pem
|
22
27
|
@uri = URI.parse opts[:api_uri] || API_URI
|
28
|
+
@user_agent = opts[:user_agent] || USER_AGENT
|
23
29
|
@https = Net::HTTP.new @uri.host, @uri.port
|
24
30
|
@https.use_ssl = true
|
25
31
|
@https.ca_file = CA_FILE
|
26
32
|
|
27
|
-
|
28
33
|
# Option to disable certificate validation in extraordinary circumstance. NOT recommended for production use
|
29
34
|
@https.verify_mode = opts[:insecure] == true ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
|
35
|
+
|
36
|
+
# Option to enable http request debugging
|
37
|
+
@https.set_debug_output($stdout) if opts[:debug] == true
|
30
38
|
|
39
|
+
# Load all the available tokens into @tokens
|
40
|
+
load_tokens
|
31
41
|
end
|
32
42
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
43
|
+
def pair_pos_client(claimCode)
|
44
|
+
response = set_pos_token(claimCode)
|
45
|
+
case response.code
|
46
|
+
when "200"
|
47
|
+
get_token 'pos'
|
48
|
+
when "500"
|
49
|
+
raise BitPayError, JSON.parse(response.body)["error"]
|
50
|
+
else
|
51
|
+
raise BitPayError, "#{response.code}: #{JSON.parse(response.body)}"
|
52
|
+
end
|
53
|
+
response
|
41
54
|
end
|
42
55
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
# created_invoice = client.post 'invoice', {:price => 1.45, :currency => 'BTC'}
|
48
|
-
def post(path, params={})
|
49
|
-
request(Net::HTTP::Post, path, params)
|
56
|
+
def create_invoice(id:, price:, currency:, facade: 'pos', params:{})
|
57
|
+
params.merge!({price: price, currency: currency})
|
58
|
+
response = send_request("POST", "invoices", facade: facade, params: params)
|
59
|
+
response["data"]
|
50
60
|
end
|
51
61
|
|
52
|
-
|
62
|
+
def get_public_invoice(id:)
|
63
|
+
request = Net::HTTP::Get.new("/invoices/#{id}")
|
64
|
+
response = @https.request request
|
65
|
+
(JSON.parse response.body)["data"]
|
66
|
+
end
|
67
|
+
|
68
|
+
## Generates REST request to api endpoint
|
69
|
+
def send_request(verb, path, facade: 'merchant', params: {}, token: nil)
|
70
|
+
token ||= @tokens[facade] || raise(BitPayError, "No token for specified facade: #{facade}")
|
53
71
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
72
|
+
# Verb-specific logic
|
73
|
+
case verb.upcase
|
74
|
+
when "GET"
|
75
|
+
urlpath = '/' + path + '?nonce=' + KeyUtils.nonce + '&token=' + token
|
76
|
+
request = Net::HTTP::Get.new urlpath
|
77
|
+
request['X-Signature'] = KeyUtils.sign(@uri.to_s + urlpath, @priv_key)
|
78
|
+
|
79
|
+
when "PUT"
|
80
|
+
|
81
|
+
when "POST" # Requires a GUID
|
82
|
+
|
83
|
+
urlpath = '/' + path
|
84
|
+
request = Net::HTTP::Post.new urlpath
|
85
|
+
params[:token] = token
|
86
|
+
params[:nonce] = KeyUtils.nonce
|
87
|
+
params[:guid] = SecureRandom.uuid
|
88
|
+
params[:id] = @client_id
|
89
|
+
request.body = params.to_json
|
90
|
+
request['X-Signature'] = KeyUtils.sign(@uri.to_s + urlpath + request.body, @priv_key)
|
91
|
+
|
92
|
+
when "DELETE"
|
93
|
+
|
94
|
+
raise(BitPayError, "Invalid HTTP verb: #{verb.upcase}")
|
95
|
+
end
|
96
|
+
|
97
|
+
# Build request headers and submit
|
98
|
+
request['User-Agent'] = @user_agent
|
99
|
+
request['Content-Type'] = 'application/json'
|
100
|
+
request['X-BitPay-Plugin-Info'] = 'Rubylib' + VERSION
|
101
|
+
request['X-Identity'] = @pub_key
|
102
|
+
|
103
|
+
response = @https.request request
|
104
|
+
JSON.parse response.body
|
61
105
|
end
|
62
106
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
107
|
+
##### PRIVATE METHODS #####
|
108
|
+
private
|
109
|
+
|
110
|
+
## Requests token by appending nonce and signing URL
|
111
|
+
# Returns a hash of available tokens
|
112
|
+
#
|
113
|
+
def load_tokens
|
114
|
+
|
115
|
+
urlpath = '/tokens?nonce=' + KeyUtils.nonce
|
116
|
+
|
117
|
+
request = Net::HTTP::Get.new(urlpath)
|
118
|
+
request['content-type'] = "application/json"
|
119
|
+
request['user-agent'] = @user_agent
|
120
|
+
request['x-identity'] = @pub_key
|
121
|
+
request['x-signature'] = KeyUtils.sign(@uri.to_s + urlpath, @priv_key)
|
122
|
+
|
123
|
+
response = @https.request request
|
124
|
+
|
125
|
+
# /tokens returns an array of hashes. Let's turn it into a more useful single hash
|
126
|
+
token_array = JSON.parse(response.body)["data"] || {}
|
127
|
+
|
128
|
+
tokens = {}
|
129
|
+
token_array.each do |t|
|
130
|
+
tokens[t.keys.first] = t.values.first
|
70
131
|
end
|
132
|
+
|
133
|
+
@tokens = tokens
|
134
|
+
return tokens
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
## Retrieves specified token from hash, otherwise tries to refresh @tokens and retry
|
139
|
+
def set_pos_token(claim_code)
|
140
|
+
params = {pairingCode: claim_code}
|
141
|
+
urlpath = '/tokens'
|
142
|
+
request = Net::HTTP::Post.new urlpath
|
143
|
+
params[:guid] = SecureRandom.uuid
|
144
|
+
params[:id] = @client_id
|
145
|
+
request.body = params.to_json
|
146
|
+
request['User-Agent'] = @user_agent
|
147
|
+
request['Content-Type'] = 'application/json'
|
148
|
+
request['X-BitPay-Plugin-Info'] = 'Rubylib' + VERSION
|
149
|
+
@https.request request
|
71
150
|
end
|
151
|
+
|
152
|
+
def get_token(facade)
|
153
|
+
token = @tokens[facade] || load_tokens[facade] || raise(BitPayError, "Not authorized for facade: #{facade}")
|
154
|
+
end
|
155
|
+
|
72
156
|
end
|
73
157
|
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# license Copyright 2011-2014 BitPay, Inc., MIT License
|
2
|
+
# see http://opensource.org/licenses/MIT
|
3
|
+
# or https://github.com/bitpay/php-bitpay-client/blob/master/LICENSE
|
4
|
+
|
5
|
+
require 'uri'
|
6
|
+
require 'net/https'
|
7
|
+
require 'json'
|
8
|
+
require 'openssl'
|
9
|
+
require 'ecdsa'
|
10
|
+
require 'securerandom'
|
11
|
+
require 'digest/sha2'
|
12
|
+
require 'cgi'
|
13
|
+
|
14
|
+
module BitPay
|
15
|
+
class KeyUtils
|
16
|
+
class << self
|
17
|
+
def nonce
|
18
|
+
Time.now.utc.strftime('%Y%m%d%H%M%S%L')
|
19
|
+
end
|
20
|
+
|
21
|
+
## Generates a new private key and writes to local FS
|
22
|
+
#
|
23
|
+
def retrieve_or_generate_pem
|
24
|
+
begin
|
25
|
+
pem = get_local_pem_file
|
26
|
+
rescue
|
27
|
+
pem = generate_pem
|
28
|
+
end
|
29
|
+
pem
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate_pem
|
33
|
+
key = OpenSSL::PKey::EC.new("secp256k1")
|
34
|
+
key.generate_key
|
35
|
+
write_pem_file(key)
|
36
|
+
key.to_pem
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_key pem
|
40
|
+
OpenSSL::PKey::EC.new(pem)
|
41
|
+
end
|
42
|
+
|
43
|
+
def create_new_key
|
44
|
+
key = OpenSSL::PKey::EC.new("secp256k1")
|
45
|
+
key.generate_key
|
46
|
+
key
|
47
|
+
end
|
48
|
+
|
49
|
+
def write_pem_file key
|
50
|
+
FileUtils.mkdir_p(BITPAY_CREDENTIALS_DIR)
|
51
|
+
File.open(PRIVATE_KEY_PATH, 'w') { |file| file.write(key.to_pem) }
|
52
|
+
end
|
53
|
+
## Gets private key from ENV variable or local FS
|
54
|
+
#
|
55
|
+
def get_local_pem_file
|
56
|
+
ENV['BITPAY_PEM'] || File.read(PRIVATE_KEY_PATH) || (raise BitPayError, MISSING_KEY)
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_private_key key
|
60
|
+
key.private_key.to_int.to_s(16)
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_public_key key
|
64
|
+
key.public_key.group.point_conversion_form = :compressed
|
65
|
+
key.public_key.to_bn.to_s(16).downcase
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_private_key_from_pem pem
|
69
|
+
raise BitPayError, MISSING_KEY unless pem
|
70
|
+
key = OpenSSL::PKey::EC.new(pem)
|
71
|
+
get_private_key key
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_public_key_from_pem pem
|
75
|
+
raise BitPayError, MISSING_KEY unless pem
|
76
|
+
key = OpenSSL::PKey::EC.new(pem)
|
77
|
+
get_public_key key
|
78
|
+
end
|
79
|
+
|
80
|
+
def generate_sin_from_pem(pem = nil)
|
81
|
+
#http://blog.bitpay.com/2014/07/01/bitauth-for-decentralized-authentication.html
|
82
|
+
#https://en.bitcoin.it/wiki/Identity_protocol_v1
|
83
|
+
|
84
|
+
# NOTE: All Digests are calculated against the binary representation,
|
85
|
+
# hence the requirement to use [].pack("H*") to convert to binary for each step
|
86
|
+
|
87
|
+
#Generate Private Key
|
88
|
+
key = pem.nil? ? get_local_pem_file : OpenSSL::PKey::EC.new(pem)
|
89
|
+
key.public_key.group.point_conversion_form = :compressed
|
90
|
+
public_key = key.public_key.to_bn.to_s(2)
|
91
|
+
step_one = Digest::SHA256.hexdigest(public_key)
|
92
|
+
step_two = Digest::RMD160.hexdigest([step_one].pack("H*"))
|
93
|
+
step_three = "0F02" + step_two
|
94
|
+
step_four_a = Digest::SHA256.hexdigest([step_three].pack("H*"))
|
95
|
+
step_four = Digest::SHA256.hexdigest([step_four_a].pack("H*"))
|
96
|
+
step_five = step_four[0..7]
|
97
|
+
step_six = step_three + step_five
|
98
|
+
encode_base58(step_six)
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
## Generate ECDSA signature
|
103
|
+
# This is the last method that requires the ecdsa gem, which we would like to replace
|
104
|
+
|
105
|
+
def sign(message, privkey)
|
106
|
+
group = ECDSA::Group::Secp256k1
|
107
|
+
digest = Digest::SHA256.digest(message)
|
108
|
+
signature = nil
|
109
|
+
while signature.nil?
|
110
|
+
temp_key = 1 + SecureRandom.random_number(group.order - 1)
|
111
|
+
signature = ECDSA.sign(group, privkey.to_i(16), digest, temp_key)
|
112
|
+
return ECDSA::Format::SignatureDerString.encode(signature).unpack("H*").first
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
########## Private Class Methods ################
|
117
|
+
|
118
|
+
## Base58 Encoding Method
|
119
|
+
#
|
120
|
+
private
|
121
|
+
def encode_base58 (data)
|
122
|
+
code_string = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
123
|
+
base = 58
|
124
|
+
x = data.hex
|
125
|
+
output_string = ""
|
126
|
+
|
127
|
+
while x > 0 do
|
128
|
+
remainder = x % base
|
129
|
+
x = x / base
|
130
|
+
output_string << code_string[remainder]
|
131
|
+
end
|
132
|
+
|
133
|
+
pos = 0
|
134
|
+
while data[pos,2] == "00" do
|
135
|
+
output_string << code_string[0]
|
136
|
+
pos += 2
|
137
|
+
end
|
138
|
+
|
139
|
+
output_string.reverse()
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
data/lib/bitpay/version.rb
CHANGED
data/lib/harness.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# license Copyright 2011-2014 BitPay, Inc., MIT License
|
2
|
+
# see http://opensource.org/licenses/MIT
|
3
|
+
# or https://github.com/bitpay/php-bitpay-client/blob/master/LICENSE
|
4
|
+
|
5
|
+
require_relative 'bitpay.rb'
|
6
|
+
require_relative 'bitpay/key_utils.rb'
|
7
|
+
|
8
|
+
# Test SIN Generation class methods
|
9
|
+
|
10
|
+
# Generate SIN
|
11
|
+
ENV["PRIV_KEY"] = "16d7c3508ec59773e71ae728d29f41fcf5d1f380c379b99d68fa9f552ce3ebc3"
|
12
|
+
puts "privkey: #{ENV['PRIV_KEY']}"
|
13
|
+
puts "target SIN: TfFVQhy2hQvchv4VVG4c7j4XPa2viJ9HrR8"
|
14
|
+
puts "Derived SIN: #{BitPay::KeyUtils.get_client_id}"
|
15
|
+
|
16
|
+
puts "\n\n------------------\n\n"
|
17
|
+
|
18
|
+
uri = "https://localhost:8088"
|
19
|
+
#name = "Ridonculous.label That shouldn't work really"
|
20
|
+
name = "somethinginnocuous"
|
21
|
+
facade = "pos"
|
22
|
+
client_id = BitPay::KeyUtils.get_client_id
|
23
|
+
|
24
|
+
BitPay::KeyUtils.generate_registration_url(uri,name,facade,client_id)
|
25
|
+
|
26
|
+
puts "\n\n------------------\n\n"
|
27
|
+
|
28
|
+
#### Test Invoice Creation using directly assigned keys
|
29
|
+
## (Ultimately pubkey and SIN should be derived)
|
30
|
+
|
31
|
+
ENV["PRIV_KEY"] = "16d7c3508ec59773e71ae728d29f41fcf5d1f380c379b99d68fa9f552ce3ebc3"
|
32
|
+
#ENV["pub_key"] = "0353a036fb495c5846f26a3727a28198da8336ae4f5aaa09e24c14a4126b5d969d"
|
33
|
+
#ENV['SIN'] = "TfFVQhy2hQvchv4VVG4c7j4XPa2viJ9HrR8"
|
34
|
+
|
35
|
+
client = BitPay::Client.new({insecure: true, debug: false})
|
36
|
+
|
37
|
+
invoice = client.post 'invoices', {:price => 10.00, :currency => 'USD'}
|
38
|
+
|
39
|
+
puts "Here's the invoice: \n" + JSON.pretty_generate(invoice)
|
40
|
+
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
def tokens
|
4
|
+
{"data" =>
|
5
|
+
[{"merchant" => "MERCHANTTOKEN"},
|
6
|
+
{"pos" =>"POSTOKEN"},
|
7
|
+
{"merchant/invoice" => "9kv7gGqZLoQ2fxbKEgfgndLoxwjp5na6VtGSH3sN7buX"}
|
8
|
+
]
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
describe BitPay::Client do
|
13
|
+
let(:bitpay_client) { BitPay::Client.new({api_uri: BitPay::TEST_API_URI}) }
|
14
|
+
|
15
|
+
before do
|
16
|
+
allow(BitPay::KeyUtils).to receive(:nonce).and_return('1')
|
17
|
+
stub_request(:get, /#{BitPay::TEST_API_URI}\/tokens.*/).to_return(:status => 200, :body => tokens.to_json, :headers => {})
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#initialize" do
|
21
|
+
|
22
|
+
it 'should be able to get pem file from the env' do
|
23
|
+
stub_const('ENV', {'BITPAY_PEM' => PEM})
|
24
|
+
expect {bitpay_client}.to_not raise_error
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#send_request" do
|
30
|
+
before do
|
31
|
+
stub_const('ENV', {'BITPAY_PEM' => PEM})
|
32
|
+
end
|
33
|
+
|
34
|
+
context "GET" do
|
35
|
+
it 'should generate a get request' do
|
36
|
+
stub_request(:get, /#{BitPay::TEST_API_URI}\/whatever.*/).to_return(:body => '{"awesome": "json"}')
|
37
|
+
bitpay_client.send_request("GET", "whatever", facade: "merchant")
|
38
|
+
expect(WebMock).to have_requested(:get, "#{BitPay::TEST_API_URI}/whatever?nonce=1&token=MERCHANTTOKEN")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "POST" do
|
43
|
+
it 'should generate a post request' do
|
44
|
+
stub_request(:post, /#{BitPay::TEST_API_URI}.*/).to_return(:body => '{"awesome": "json"}')
|
45
|
+
bitpay_client.send_request("POST", "whatever", facade: "merchant")
|
46
|
+
expect(WebMock).to have_requested(:post, "#{BitPay::TEST_API_URI}/whatever")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#pair_pos_client" do
|
53
|
+
it 'throws a BitPayError with the error message if the token setting fails' do
|
54
|
+
stub_const('ENV', {'BITPAY_PEM' => PEM})
|
55
|
+
stub_request(:any, /#{BitPay::TEST_API_URI}.*/).to_return(status: 500, body: "{\n \"error\": \"Unable to create token\"\n}")
|
56
|
+
expect { bitpay_client.pair_pos_client(:claim_code) }.to raise_error(BitPay::BitPayError, 'Unable to create token')
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'gracefully handles 4xx errors' do
|
60
|
+
stub_const('ENV', {'BITPAY_PEM' => PEM})
|
61
|
+
stub_request(:any, /#{BitPay::TEST_API_URI}.*/).to_return(status: 403, body: "{\n \"error\": \"this is a 403 error\"\n}")
|
62
|
+
expect { bitpay_client.pair_pos_client(:claim_code) }.to raise_error(BitPay::BitPayError, '403: {"error"=>"this is a 403 error"}')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "#create_invoice" do
|
67
|
+
subject { bitpay_client }
|
68
|
+
before {stub_const('ENV', {'BITPAY_PEM' => PEM})}
|
69
|
+
it { is_expected.to respond_to(:create_invoice) }
|
70
|
+
|
71
|
+
it 'should make call to the server to create an invoice' do
|
72
|
+
stub_request(:post, /#{BitPay::TEST_API_URI}\/invoices.*/).to_return(:body => '{"data": "awesome"}')
|
73
|
+
bitpay_client.create_invoice(id: "addd", price: 20, currency: "USD")
|
74
|
+
assert_requested :post, "#{BitPay::TEST_API_URI}/invoices"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative '../spec_helper.rb'
|
2
|
+
|
3
|
+
describe "pairing a token", javascript: true, type: :feature do
|
4
|
+
let(:claimCode) do
|
5
|
+
visit ROOT_ADDRESS
|
6
|
+
click_link('Login')
|
7
|
+
fill_in 'email', :with => TEST_USER
|
8
|
+
fill_in 'password', :with => TEST_PASS
|
9
|
+
click_button('loginButton')
|
10
|
+
click_link "My Account"
|
11
|
+
click_link "API Tokens", match: :first
|
12
|
+
find(".token-access-new-button").find(".btn").click
|
13
|
+
find(".token-claimcode", match: :first).text
|
14
|
+
end
|
15
|
+
let(:pem) { BitPay::KeyUtils.generate_pem }
|
16
|
+
let(:client) { BitPay::Client.new(api_uri: ROOT_ADDRESS, pem: pem, insecure: true) }
|
17
|
+
|
18
|
+
context "pairing an unpaired client" do
|
19
|
+
it "should have no tokens before pairing" do
|
20
|
+
expect(client.instance_variable_get(:@tokens)).to be_empty
|
21
|
+
end
|
22
|
+
it "should have a pos token after pairing" do
|
23
|
+
client.pair_pos_client(claimCode)
|
24
|
+
expect(client.instance_variable_get(:@tokens)['pos']).not_to be_empty
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative '../spec_helper.rb'
|
2
|
+
|
3
|
+
describe "create an invoice", javascript: true, type: :feature do
|
4
|
+
before :all do
|
5
|
+
WebMock.allow_net_connect!
|
6
|
+
get_claim_code = -> {
|
7
|
+
visit ROOT_ADDRESS
|
8
|
+
click_link('Login')
|
9
|
+
fill_in 'email', :with => TEST_USER
|
10
|
+
fill_in 'password', :with => TEST_PASS
|
11
|
+
click_button('loginButton')
|
12
|
+
click_link "My Account"
|
13
|
+
click_link "API Tokens", match: :first
|
14
|
+
find(".token-access-new-button").find(".btn").click
|
15
|
+
find(".token-claimcode", match: :first).text
|
16
|
+
}
|
17
|
+
set_client = -> {
|
18
|
+
private_key = BitPay::KeyUtils.get_private_key_from_pem PEM
|
19
|
+
client = BitPay::Client.new(api_uri: ROOT_ADDRESS, pem: PEM, insecure: true)
|
20
|
+
client.pair_pos_client(get_claim_code.call)
|
21
|
+
client
|
22
|
+
}
|
23
|
+
@client ||= set_client.call
|
24
|
+
@invoice_id ||= SecureRandom.uuid
|
25
|
+
@price ||= (100..150).to_a.sample
|
26
|
+
@invoice = @client.create_invoice(id: @invoice_id, currency: "USD", price: @price)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should create an invoice" do
|
30
|
+
expect(@invoice["status"]).to eq "new"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should be able to retrieve an invoice" do
|
34
|
+
expect(@client.get_public_invoice(id: @invoice['id'])["price"]).to eq @price
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
context 'local variables' do
|
4
|
+
it "should find the root address" do
|
5
|
+
expect(ROOT_ADDRESS).not_to be_nil
|
6
|
+
end
|
7
|
+
it "should find the user" do
|
8
|
+
expect(TEST_USER).not_to be_nil
|
9
|
+
end
|
10
|
+
it "should find the user" do
|
11
|
+
expect(TEST_PASS).not_to be_nil
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BitPay::KeyUtils do
|
4
|
+
let(:key_utils) {BitPay::KeyUtils}
|
5
|
+
|
6
|
+
|
7
|
+
describe '.get_local_private_key' do
|
8
|
+
it "should get the key from the ENV['PRIV_KEY'] variable" do
|
9
|
+
stub_const('ENV', {'BITPAY_PEM' => PEM})
|
10
|
+
expect(key_utils.get_local_pem_file).to eq(PEM)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should get the key from ~/.bitpay/bitpay.pem if env variable is not set' do
|
14
|
+
allow(File).to receive(:read).with(BitPay::PRIVATE_KEY_PATH) {PEM}
|
15
|
+
expect(key_utils.get_local_pem_file).to eq(PEM)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '.generate_pem' do
|
21
|
+
it 'should write a new key to ~/.bitpay/bitpay.pem' do
|
22
|
+
file = class_double("File").as_stubbed_const
|
23
|
+
double = double("Object").as_null_object
|
24
|
+
allow(file).to receive(:path).with(BitPay::BITPAY_CREDENTIALS_DIR).and_return(double)
|
25
|
+
expect(file).to receive(:open).with(BitPay::PRIVATE_KEY_PATH, 'w')
|
26
|
+
key_utils.generate_pem
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '.retrieve_or_generate_pem' do
|
32
|
+
it 'should write a new key to ~/.bitpay/bitpay.pem if there is no existing file' do
|
33
|
+
file = class_double("File").as_stubbed_const
|
34
|
+
double = double("Object").as_null_object
|
35
|
+
allow(file).to receive(:read).with(BitPay::PRIVATE_KEY_PATH).and_throw(StandardError)
|
36
|
+
allow(file).to receive(:path).with(BitPay::BITPAY_CREDENTIALS_DIR).and_return(double)
|
37
|
+
expect(file).to receive(:open).with(BitPay::PRIVATE_KEY_PATH, 'w')
|
38
|
+
key_utils.retrieve_or_generate_pem
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should retrieve the pem if there is an existing file' do
|
42
|
+
file = class_double("File").as_stubbed_const
|
43
|
+
double = double("Object").as_null_object
|
44
|
+
allow(file).to receive(:path).with(BitPay::BITPAY_CREDENTIALS_DIR).and_return(double)
|
45
|
+
expect(file).to receive(:open).with(BitPay::PRIVATE_KEY_PATH, 'w')
|
46
|
+
key_utils.generate_pem
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '.get_public_key_from_pem' do
|
51
|
+
it 'should generate the right public key' do
|
52
|
+
expect(key_utils.get_public_key_from_pem(PEM)).to eq(PUB_KEY)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should get pem from the env if none is passed' do
|
56
|
+
expect(key_utils.get_public_key_from_pem(PEM)).to eq(PUB_KEY)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '.generate_sin_from_pem' do
|
62
|
+
let(:pem){PEM}
|
63
|
+
let(:sin){"TeyN4LPrXiG5t2yuSamKqP3ynVk3F52iHrX"}
|
64
|
+
|
65
|
+
it 'will return the right sin for the right pem' do
|
66
|
+
expect(key_utils.generate_sin_from_pem(pem)).to eq sin
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "errors when priv_key is not provided" do
|
71
|
+
before :each do
|
72
|
+
allow(File).to receive(:read).with(BitPay::PRIVATE_KEY_PATH) {nil}
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'will not retrieve public key' do
|
76
|
+
expect{key_utils.get_public_key_from_pem(nil)}.to raise_error(BitPay::BitPayError)
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'webmock/rspec'
|
2
|
+
require 'pry'
|
3
|
+
require 'capybara/rspec'
|
4
|
+
require 'capybara/poltergeist'
|
5
|
+
|
6
|
+
require File.join File.dirname(__FILE__), '..', 'lib', 'bitpay', 'client.rb'
|
7
|
+
require File.join File.dirname(__FILE__), '..', 'lib', 'bitpay', 'key_utils.rb'
|
8
|
+
require File.join File.dirname(__FILE__), '..', 'lib', 'bitpay.rb'
|
9
|
+
require_relative '../config/constants.rb'
|
10
|
+
require_relative '../config/capybara.rb'
|
11
|
+
|
12
|
+
#
|
13
|
+
## Test Variables
|
14
|
+
#
|
15
|
+
PEM = "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEICg7E4NN53YkaWuAwpoqjfAofjzKI7Jq1f532dX+0O6QoAcGBSuBBAAK\noUQDQgAEjZcNa6Kdz6GQwXcUD9iJ+t1tJZCx7hpqBuJV2/IrQBfue8jh8H7Q/4vX\nfAArmNMaGotTpjdnymWlMfszzXJhlw==\n-----END EC PRIVATE KEY-----\n"
|
16
|
+
|
17
|
+
PUB_KEY = '038d970d6ba29dcfa190c177140fd889fadd6d2590b1ee1a6a06e255dbf22b4017'
|
18
|
+
CLIENT_ID = "TfFVQhy2hQvchv4VVG4c7j4XPa2viJ9HrR8"
|
19
|
+
|
20
|
+
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.before :each do |example|
|
23
|
+
WebMock.allow_net_connect! if example.metadata[:type] == :feature
|
24
|
+
end
|
25
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bitpay-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bitpay, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -38,6 +38,34 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ecdsa
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: commander
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: rake
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,7 +81,21 @@ dependencies:
|
|
53
81
|
- !ruby/object:Gem::Version
|
54
82
|
version: '0'
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
84
|
+
name: webmock
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
57
99
|
requirement: !ruby/object:Gem::Requirement
|
58
100
|
requirements:
|
59
101
|
- - ">="
|
@@ -67,7 +109,21 @@ dependencies:
|
|
67
109
|
- !ruby/object:Gem::Version
|
68
110
|
version: '0'
|
69
111
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
112
|
+
name: pry-byebug
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: pry-rescue
|
71
127
|
requirement: !ruby/object:Gem::Requirement
|
72
128
|
requirements:
|
73
129
|
- - ">="
|
@@ -81,7 +137,63 @@ dependencies:
|
|
81
137
|
- !ruby/object:Gem::Version
|
82
138
|
version: '0'
|
83
139
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
140
|
+
name: capybara
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: poltergeist
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: airborne
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: rspec
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: mongo
|
85
197
|
requirement: !ruby/object:Gem::Requirement
|
86
198
|
requirements:
|
87
199
|
- - ">="
|
@@ -97,7 +209,8 @@ dependencies:
|
|
97
209
|
description: Powerful, flexible, lightweight, thread-safe interface to the BitPay
|
98
210
|
developers API
|
99
211
|
email: info@bitpay.com
|
100
|
-
executables:
|
212
|
+
executables:
|
213
|
+
- bitpay
|
101
214
|
extensions: []
|
102
215
|
extra_rdoc_files: []
|
103
216
|
files:
|
@@ -107,15 +220,27 @@ files:
|
|
107
220
|
- LICENSE.md
|
108
221
|
- README.md
|
109
222
|
- Rakefile
|
223
|
+
- bin/bitpay
|
110
224
|
- bitpay-client.gemspec
|
225
|
+
- config/capybara.rb
|
226
|
+
- config/constants.rb
|
111
227
|
- lib/bitpay.rb
|
112
228
|
- lib/bitpay/cacert.pem
|
229
|
+
- lib/bitpay/cli.rb
|
113
230
|
- lib/bitpay/client.rb
|
231
|
+
- lib/bitpay/key_utils.rb
|
114
232
|
- lib/bitpay/version.rb
|
115
|
-
-
|
116
|
-
-
|
233
|
+
- lib/harness.rb
|
234
|
+
- spec/client_spec.rb
|
235
|
+
- spec/features/pair_spec.rb
|
236
|
+
- spec/features/pos_spec.rb
|
237
|
+
- spec/features/setup_spec.rb
|
238
|
+
- spec/key_utils_spec.rb
|
239
|
+
- spec/set_constants.sh
|
240
|
+
- spec/spec_helper.rb
|
117
241
|
homepage: https://github.com/bitpay/ruby-client
|
118
|
-
licenses:
|
242
|
+
licenses:
|
243
|
+
- MIT
|
119
244
|
metadata: {}
|
120
245
|
post_install_message:
|
121
246
|
rdoc_options: []
|
@@ -123,9 +248,9 @@ require_paths:
|
|
123
248
|
- lib
|
124
249
|
required_ruby_version: !ruby/object:Gem::Requirement
|
125
250
|
requirements:
|
126
|
-
- - "
|
251
|
+
- - "~>"
|
127
252
|
- !ruby/object:Gem::Version
|
128
|
-
version: '
|
253
|
+
version: '2.1'
|
129
254
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
255
|
requirements:
|
131
256
|
- - ">="
|
data/test/bitpay/client_test.rb
DELETED
@@ -1,70 +0,0 @@
|
|
1
|
-
require File.join File.dirname(__FILE__), '..', 'env.rb'
|
2
|
-
|
3
|
-
USER_AGENT = 'ruby-bitpay-client '+BitPay::VERSION
|
4
|
-
|
5
|
-
def invoice_create_body
|
6
|
-
{:price => 1, :currency => 'USD'}
|
7
|
-
end
|
8
|
-
|
9
|
-
def invoice_response_body
|
10
|
-
{
|
11
|
-
"id" => "DGrAEmbsXe9bavBPMJ8kuk",
|
12
|
-
"url" => "https://bitpay.com/invoice?id=DGrAEmbsXe9bavBPMJ8kuk",
|
13
|
-
"status" => "new",
|
14
|
-
"btcPrice" => "0.0495",
|
15
|
-
"price" => 10,
|
16
|
-
"currency" => "USD",
|
17
|
-
"invoiceTime" => 1383265343674,
|
18
|
-
"expirationTime" => 1383266243674,
|
19
|
-
"currentTime" => 1383265957613
|
20
|
-
}
|
21
|
-
end
|
22
|
-
|
23
|
-
def unauthorized_key_body
|
24
|
-
{"error" => {"type" => "unauthorized", "message" => "invalid api key"}}
|
25
|
-
end
|
26
|
-
|
27
|
-
stub_request(:post, "https://KEY:@bitpay.com/api/invoice/create").
|
28
|
-
with(
|
29
|
-
:headers => {'User-Agent'=>USER_AGENT, 'Content-Type' => 'application/json'},
|
30
|
-
:body => invoice_create_body
|
31
|
-
).
|
32
|
-
to_return(:body => invoice_response_body.to_json)
|
33
|
-
|
34
|
-
stub_request(:get, "https://KEY:@bitpay.com/api/invoice/DGrAEmbsXe9bavBPMJ8kuk").
|
35
|
-
with(:headers => {'User-Agent'=>USER_AGENT}).
|
36
|
-
to_return(:body => invoice_response_body.to_json)
|
37
|
-
|
38
|
-
stub_request(:get, "https://KEY:@bitpay.com/api/invoice/BADAPIKEY").
|
39
|
-
with(:headers => {'User-Agent'=>USER_AGENT}).
|
40
|
-
to_return(:status => 403, :body => unauthorized_key_body.to_json)
|
41
|
-
|
42
|
-
describe BitPay::Client do
|
43
|
-
before do
|
44
|
-
@client = BitPay::Client.new 'KEY'
|
45
|
-
end
|
46
|
-
|
47
|
-
describe 'post' do
|
48
|
-
it 'creates invoice' do
|
49
|
-
response = @client.post 'invoice/create', invoice_create_body
|
50
|
-
response.class.must_equal Hash
|
51
|
-
response['id'].must_equal 'DGrAEmbsXe9bavBPMJ8kuk'
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
describe 'get' do
|
56
|
-
it 'retreives invoice' do
|
57
|
-
response = @client.get 'invoice/DGrAEmbsXe9bavBPMJ8kuk'
|
58
|
-
response.class.must_equal Hash
|
59
|
-
response['id'].must_equal 'DGrAEmbsXe9bavBPMJ8kuk'
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
describe 'bad API Key' do
|
64
|
-
it 'returns Error' do
|
65
|
-
assert_raises(BitPay::Client::BitPayError) {
|
66
|
-
response = @client.get 'invoice/BADAPIKEY'
|
67
|
-
}
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|