Compare commits

...

4 Commits

Author SHA1 Message Date
Paul Daigle
a8defe21d3 Bump gem version, add Changelog 2015-03-12 23:20:41 -04:00
Paul Daigle
25d6ebbcec Fix github issue 40
Both post and get requests assumed that the response from the server
would include a "data" field, which is not the case. Moved handling of
the data element back into the invoice and pairing methods.

Also, this update will remove the refund functionality from branch 2.3,
it belongs in 2.4.
2015-03-12 18:55:29 -04:00
Paul Daigle
48d3c753ee Refactor: create rest_connector module
The rest connector module moves most of the functionality of http
connection out of the client and into a module. The goal is to simplify
method calls and DRY up the code.

Changes include replacing all calls to send_request with calls to either
'get' or 'post'. process request now returns the "data" portion of the
JSON response from the server, as every method was retrieving that
separately.

removed some untested code.
2015-03-12 18:23:49 -04:00
Hamish Eisler
72487c9591 correct BTC validation 2015-03-12 18:23:02 -04:00
8 changed files with 134 additions and 132 deletions

11
CHANGELOG.md Normal file
View File

@ -0,0 +1,11 @@
# Change Log
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [2.3.3] - 2015-03-12
### Changed
- Pull refund functionality from 2.3 spec
- Bump gem version
### Fixed
- Fix Bug: GitHub issue #40 - error on return from endpoints that do not have a "data" field

View File

@ -11,9 +11,9 @@ TEST_PASS = ENV['RCTESTPASSWORD']
DASHBOARD_URL = "#{ROOT_ADDRESS}/dashboard/merchant/home"
unless
ROOT_ADDRESS &&
TEST_USER &&
TEST_PASS
ROOT_ADDRESS &&
TEST_USER &&
TEST_PASS
then
raise "Missing configuration options - see constants.rb"
raise "Missing configuration options - see constants.rb"
end

View File

@ -1,3 +1,4 @@
@invoices
Feature: creating an invoice
The user won't get any money
If they can't
@ -10,9 +11,10 @@ Feature: creating an invoice
When the user creates an invoice for <price> <currency>
Then they should recieve an invoice in response for <price> <currency>
Examples:
| price | currency |
| "5.23" | "USD" |
| price | currency |
| "5.23" | "USD" |
| "10.21" | "EUR" |
| "0.225" | "BTC" |
Scenario Outline: The invoice contains illegal characters
When the user creates an invoice for <price> <currency>
@ -20,7 +22,7 @@ Feature: creating an invoice
Examples:
| price | currency | message |
| "5,023" | "USD" | "Price must be formatted as a float" |
| "3.21" | "EaUR" | "Currency is invalid." |
| "3.21" | "EaUR" | "Currency is invalid." |
| "" | "USD" | "Price must be formatted as a float" |
| "Ten" | "USD" | "Price must be formatted as a float" |
| "10" | "" | "Currency is invalid." |

View File

@ -61,5 +61,5 @@ Given(/^the user requests a client\-side pairing$/) do
end
Then(/^they will receive a claim code$/) do
expect(@response["data"].first["pairingCode"] ).not_to be_empty
expect(@response.first["pairingCode"] ).not_to be_empty
end

View File

@ -66,7 +66,7 @@ def new_client_from_stored_values
end
def get_token_from_file
token = JSON.parse(File.read(BitPay::TOKEN_FILE_PATH))['data'][0]
token = JSON.parse(File.read(BitPay::TOKEN_FILE_PATH))[0]
{token['facade'] => token['token']}
end

View File

