bitpay-sdk 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -2
- data/README.md +159 -32
- data/Rakefile +8 -1
- data/bitpay-sdk.gemspec +14 -14
- data/config/capybara.rb +1 -1
- data/config/constants.rb +16 -0
- data/features/creating_invoices.feature +5 -5
- data/features/pairing.feature +4 -1
- data/features/step_definitions/invoice_steps.rb +5 -5
- data/features/step_definitions/keygen_steps.rb +15 -1
- data/features/step_definitions/step_helpers.rb +19 -21
- data/lib/bitpay/client.rb +112 -35
- data/lib/bitpay/version.rb +1 -1
- data/spec/client_spec.rb +57 -12
- data/spec/fixtures/invoices-POST.json +29 -0
- data/spec/fixtures/invoices_{id}-GET.json +35 -0
- data/spec/fixtures/invoices_{id}_refunds-GET.json +17 -0
- data/spec/fixtures/invoices_{id}_refunds-POST.json +9 -0
- data/spec/fixtures/invoices_{id}_refunds_{refund_id}-GET.json +9 -0
- data/spec/spec_helper.rb +13 -10
- metadata +35 -34
- data/spec/features/pair_spec.rb +0 -29
- data/spec/features/pos_spec.rb +0 -37
- data/spec/features/setup_spec.rb +0 -13
- data/spec/features/verify_tokens_spec.rb +0 -34
@@ -1,17 +1,12 @@
|
|
1
1
|
require 'capybara/poltergeist'
|
2
2
|
require 'pry'
|
3
|
+
require 'fileutils'
|
3
4
|
|
4
5
|
require File.join File.dirname(__FILE__), '..', '..', 'lib', 'bitpay_sdk.rb'
|
5
6
|
require_relative '../../config/constants.rb'
|
6
7
|
require_relative '../../config/capybara.rb'
|
7
8
|
|
8
|
-
|
9
|
-
## Test Variables
|
10
|
-
#
|
11
|
-
#PEM = "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEICg7E4NN53YkaWuAwpoqjfAofjzKI7Jq1f532dX+0O6QoAcGBSuBBAAK\noUQDQgAEjZcNa6Kdz6GQwXcUD9iJ+t1tJZCx7hpqBuJV2/IrQBfue8jh8H7Q/4vX\nfAArmNMaGotTpjdnymWlMfszzXJhlw==\n-----END EC PRIVATE KEY-----\n"
|
12
|
-
#
|
13
|
-
#PUB_KEY = '038d970d6ba29dcfa190c177140fd889fadd6d2590b1ee1a6a06e255dbf22b4017'
|
14
|
-
#CLIENT_ID = "TeyN4LPrXiG5t2yuSamKqP3ynVk3F52iHrX"
|
9
|
+
|
15
10
|
module BitPay
|
16
11
|
# Location for API Credentials
|
17
12
|
BITPAY_CREDENTIALS_DIR = File.join(Dir.home, ".bitpay")
|
@@ -21,26 +16,25 @@ module BitPay
|
|
21
16
|
TOKEN_FILE_PATH = File.join(BITPAY_CREDENTIALS_DIR, TOKEN_FILE)
|
22
17
|
end
|
23
18
|
|
19
|
+
# Lots of sleeps in here to deal with finicky transitions and PhantomJS
|
24
20
|
def get_claim_code_from_server
|
25
21
|
Capybara::visit ROOT_ADDRESS
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
Capybara::
|
33
|
-
Capybara::find(".token-access-new-button").find(".btn").find(".icon-plus").click
|
34
|
-
sleep 0.25
|
35
|
-
Capybara::find_button("Add Token", match: :first).click
|
22
|
+
log_in unless logged_in
|
23
|
+
Capybara::visit DASHBOARD_URL
|
24
|
+
raise "Bad Login" unless Capybara.current_session.current_url == DASHBOARD_URL
|
25
|
+
Capybara::visit "#{ROOT_ADDRESS}/api-tokens"
|
26
|
+
Capybara::find(".token-access-new-button").find(".btn").find(".icon-plus", match: :first).trigger("click")
|
27
|
+
sleep 0.50
|
28
|
+
Capybara::find(".token-access-new-button-wrapper").find_by_id("token-new-form", visible: true).find(".btn").trigger("click")
|
36
29
|
Capybara::find(".token-claimcode", match: :first).text
|
37
30
|
end
|
38
31
|
|
39
32
|
def log_in
|
40
|
-
Capybara::
|
33
|
+
Capybara::visit "#{ROOT_ADDRESS}/dashboard/login/"
|
41
34
|
Capybara::fill_in 'email', :with => TEST_USER
|
42
35
|
Capybara::fill_in 'password', :with => TEST_PASS
|
43
|
-
Capybara::
|
36
|
+
Capybara::click_on('Login')
|
37
|
+
Capybara::find(".ion-gear-a", match: :first)
|
44
38
|
end
|
45
39
|
|
46
40
|
def new_paired_client
|
@@ -55,16 +49,20 @@ def new_client_from_stored_values
|
|
55
49
|
if File.file?(BitPay::PRIVATE_KEY_PATH) && File.file?(BitPay::TOKEN_FILE_PATH)
|
56
50
|
token = get_token_from_file
|
57
51
|
pem = File.read(BitPay::PRIVATE_KEY_PATH)
|
58
|
-
BitPay::SDK::Client.new(pem: pem, tokens: token, insecure: true, api_uri: ROOT_ADDRESS )
|
52
|
+
client = BitPay::SDK::Client.new(pem: pem, tokens: token, insecure: true, api_uri: ROOT_ADDRESS )
|
53
|
+
unless client.verify_tokens then
|
54
|
+
raise "Locally stored tokens are invalid, please remove #{BitPay::TOKEN_FILE_PATH}" end
|
59
55
|
else
|
60
56
|
claim_code = get_claim_code_from_server
|
61
57
|
pem = BitPay::KeyUtils.generate_pem
|
62
58
|
client = BitPay::SDK::Client.new(api_uri: ROOT_ADDRESS, pem: pem, insecure: true)
|
59
|
+
sleep 1 # rate limit compliance
|
63
60
|
token = client.pair_pos_client(claim_code)
|
61
|
+
FileUtils.mkdir_p(BitPay::BITPAY_CREDENTIALS_DIR)
|
64
62
|
File.write(BitPay::PRIVATE_KEY_PATH, pem)
|
65
63
|
File.write(BitPay::TOKEN_FILE_PATH, JSON.generate(token))
|
66
|
-
client
|
67
64
|
end
|
65
|
+
client
|
68
66
|
end
|
69
67
|
|
70
68
|
def get_token_from_file
|
data/lib/bitpay/client.rb
CHANGED
@@ -14,39 +14,52 @@ module BitPay
|
|
14
14
|
module SDK
|
15
15
|
class Client
|
16
16
|
|
17
|
-
|
18
17
|
# @return [Client]
|
19
18
|
# @example
|
20
19
|
# # Create a client with a pem file created by the bitpay client:
|
21
|
-
# client = BitPay::Client.new
|
20
|
+
# client = BitPay::SDK::Client.new
|
22
21
|
def initialize(opts={})
|
23
|
-
@pem
|
24
|
-
@key
|
25
|
-
@priv_key
|
26
|
-
@pub_key
|
27
|
-
@client_id
|
28
|
-
@uri
|
29
|
-
@user_agent
|
30
|
-
@https
|
31
|
-
@https.use_ssl
|
32
|
-
@https.
|
33
|
-
@
|
22
|
+
@pem = opts[:pem] || ENV['BITPAY_PEM'] || KeyUtils.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
|
27
|
+
@uri = URI.parse opts[:api_uri] || API_URI
|
28
|
+
@user_agent = opts[:user_agent] || USER_AGENT
|
29
|
+
@https = Net::HTTP.new @uri.host, @uri.port
|
30
|
+
@https.use_ssl = true
|
31
|
+
@https.open_timeout = 10
|
32
|
+
@https.read_timeout = 10
|
33
|
+
|
34
|
+
@https.ca_file = CA_FILE
|
35
|
+
@tokens = opts[:tokens] || {}
|
34
36
|
|
35
37
|
# Option to disable certificate validation in extraordinary circumstance. NOT recommended for production use
|
36
38
|
@https.verify_mode = opts[:insecure] == true ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
|
37
39
|
|
38
40
|
# Option to enable http request debugging
|
39
41
|
@https.set_debug_output($stdout) if opts[:debug] == true
|
42
|
+
end
|
40
43
|
|
44
|
+
## Pair client with BitPay service
|
45
|
+
# => Pass empty hash {} to retreive client-initiated pairing code
|
46
|
+
# => Pass {pairingCode: 'WfD01d2'} to claim a server-initiated pairing code
|
47
|
+
#
|
48
|
+
def pair_client(params={})
|
49
|
+
pairing_request(params)
|
41
50
|
end
|
42
51
|
|
52
|
+
## Compatibility method for pos pairing
|
53
|
+
#
|
43
54
|
def pair_pos_client(claimCode)
|
44
55
|
raise BitPay::ArgumentError, "pairing code is not legal" unless verify_claim_code(claimCode)
|
45
|
-
|
46
|
-
get_token 'pos'
|
47
|
-
response
|
56
|
+
pair_client({pairingCode: claimCode})
|
48
57
|
end
|
49
58
|
|
59
|
+
## Create bitcoin invoice
|
60
|
+
#
|
61
|
+
# Defaults to pos facade, also works with merchant facade
|
62
|
+
#
|
50
63
|
def create_invoice(price:, currency:, facade: 'pos', params:{})
|
51
64
|
raise BitPay::ArgumentError, "Illegal Argument: Price must be formatted as a float" unless ( price.is_a?(Numeric) || /^[[:digit:]]+(\.[[:digit:]]{2})?$/.match(price) )
|
52
65
|
raise BitPay::ArgumentError, "Illegal Argument: Currency is invalid." unless /^[[:upper:]]{3}$/.match(currency)
|
@@ -55,29 +68,92 @@ module BitPay
|
|
55
68
|
response["data"]
|
56
69
|
end
|
57
70
|
|
71
|
+
## Gets the privileged merchant-version of the invoice
|
72
|
+
# Requires merchant facade token
|
73
|
+
#
|
74
|
+
def get_invoice(id:)
|
75
|
+
response = send_request("GET", "invoices/#{id}", facade: 'merchant')
|
76
|
+
response["data"]
|
77
|
+
end
|
78
|
+
|
79
|
+
## Gets the public version of the invoice
|
80
|
+
#
|
58
81
|
def get_public_invoice(id:)
|
59
82
|
request = Net::HTTP::Get.new("/invoices/#{id}")
|
60
83
|
response = process_request(request)
|
61
84
|
response["data"]
|
62
85
|
end
|
63
86
|
|
64
|
-
|
87
|
+
|
88
|
+
## Refund paid BitPay invoice
|
89
|
+
#
|
90
|
+
# If invoice["data"]["flags"]["refundable"] == true the a refund address was
|
91
|
+
# provided with the payment and the refund_address parameter is an optional override
|
92
|
+
#
|
93
|
+
# Amount and Currency are required fields for fully paid invoices but optional
|
94
|
+
# for under or overpaid invoices which will otherwise be completely refunded
|
95
|
+
#
|
96
|
+
# Requires merchant facade token
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# client.refund_invoice(id: 'JB49z2MsDH7FunczeyDS8j', params: {amount: 10, currency: 'USD', bitcoinAddress: '1Jtcygf8W3cEmtGgepggtjCxtmFFjrZwRV'})
|
100
|
+
#
|
101
|
+
def refund_invoice(id:, params:{})
|
102
|
+
invoice = get_invoice(id: id)
|
103
|
+
response = send_request("POST", "invoices/#{id}/refunds", facade: nil, token: invoice["token"], params: params)
|
104
|
+
response["data"]
|
105
|
+
end
|
106
|
+
|
107
|
+
## Get All Refunds for Invoice
|
108
|
+
# Returns an array of all refund requests for a specific invoice,
|
109
|
+
#
|
110
|
+
# Requires merchant facade token
|
111
|
+
#
|
112
|
+
# @example:
|
113
|
+
# client.get_all_refunds_for_invoice(id: 'JB49z2MsDH7FunczeyDS8j')
|
114
|
+
#
|
115
|
+
def get_all_refunds_for_invoice(id:)
|
116
|
+
urlpath = "invoices/#{id}/refunds"
|
117
|
+
invoice = get_invoice(id: id)
|
118
|
+
response = send_request("GET", urlpath, facade: nil, token: invoice["token"])
|
119
|
+
response["data"]
|
65
120
|
end
|
66
121
|
|
67
|
-
|
68
|
-
|
69
|
-
|
122
|
+
## Get Refund
|
123
|
+
# Requires merchant facade token
|
124
|
+
#
|
125
|
+
# @example:
|
126
|
+
# client.get_refund(id: 'JB49z2MsDH7FunczeyDS8j', request_id: '4evCrXq4EDXk4oqDXdWQhX')
|
127
|
+
#
|
128
|
+
def get_refund(id:, request_id:)
|
129
|
+
urlpath = "invoices/#{id}/refunds/#{request_id}"
|
130
|
+
invoice = get_invoice(id: id)
|
131
|
+
response = send_request("GET", urlpath, facade: nil, token: invoice["token"])
|
132
|
+
response["data"]
|
133
|
+
end
|
134
|
+
|
135
|
+
## Checks that the passed tokens are valid by
|
136
|
+
# comparing them to those that are authorized by the server
|
137
|
+
#
|
138
|
+
# Uses local @tokens variable if no tokens are passed
|
139
|
+
# in order to validate the connector is properly paired
|
140
|
+
#
|
141
|
+
def verify_tokens(tokens: @tokens)
|
142
|
+
server_tokens = refresh_tokens
|
143
|
+
tokens.each{|key, value| return false if server_tokens[key] != value}
|
70
144
|
return true
|
71
145
|
end
|
146
|
+
|
72
147
|
## Generates REST request to api endpoint
|
73
|
-
|
148
|
+
# => Defaults to merchant facade unless token or facade is explicitly provided
|
149
|
+
#
|
74
150
|
def send_request(verb, path, facade: 'merchant', params: {}, token: nil)
|
75
151
|
token ||= get_token(facade)
|
76
152
|
|
77
153
|
# Verb-specific logic
|
78
154
|
case verb.upcase
|
79
155
|
when "GET"
|
80
|
-
urlpath = '/' + path + '?
|
156
|
+
urlpath = '/' + path + '?token=' + token
|
81
157
|
request = Net::HTTP::Get.new urlpath
|
82
158
|
request['X-Signature'] = KeyUtils.sign(@uri.to_s + urlpath, @priv_key)
|
83
159
|
|
@@ -88,7 +164,6 @@ module BitPay
|
|
88
164
|
urlpath = '/' + path
|
89
165
|
request = Net::HTTP::Post.new urlpath
|
90
166
|
params[:token] = token
|
91
|
-
params[:nonce] = KeyUtils.nonce
|
92
167
|
params[:guid] = SecureRandom.uuid
|
93
168
|
params[:id] = @client_id
|
94
169
|
request.body = params.to_json
|
@@ -133,19 +208,17 @@ module BitPay
|
|
133
208
|
|
134
209
|
end
|
135
210
|
|
136
|
-
##
|
137
|
-
#
|
211
|
+
## Fetches the tokens hash from the server and
|
212
|
+
# updates @tokens
|
138
213
|
#
|
139
|
-
def
|
140
|
-
|
141
|
-
urlpath = '/tokens?nonce=' + KeyUtils.nonce
|
214
|
+
def refresh_tokens
|
215
|
+
urlpath = '/tokens'
|
142
216
|
|
143
217
|
request = Net::HTTP::Get.new(urlpath)
|
144
|
-
request['
|
145
|
-
request['
|
218
|
+
request['X-Identity'] = @pub_key
|
219
|
+
request['X-Signature'] = KeyUtils.sign(@uri.to_s + urlpath, @priv_key)
|
146
220
|
|
147
221
|
response = process_request(request)
|
148
|
-
|
149
222
|
token_array = response["data"] || {}
|
150
223
|
|
151
224
|
tokens = {}
|
@@ -158,9 +231,13 @@ module BitPay
|
|
158
231
|
|
159
232
|
end
|
160
233
|
|
161
|
-
##
|
162
|
-
|
163
|
-
|
234
|
+
## Makes a request to /tokens for pairing
|
235
|
+
# Adds passed params as post parameters
|
236
|
+
# If empty params, retrieves server-generated pairing code
|
237
|
+
# If pairingCode key/value is passed, will pair client ID to this account
|
238
|
+
# Returns response hash
|
239
|
+
#
|
240
|
+
def pairing_request(params)
|
164
241
|
urlpath = '/tokens'
|
165
242
|
request = Net::HTTP::Post.new urlpath
|
166
243
|
params[:guid] = SecureRandom.uuid
|
@@ -170,7 +247,7 @@ module BitPay
|
|
170
247
|
end
|
171
248
|
|
172
249
|
def get_token(facade)
|
173
|
-
token = @tokens[facade] ||
|
250
|
+
token = @tokens[facade] || refresh_tokens[facade] || raise(BitPayError, "Not authorized for facade: #{facade}")
|
174
251
|
end
|
175
252
|
|
176
253
|
def verify_claim_code(claim_code)
|
data/lib/bitpay/version.rb
CHANGED
data/spec/client_spec.rb
CHANGED
@@ -2,8 +2,8 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
def tokens
|
4
4
|
{"data" =>
|
5
|
-
[{"merchant" => "
|
6
|
-
{"pos" =>"
|
5
|
+
[{"merchant" => "MERCHANT_TOKEN"},
|
6
|
+
{"pos" =>"POS_TOKEN"},
|
7
7
|
{"merchant/invoice" => "9kv7gGqZLoQ2fxbKEgfgndLoxwjp5na6VtGSH3sN7buX"}
|
8
8
|
]
|
9
9
|
}
|
@@ -14,8 +14,17 @@ describe BitPay::SDK::Client do
|
|
14
14
|
let(:claim_code) { "a12bc3d" }
|
15
15
|
|
16
16
|
before do
|
17
|
-
|
18
|
-
stub_request(:get, /#{BitPay::TEST_API_URI}\/tokens.*/)
|
17
|
+
# Stub JSON responses from fixtures
|
18
|
+
stub_request(:get, /#{BitPay::TEST_API_URI}\/tokens.*/)
|
19
|
+
.to_return(:status => 200, :body => tokens.to_json, :headers => {})
|
20
|
+
stub_request(:get, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID?token=MERCHANT_TOKEN").
|
21
|
+
to_return(:body => get_fixture('invoices_{id}-GET.json'))
|
22
|
+
stub_request(:get, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID/refunds?token=MERCHANT_INVOICE_TOKEN").
|
23
|
+
to_return(:body => get_fixture('invoices_{id}_refunds-GET.json'))
|
24
|
+
stub_request(:get, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID/refunds/TEST_REQUEST_ID?token=MERCHANT_INVOICE_TOKEN").
|
25
|
+
to_return(:body => get_fixture('invoices_{id}_refunds-GET.json'))
|
26
|
+
stub_request(:post, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID/refunds").
|
27
|
+
to_return(:body => get_fixture('invoices_{id}_refunds-POST.json'))
|
19
28
|
end
|
20
29
|
|
21
30
|
describe "#initialize" do
|
@@ -36,7 +45,7 @@ describe BitPay::SDK::Client do
|
|
36
45
|
it 'should generate a get request' do
|
37
46
|
stub_request(:get, /#{BitPay::TEST_API_URI}\/whatever.*/).to_return(:body => '{"awesome": "json"}')
|
38
47
|
bitpay_client.send_request("GET", "whatever", facade: "merchant")
|
39
|
-
expect(WebMock).to have_requested(:get, "#{BitPay::TEST_API_URI}/whatever?
|
48
|
+
expect(WebMock).to have_requested(:get, "#{BitPay::TEST_API_URI}/whatever?token=MERCHANT_TOKEN")
|
40
49
|
end
|
41
50
|
end
|
42
51
|
|
@@ -68,7 +77,7 @@ describe BitPay::SDK::Client do
|
|
68
77
|
it 'short circuits on invalid pairing codes' do
|
69
78
|
100.times do
|
70
79
|
claim_code = an_illegal_claim_code
|
71
|
-
expect{bitpay_client.pair_pos_client(claim_code)}.to raise_error BitPay::ArgumentError, "pairing code is not legal"
|
80
|
+
expect { bitpay_client.pair_pos_client(claim_code) }.to raise_error BitPay::ArgumentError, "pairing code is not legal"
|
72
81
|
end
|
73
82
|
end
|
74
83
|
end
|
@@ -106,19 +115,55 @@ describe BitPay::SDK::Client do
|
|
106
115
|
end
|
107
116
|
end
|
108
117
|
|
109
|
-
describe '#
|
118
|
+
describe '#refund_invoice' do
|
119
|
+
subject { bitpay_client }
|
120
|
+
before { stub_const('ENV', {'BITPAY_PEM' => PEM}) }
|
121
|
+
it { is_expected.to respond_to(:refund_invoice) }
|
122
|
+
|
123
|
+
it 'should get the token for the invoice' do
|
124
|
+
bitpay_client.refund_invoice(id: 'TEST_INVOICE_ID')
|
125
|
+
expect(WebMock).to have_requested :get, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID?token=MERCHANT_TOKEN"
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'should generate a POST to the invoices/refund endpoint' do
|
129
|
+
bitpay_client.refund_invoice(id: 'TEST_INVOICE_ID')
|
130
|
+
expect(WebMock).to have_requested :post, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID/refunds"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe '#get_all_refunds_for_invoice' do
|
110
135
|
subject { bitpay_client }
|
111
136
|
before {stub_const('ENV', {'BITPAY_PEM' => PEM})}
|
112
|
-
it { is_expected.to respond_to(:
|
113
|
-
|
114
|
-
|
137
|
+
it { is_expected.to respond_to(:get_all_refunds_for_invoice) }
|
138
|
+
|
139
|
+
it 'should get the token for the invoice' do
|
140
|
+
bitpay_client.get_all_refunds_for_invoice(id: 'TEST_INVOICE_ID')
|
141
|
+
expect(WebMock).to have_requested :get, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID?token=MERCHANT_TOKEN"
|
142
|
+
end
|
143
|
+
it 'should GET all refunds' do
|
144
|
+
bitpay_client.get_all_refunds_for_invoice(id: 'TEST_INVOICE_ID')
|
145
|
+
expect(WebMock).to have_requested :get, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID/refunds?token=MERCHANT_INVOICE_TOKEN"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe '#get_refund' do
|
150
|
+
subject { bitpay_client }
|
151
|
+
before {stub_const('ENV', {'BITPAY_PEM' => PEM})}
|
152
|
+
it { is_expected.to respond_to(:get_refund) }
|
153
|
+
it 'should get the token for the invoice' do
|
154
|
+
bitpay_client.get_refund(id: 'TEST_INVOICE_ID', request_id: 'TEST_REQUEST_ID')
|
155
|
+
expect(WebMock).to have_requested :get, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID?token=MERCHANT_TOKEN"
|
156
|
+
end
|
157
|
+
it 'should GET a single refund' do
|
158
|
+
bitpay_client.get_refund(id: 'TEST_INVOICE_ID', request_id: 'TEST_REQUEST_ID')
|
159
|
+
expect(WebMock).to have_requested :get, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID/refunds/TEST_REQUEST_ID?token=MERCHANT_INVOICE_TOKEN"
|
115
160
|
end
|
116
161
|
end
|
117
162
|
|
118
|
-
describe "#
|
163
|
+
describe "#verify_tokens" do
|
119
164
|
subject { bitpay_client }
|
120
165
|
before {stub_const('ENV', {'BITPAY_PEM' => PEM})}
|
121
|
-
it { is_expected.to respond_to(:
|
166
|
+
it { is_expected.to respond_to(:verify_tokens) }
|
122
167
|
end
|
123
168
|
end
|
124
169
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
{
|
2
|
+
"facade": "pos/invoice",
|
3
|
+
"data": {
|
4
|
+
"url": "https://test.bitpay.com/invoice?id=2RSyNDvsiTrA31rPwnnEcd",
|
5
|
+
"status": "new",
|
6
|
+
"btcPrice": "0.037523",
|
7
|
+
"btcDue": "0.037523",
|
8
|
+
"price": 10,
|
9
|
+
"currency": "USD",
|
10
|
+
"exRates": {
|
11
|
+
"USD": 266.5
|
12
|
+
},
|
13
|
+
"invoiceTime": 1422319964413,
|
14
|
+
"expirationTime": 1422320864413,
|
15
|
+
"currentTime": 1422319964431,
|
16
|
+
"guid": "34d7be05-eb65-4f72-a2ce-79bf23e93f17",
|
17
|
+
"id": "2RSyNDvsiTrA31rPwnnEcd",
|
18
|
+
"btcPaid": "0.000000",
|
19
|
+
"rate": 266.5,
|
20
|
+
"exceptionStatus": false,
|
21
|
+
"paymentUrls": {
|
22
|
+
"BIP21": "bitcoin:mhPM48eieakd6AgCuHMwAtpFXE5yQ3N7om?amount=0.037523",
|
23
|
+
"BIP72": "bitcoin:mhPM48eieakd6AgCuHMwAtpFXE5yQ3N7om?amount=0.037523&r=https://test.bitpay.com/i/2RSyNDvsiTrA31rPwnnEcd",
|
24
|
+
"BIP72b": "bitcoin:?r=https://test.bitpay.com/i/2RSyNDvsiTrA31rPwnnEcd",
|
25
|
+
"BIP73": "https://test.bitpay.com/i/2RSyNDvsiTrA31rPwnnEcd"
|
26
|
+
},
|
27
|
+
"token": "2RPipMRUXAvt5wAfthCzF7Tj4SppBWPHGQ7hCeWYeWDm7RtwUtDds1XUNt11VTf5C6UfCAACBhsKwjW6SAocLsd7"
|
28
|
+
}
|
29
|
+
}
|