@ -1,4 +1,4 @@
# license Copyright 2011-2014 BitPay, Inc., MIT License
# license Copyright 2011-2015 BitPay, Inc., MIT License
# see http://opensource.org/licenses/MIT
# or https://github.com/bitpay/php-bitpay-client/blob/master/LICENSE
@ -7,13 +7,14 @@ require 'net/https'
require 'json'
require_relative 'key_utils'
require_relative 'rest_connector'
module BitPay
# This class is used to instantiate a BitPay Client object. It is expected to be thread safe.
#
module SDK
class Client
include BitPay::RestConnector
# @return [Client]
# @example
# # Create a client with a pem file created by the bitpay client:
@ -46,7 +47,8 @@ module BitPay
# => Pass {pairingCode: 'WfD01d2'} to claim a server-initiated pairing code
#
def pair_client(params={})
pairing_request(params)
tokens = post(path: 'tokens', params: params)
return tokens["data"]
end
## Compatibility method for pos pairing
@ -61,27 +63,22 @@ module BitPay
# Defaults to pos facade, also works with merchant facade
#
def create_invoice(price:, currency:, facade: 'pos', params:{})
raise BitPay::ArgumentError, "Illegal Argument: Price must be formatted as a float" unless ( price.is_a?(Numeric) || /^[[:digit:]]+(\.[[:digit:]]{2})?$/.match(price) )
raise BitPay::ArgumentError, "Illegal Argument: Price must be formatted as a float" unless
price.is_a?(Numeric) ||
/^[[:digit:]]+(\.[[:digit:]]{2})?$/.match(price) ||
currency == 'BTC' && /^[[:digit:]]+(\.[[:digit:]]{1,6})?$/.match(price)
raise BitPay::ArgumentError, "Illegal Argument: Currency is invalid." unless /^[[:upper:]]{3}$/.match(currency)
params.merge!({price: price, currency: currency})
response = send_request("POST", "invoices", facade: facade, params: params)
response["data"]
token = get_token(facade)
invoice = post(path: "invoices", token: token, params: params)
invoice["data"]
end
## Gets the privileged merchant-version of the invoice
# Requires merchant facade token
#
def get_invoice(id:)
response = send_request("GET", "invoices/#{id}", facade: 'merchant')
response["data"]
end
## Gets the public version of the invoice
#
def get_public_invoice(id:)
request = Net::HTTP::Get.new("/invoices/#{id}")
response = process_request(request)
response["data"]
invoice = get(path: "invoices/#{id}", public: true)
invoice["data"]
end
## Checks that the passed tokens are valid by
@ -95,116 +92,12 @@ module BitPay
tokens.each{|key, value| return false if server_tokens[key] != value}
return true
end
## Generates REST request to api endpoint
# => Defaults to merchant facade unless token or facade is explicitly provided
#
def send_request(verb, path, facade: 'merchant', params: {}, token: nil)
token ||= get_token(facade)
# Verb-specific logic
case verb.upcase
when "GET"
urlpath = '/' + path + '?token=' + token
request = Net::HTTP::Get.new urlpath
request['X-Signature'] = KeyUtils.sign(@uri.to_s + urlpath, @priv_key)
when "PUT"
when "POST" # Requires a GUID
urlpath = '/' + path
request = Net::HTTP::Post.new urlpath
params[:token] = token
params[:guid] = SecureRandom.uuid
params[:id] = @client_id
request.body = params.to_json
request['X-Signature'] = KeyUtils.sign(@uri.to_s + urlpath + request.body, @priv_key)
when "DELETE"
raise(BitPayError, "Invalid HTTP verb: #{verb.upcase}")
end
# Build request headers and submit
request['X-Identity'] = @pub_key
response = process_request(request)
end
##### PRIVATE METHODS #####
private
## Processes HTTP Request and returns parsed response
# Otherwise throws error
#
def process_request(request)
request['User-Agent'] = @user_agent
request['Content-Type'] = 'application/json'
request['X-BitPay-Plugin-Info'] = 'Rubylib' + VERSION
begin
response = @https.request request
rescue => error
raise BitPay::ConnectionError, "#{error.message}"
end
if response.kind_of? Net::HTTPSuccess
return JSON.parse(response.body)
elsif JSON.parse(response.body)["error"]
raise(BitPayError, "#{response.code}: #{JSON.parse(response.body)['error']}")
else
raise BitPayError, "#{response.code}: #{JSON.parse(response.body)}"
end
end
## Fetches the tokens hash from the server and
# updates @tokens
#
def refresh_tokens
urlpath = '/tokens'
request = Net::HTTP::Get.new(urlpath)
request['X-Identity'] = @pub_key
request['X-Signature'] = KeyUtils.sign(@uri.to_s + urlpath, @priv_key)
response = process_request(request)
token_array = response["data"] || {}
tokens = {}
token_array.each do |t|
tokens[t.keys.first] = t.values.first
end
@tokens = tokens
return tokens
end
## Makes a request to /tokens for pairing
# Adds passed params as post parameters
# If empty params, retrieves server-generated pairing code
# If pairingCode key/value is passed, will pair client ID to this account
# Returns response hash
#
def pairing_request(params)
urlpath = '/tokens'
request = Net::HTTP::Post.new urlpath
params[:guid] = SecureRandom.uuid
params[:id] = @client_id
request.body = params.to_json
process_request(request)
end
def get_token(facade)
token = @tokens[facade] || refresh_tokens[facade] || raise(BitPayError, "Not authorized for facade: #{facade}")
end
def verify_claim_code(claim_code)
regex = /^[[:alnum:]]{7}$/
matches = regex.match(claim_code)
matches = regex.match(claim_code)
!(matches.nil?)
end
end

View File

@ -0,0 +1,96 @@
# license Copyright 2011-2015 BitPay, Inc., MIT License
# see http://opensource.org/licenses/MIT
# or https://github.com/bitpay/php-bitpay-client/blob/master/LICENSE
module BitPay
module RestConnector
def send_request(verb, path, facade: 'merchant', params: {}, token: nil)
token ||= get_token(facade)
case verb.upcase
when "GET"
return get(path: path, token:token)
when "POST"
return post(path: path, token: token, params: params)
else
raise(BitPayError, "Invalid HTTP verb: #{verb.upcase}")
end
end
def get(path:, token: nil, public: false)
urlpath = '/' + path
urlpath = urlpath + '?token=' + token if token
request = Net::HTTP::Get.new urlpath
unless public
request['X-Signature'] = KeyUtils.sign(@uri.to_s + urlpath, @priv_key)
request['X-Identity'] = @pub_key
end
process_request(request)
end
def post(path:, token: nil, params:)
urlpath = '/' + path
request = Net::HTTP::Post.new urlpath
params[:token] = token if token
params[:guid] = SecureRandom.uuid
params[:id] = @client_id
request.body = params.to_json
if token
request['X-Signature'] = KeyUtils.sign(@uri.to_s + urlpath + request.body, @priv_key)
request['X-Identity'] = @pub_key
end
process_request(request)
end
private
## Processes HTTP Request and returns parsed response
# Otherwise throws error
#
def process_request(request)
request['User-Agent'] = @user_agent
request['Content-Type'] = 'application/json'
request['X-BitPay-Plugin-Info'] = 'Rubylib' + VERSION
begin
response = @https.request request
rescue => error
raise BitPay::ConnectionError, "#{error.message}"
end
if response.kind_of? Net::HTTPSuccess
return JSON.parse(response.body)
elsif JSON.parse(response.body)["error"]
raise(BitPayError, "#{response.code}: #{JSON.parse(response.body)['error']}")
else
raise BitPayError, "#{response.code}: #{JSON.parse(response.body)}"
end
end
## Fetches the tokens hash from the server and
# updates @tokens
#
def refresh_tokens
response = get(path: 'tokens')["data"]
token_array = response || {}
tokens = {}
token_array.each do |t|
tokens[t.keys.first] = t.values.first
end
@tokens = tokens
return tokens
end
## Makes a request to /tokens for pairing
# Adds passed params as post parameters
# If empty params, retrieves server-generated pairing code
# If pairingCode key/value is passed, will pair client ID to this account
# Returns response hash
#
def get_token(facade)
token = @tokens[facade] || refresh_tokens[facade] || raise(BitPayError, "Not authorized for facade: #{facade}")
end
end
end

View File

@ -3,5 +3,5 @@
# or https://github.com/bitpay/php-bitpay-client/blob/master/LICENSE
module BitPay
VERSION = '2.3.1'
VERSION = '2.3.3'
end