Compare commits
28 Commits
master
...
2.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfc7cb9ccf | ||
|
|
f64490125c | ||
|
|
deca5e4e8b | ||
|
|
c8ff36fa13 | ||
|
|
08f0a40aa2 | ||
|
|
2229f88db6 | ||
|
|
1f0932385a | ||
|
|
c84c5cbbec | ||
|
|
27017c2a39 | ||
|
|
98a71c3acf | ||
|
|
1276c59d9a | ||
|
|
dd67d86b6a | ||
|
|
a5fd1bcac2 | ||
|
|
c653e10390 | ||
|
|
e91b95cf36 | ||
|
|
3f4e5b4dbc | ||
|
|
89f6355394 | ||
|
|
b5f559bf25 | ||
|
|
d595427e05 | ||
|
|
a0d3a4773d | ||
|
|
4f96d636c6 | ||
|
|
473e67bce8 | ||
|
|
c77cc3a26b | ||
|
|
e1783b5c1c | ||
|
|
3e237970c5 | ||
|
|
4e813a2b6c | ||
|
|
e526f84236 | ||
|
|
3eb56c025f |
6
.gitignore
vendored
6
.gitignore
vendored
@ -5,9 +5,3 @@ pkg
|
||||
.c9
|
||||
.ruby-version
|
||||
.ruby-gemset
|
||||
*.swo
|
||||
*.swp
|
||||
bitpaykey.pem
|
||||
constants.txt
|
||||
coverage/
|
||||
.pem.data
|
||||
|
||||
@ -1,5 +1,2 @@
|
||||
sudo: false
|
||||
rvm:
|
||||
- 2.1.10
|
||||
- 2.2.5
|
||||
- 2.3.1
|
||||
- 2.1.0
|
||||
|
||||
24
CHANGELOG.md
24
CHANGELOG.md
@ -1,24 +0,0 @@
|
||||
# Change Log
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [2.4.4] - 2015-04-14
|
||||
### Changed
|
||||
- Separated key utilities into its own Gem
|
||||
|
||||
## [2.4.3] - 2015-04-13
|
||||
### Changed
|
||||
- Loosened production gem requirements from patch level to major level
|
||||
|
||||
## [2.4.2] - 2015-03-11
|
||||
### Fixed
|
||||
- GitHub issue 39: handling post paths that include a ? and require a token. A workaround exists for this issue.
|
||||
|
||||
## [2.4.1] - 2015-03-11
|
||||
### Fixed
|
||||
- GitHub issue 40: error for endpoints that did not return a 'data' field
|
||||
|
||||
## [2.4.0] - 2015-03-05
|
||||
### Changed
|
||||
- Add feature: Accept refunds
|
||||
- Fix Bug: Accept bitcoin payments like 0.003
|
||||
233
GUIDE.md
233
GUIDE.md
@ -1,233 +0,0 @@
|
||||
# Using the BitPay Ruby Client Library
|
||||
## Prerequisites
|
||||
You must have a BitPay merchant account to use this library. It's free to [sign-up for a BitPay merchant account](https://bitpay.com/start).
|
||||
|
||||
Once you have a BitPay merchant account, you will need [a working BitPay Access Token](/api/getting-access.html) – this can be done either [via the library](#pairing) or manually in [the BitPay Dashboard](https://bitpay.com/tokens).
|
||||
|
||||
## Quick Start
|
||||
### Installation
|
||||
```bash
|
||||
gem install bitpay-sdk
|
||||
```
|
||||
|
||||
In your Gemfile:
|
||||
|
||||
```ruby
|
||||
gem 'bitpay-sdk', :require => 'bitpay_sdk'
|
||||
```
|
||||
|
||||
Or directly:
|
||||
```ruby
|
||||
require 'bitpay_sdk'
|
||||
```
|
||||
|
||||
### Configuration
|
||||
The bitpay client creates a cryptographically secure connection to your server by pairing an API code with keys generated by the library. The client can be initialized with pre-existing keys passed in as a pem file, or paired if initialized with a pem file and a tokens hash. Examples can be found in the cucumber step helpers.
|
||||
|
||||
## Pairing
|
||||
Most calls to the BitPay REST API require that your client is paired with the bitpay.com server. To pair with bitpay.com you need to have an approved merchant account.
|
||||
|
||||
Your client can be paired via the `pos` (point-of-sale) or `merchant` facade (or both). The `pos` facade allows for invoices to be created. The `merchant` facade has broader privileges to view all invoices, bills, and ledger entries, as well as to issue refunds. Consider the level of access required when you pair your client.
|
||||
|
||||
### A quick note on keys
|
||||
|
||||
The BitPay client gem includes the BitPay KeyUtilities gem, which can be used to generate new public private key pairs which it returns in PEM format. However, there are no methods which save the keys anywhere, so it is your responsibility to store the PEM file somewhere secure.
|
||||
|
||||
### BitPay authentication
|
||||
|
||||
BitPay authentication depends on four parts:
|
||||
|
||||
1. An account on our servers.
|
||||
1. A token shared between the client and the server.
|
||||
1. A public key, shared between the client and the server.
|
||||
1. A private key, held exclusively by the client.
|
||||
|
||||
In order to complete authentication, you have to associate your private key with a token, and associate that token with an account. Once this authentication is complete, as long as you have the private key, you never have to authenticate again. The token you created will always be associated with that private key, so any time you create a new bitpay client object with that key, it is authenticated with BitPay. This is true whether you use the ruby-client, python client, or no client at all, the key is the important thing.
|
||||
|
||||
There are two ways to authenticate, from the client side or the server side. The Ruby Client supports both.
|
||||
|
||||
To pair from the server side, you log in to the BitPay server, navigate to dashboard/merchant/api-tokens, and create a new token. This creates a new token, which is associated with your account. It is not associated with a key, so we provide a pairing code that you can use as a one time secret to associate the token with a key. From the client side, you can use the client.pair_pos_client(<pairing_code>) method to associate that method with a key held by the client.
|
||||
|
||||
To pair from the client side, you use the client to call the /tokens endpoint on the server with no parameters. This creates a token on the server and associates that token with a public key. What it doesn't do is associate that token to an account (because we don't know what account to associate with). This call returns a pairing code, which is a one time secret that allows you to find the token you just created. In order to associate the token with an account, you log in to the BitPay server, and use the dashboard/merchant/api-tokens interface to associate the token with a specific account. And example of client side pairing is shown below.
|
||||
|
||||
### Pairing Programatically
|
||||
|
||||
If you are developing a client with built-in pairing capability, you can pair programattically using the `pair_client` method. This method can be called in two ways:
|
||||
|
||||
* `pair_client()` will perform a client-initiated pairing, and will provide a pairing code that can be entered at https://bitpay.com/dashboard/merchant/api-tokens to assign either `merchant` or `pos` facade.
|
||||
* `pair_client('pairing_code')` will complete a server-initiated pairing, when provided a pre-generated pairing code from https://bitpay.com/dashboard/merchant/api-tokens. In this case, the `pos` facade will be automatically assigned.
|
||||
|
||||
This is an example of creating a paired client with the BitPay toolset.
|
||||
|
||||
```bash
|
||||
$ gem install bitpay-sdk
|
||||
Successfully installed bitpay-sdk-2.2.0
|
||||
1 gem installed
|
||||
$ irb
|
||||
2.1.1 :001 > require 'bitpay_sdk'
|
||||
=> true
|
||||
2.1.2 :002 > pem = BitPay::KeyUtils.generate_pem
|
||||
=> "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIH8oSTRm8lVMTVOsDZleIB8AmkiuHnp+ctEknqeUmZahoAcGBSuBBAAK\noUQDQgAEbjhdKA+X8NEKgcbHhyJaBMvePV7Sj6AQuOMQzuZYdskdkPY1/jlfQwNG\n4GVd/zSw4uhfukw/SDBOEKlQGVAmxQ==\n-----END EC PRIVATE KEY-----\n"
|
||||
2.1.1 :002 > client = BitPay::SDK::Client.new(api_uri: 'https://test.bitpay.com', pem: pem)
|
||||
=> #<BitPay::SDK::Client:0x000000019c6d40 @pem="---... @tokens={}>
|
||||
2.1.1 :003 > client.pair_client()
|
||||
=> {"data"=>[{"policies"=>[{"policy"=>"id", "method"=>"inactive", "params"=>["Tf49SFeiUAtytFEW2EUqZgWj32nP51PK73M"]}], "token"=>"BKQyVdaGQZAArdkkSuvtZN5gcN2355c8vXLj5eFPkfuK", "dateCreated"=>1422474475162, "pairingExpiration"=>1422560875162, "pairingCode"=>"Vy76yTh"}]}
|
||||
```
|
||||
|
||||
As described above, using the value from the `pairingCode` element, visit https://test.bitpay.com/api-tokens and search to register for the appropriate facade. That client is now paired. As previously mentioned, you must save the pem string you generated in order to use the client again.
|
||||
|
||||
## General Usage
|
||||
|
||||
### Initialize the client
|
||||
|
||||
```ruby
|
||||
client = BitPay::SDK::Client.new(pem: File.read('bitpaykey.pem'))
|
||||
```
|
||||
|
||||
Optional parameters:
|
||||
* `api_uri` - specify a different api endpoint (e.g. 'https://test.bitpay.com'). Ensure no trailing slash.
|
||||
* `tokens` - pass a stored hash of bitpay API tokens
|
||||
* `user-agent` - specify a custom user-agent value
|
||||
* `debug: true` - enable HTTP request logging to $stdout
|
||||
* `insecure: true` - disable HTTPs certificate validation (for local test environments)
|
||||
|
||||
### Create a new bitcoin invoice
|
||||
|
||||
```ruby
|
||||
invoice = client.create_invoice(price: <price>, currency: <currency>)
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
Response will be a hash with information on your newly created invoice. Send your customer to the `url` to complete payment:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"url": "https://bitpay.com/invoice?id=NKaqMuZWy3BAcP77RdkEEv",
|
||||
"paymentUrls": {
|
||||
"BIP21": "bitcoin:mvYRECDxKPaPHnjNz9ZxiTpbx29xYNoRy4?amount=0.3745",
|
||||
"BIP72": "bitcoin:mvYRECDxKPaPHnjNz9ZxiTpbx29xYNoRy4?amount=0.3745&r=https://bitpay.com/i/NKaqMuZWy3BAcP77RdkEEv",
|
||||
"BIP72b": "bitcoin:?r=https://bitpay.com/i/NKaqMuZWy3BAcP77RdkEEv",
|
||||
"BIP73": "https://bitpay.com/i/NKaqMuZWy3BAcP77RdkEEv"
|
||||
},
|
||||
"status": "new",
|
||||
"btcPrice": "0.3745",
|
||||
"btcDue": "0.3745",
|
||||
"price": 148,
|
||||
"currency": "USD",
|
||||
"exRates": {
|
||||
"USD": 395.20000000000005
|
||||
},
|
||||
"invoiceTime": 1415987168612,
|
||||
"expirationTime": 1415988068612,
|
||||
"currentTime": 1415987168629,
|
||||
"guid": "438e8237-fff1-483c-81b4-dc7dba28922a",
|
||||
"id": "NKaqMuZWy3BAcP77RdkEEv",
|
||||
"transactions": [
|
||||
|
||||
],
|
||||
"btcPaid": "0.0000",
|
||||
"rate": 395.2,
|
||||
"exceptionStatus": false,
|
||||
"token": "9kZgUXFb5AC6qMuLaMpP9WopbM8X2UjMhkphKKdaprRbSKgUJNE6JNTX8bGsmgxKKv",
|
||||
"buyer": {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are many options available when creating invoices, which are listed in the [BitPay API documentation](https://bitpay.com/bitcoin-payment-gateway-api).
|
||||
|
||||
### Get invoice status
|
||||
The ruby library provides two methods for fetching an existing invoice:
|
||||
|
||||
```ruby
|
||||
# For authorized clients with a 'merchant' token
|
||||
client.get_invoice(id: 'PvVhgBfA7wKPWhuVC24rJo')
|
||||
|
||||
# For non-authenticated clients (public facade)
|
||||
# Returns the public subset of invoice fields
|
||||
client.get_public_invoice(id: 'PvVhgBfA7wKPWhuVC24rJo')
|
||||
```
|
||||
|
||||
### Create a refund request
|
||||
|
||||
Clients with a `merchant` token can initiate a refund request for a paid invoice:
|
||||
|
||||
```ruby
|
||||
client.refund_invoice(id: '6pbV13VBZfGFJ8BBmXmLZ8', params: {amount: 10, currency: 'USD'})
|
||||
```
|
||||
|
||||
Refund rules:
|
||||
|
||||
* Invoices cannot be refunded prior to 6 blockchain confirmations
|
||||
* Invoices without `["flags"]["refundable"] == true` must specify a `bitcoinAddress` param (one was not provided as part of the transaction)
|
||||
* Invoices that are paid in full must specify an `amount` and `currency` param to indicate the amount to be refunded
|
||||
|
||||
### View Refund Requests
|
||||
|
||||
The ruby library provides two methods for viewing refund requests. Both require a `merchant` token.
|
||||
|
||||
```ruby
|
||||
# To get an array of all refunds against a specific invoice
|
||||
client.get_all_refunds_for_invoice(id: 'PvVhgBfA7wKPWhuVC24rJo')
|
||||
|
||||
# To get a specific refund for a specific invoice
|
||||
client.get_refund(id: 'JB49z2MsDH7FunczeyDS8j', request_id: '4evCrXq4EDXk4oqDXdWQhX')
|
||||
```
|
||||
### Cancel Refund Requests
|
||||
|
||||
Requires a `merchant` token.
|
||||
|
||||
```ruby
|
||||
client.cancel_refund(id: 'JB49z2MsDH7FunczeyDS8j', request_id: '4evCrXq4EDXk4oqDXdWQhX')
|
||||
```
|
||||
|
||||
### Make a HTTP request directly against the REST API
|
||||
|
||||
For API tasks which lack a dedicated library method, BitPay provides methods that will automatically apply the proper cryptographic parameters to a request.
|
||||
|
||||
```ruby
|
||||
client.send_request("GET", "invoices/JB49z2MsDH7FunczeyDS8j", facade: 'merchant')
|
||||
## This request is identical to:
|
||||
token = client.get_token("merchant")
|
||||
client.get(path: "invoices/JB49z2MsDH7FunczeyDS8j", token: token)
|
||||
|
||||
## post requests are also possible
|
||||
token = client.get_token("merchant")
|
||||
client.post(path: "tokens", token: token, params: {facade: "pos"}) #returns a new token with pairing code
|
||||
## equivalent to
|
||||
client.send_request("POST", "tokens", facade: 'merchant', params: {facade: 'pos'})
|
||||
```
|
||||
Usage:
|
||||
* Specify HTTP verb and REST endpoint
|
||||
* Specifying a `facade` will fetch and apply the corresponding `token`
|
||||
* Alternatively provide a `token` explicitly
|
||||
* For `POST` requests, the `params` hash will be included as the message body
|
||||
|
||||
## Testnet Usage
|
||||
|
||||
During development and testing, take advantage of the [Bitcoin TestNet](https://en.bitcoin.it/wiki/Testnet) by passing a custom `api_uri` option on initialization:
|
||||
|
||||
```ruby
|
||||
BitPay::SDK::Client.new({api_uri: "https://test.bitpay.com/api"})
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## API Documentation
|
||||
|
||||
API Documentation is available on the [BitPay site](https://bitpay.com/api).
|
||||
|
||||
## Running the Tests
|
||||
|
||||
In order to run the tests, you must have phantomjs installed and on your PATH.
|
||||
|
||||
The tests require that environment variables be set for the bitpay server, user name, password, an invoice id for refunds and a valid testnet bitcoin address for refunds. First run:
|
||||
|
||||
```bash
|
||||
$ source ./spec/set_constants.sh https://test.bitpay.com <yourusername> <yourpassword> <a-confirmed-invoice-id> <a-valid-testnet-address>
|
||||
$ bundle install
|
||||
$ bundle exec rake
|
||||
```
|
||||
|
||||
Tests are likely to run up against rate limiters on test.bitpay.com if used too frequently. Rake tasks which interact directly with BitPay will not run for the general public.
|
||||
4
Gemfile
4
Gemfile
@ -1,2 +1,6 @@
|
||||
source 'https://rubygems.org'
|
||||
gemspec
|
||||
|
||||
platform :jruby do
|
||||
gem 'jruby-openssl'
|
||||
end
|
||||
86
README.md
86
README.md
@ -1,16 +1,82 @@
|
||||
# BitPay Library for Ruby
|
||||
|
||||
[](https://raw.githubusercontent.com/bitpay/ruby-client/master/LICENSE.md)
|
||||
[](https://travis-ci.org/bitpay/ruby-client)
|
||||
[](https://rubygems.org/gems/bitpay-sdk)
|
||||
[](https://coveralls.io/r/bitpay/ruby-client?branch=master)
|
||||
[](https://codeclimate.com/github/bitpay/ruby-client)
|
||||
|
||||
# BitPay Library for Ruby [](http://travis-ci.org/bitpay/ruby-client)
|
||||
Powerful, flexible, lightweight interface to the BitPay Bitcoin Payment Gateway API.
|
||||
|
||||
The `bitpay-sdk` gem provides all the programattic tools required to implement a ruby client application for the BitPay REST API.
|
||||
## Installation
|
||||
|
||||
## [Getting Started »](https://github.com/bitpay/ruby-client/blob/master/GUIDE.md)
|
||||
gem install bitpay-client
|
||||
|
||||
In your Gemfile:
|
||||
|
||||
gem 'bitpay-client', :require => 'bitpay'
|
||||
|
||||
Or directly:
|
||||
|
||||
require 'bitpay'
|
||||
|
||||
## Configuration
|
||||
|
||||
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.
|
||||
|
||||
The client will generate a key when initialized if one does not already exist.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Pairing with Bitpay.com
|
||||
|
||||
To pair with bitpay.com you need to have an approved merchant account.
|
||||
1. Login to your account
|
||||
1. Navigate to bitpay.com/api-tokens (Dashboard > My Account > API Tokens)
|
||||
1. Copy an existing pairing code or create a new token and copy the pairing code.
|
||||
1. Use the bitpay command line tool to pair with bitpay.com `bitpay pair <pairing_code>`
|
||||
|
||||
### To create an invoice with a paired client:
|
||||
|
||||
client = BitPay::Client.new
|
||||
invoice = client.create_invoice (id: <id>, price: <price>, currency: <currency>, facade: <facade>)
|
||||
|
||||
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.
|
||||
|
||||
Response will be a hash with information on your newly created invoice. Send your customer to the `url` to complete payment:
|
||||
|
||||
{
|
||||
"id" => "DGrAEmbsXe9bavBPMJ8kuk",
|
||||
"url" => "https://bitpay.com/invoice?id=DGrAEmbsXe9bavBPMJ8kuk",
|
||||
"status" => "new",
|
||||
"btcPrice" => "0.0495",
|
||||
"price" => 10,
|
||||
"currency" => "USD",
|
||||
"invoiceTime" => 1383265343674,
|
||||
"expirationTime" => 1383266243674,
|
||||
"currentTime" => 1383265957613
|
||||
}
|
||||
|
||||
There are many options available when creating invoices, which are listed in the [BitPay API documentation](https://bitpay.com/bitcoin-payment-gateway-api).
|
||||
|
||||
To get updated information on this invoice, make a get call with the id returned:
|
||||
|
||||
invoice = client.get_public_invoice(DGrAEmbsXe9bavBPMJ8kuk)'
|
||||
|
||||
## Testnet Usage
|
||||
|
||||
During development and testing, take advantage of the [Bitcoin TestNet](https://en.bitcoin.it/wiki/Testnet) by passing a custom `api_uri` option on initialization:
|
||||
|
||||
BitPay::Client.new("myAPIKey", {api_uri: "https://test.bitpay.com/api"})
|
||||
|
||||
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.
|
||||
|
||||
## API Documentation
|
||||
|
||||
API Documentation is available on the [BitPay site](https://bitpay.com/api).
|
||||
|
||||
## RDoc/YARD Documentation
|
||||
The code has been fully code documented, and the latest version is always available at the [Rubydoc Site](http://rubydoc.info/gems/bitpay-client).
|
||||
|
||||
## Running the Tests
|
||||
|
||||
$ bundle install
|
||||
$ bundle exec rake
|
||||
|
||||
In addition to a full test suite, there is Travis integration for MRI 1.9, JRuby and Rubinius.
|
||||
|
||||
## Found a bug?
|
||||
Let us know! Send a pull request or a patch. Questions? Ask! We're here to help. We will respond to all filed issues.
|
||||
|
||||
67
Rakefile
67
Rakefile
@ -1,24 +1,15 @@
|
||||
require "bundler/gem_tasks"
|
||||
require 'rspec/core/rake_task'
|
||||
require 'capybara'
|
||||
require 'capybara/poltergeist'
|
||||
require 'mongo'
|
||||
require 'cucumber'
|
||||
require 'cucumber/rake/task'
|
||||
|
||||
require_relative 'config/constants.rb'
|
||||
require_relative 'config/capybara.rb'
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
|
||||
#task :default => :spec
|
||||
task :default => :default_tasks
|
||||
|
||||
Cucumber::Rake::Task.new(:features) do |t|
|
||||
t.cucumber_opts = "features --format pretty"
|
||||
end
|
||||
|
||||
desc "Run BitPay tests"
|
||||
task :default_tasks do
|
||||
Rake::Task["spec"].invoke
|
||||
Rake::Task["features"].invoke
|
||||
end
|
||||
task :default => :spec
|
||||
|
||||
desc "Bitpay Tasks"
|
||||
namespace :bitpay do
|
||||
@ -26,14 +17,26 @@ namespace :bitpay do
|
||||
desc "Clear all claim codes from the test server."
|
||||
task :clear_claim_codes do
|
||||
puts "clearing claim codes"
|
||||
client = Mongo::MongoClient.new
|
||||
db = client['bitpay-dev']
|
||||
coll = db['tokenaccesses']
|
||||
coll.remove()
|
||||
Capybara.visit ROOT_ADDRESS
|
||||
Capybara.click_link('Login')
|
||||
Capybara.fill_in 'email', :with => TEST_USER
|
||||
Capybara.fill_in 'password', :with => TEST_PASS
|
||||
Capybara.click_button('loginButton')
|
||||
Capybara.click_link "My Account"
|
||||
Capybara.click_link "API Tokens", match: :first
|
||||
while Capybara.page.has_selector?(".token-claimcode") || Capybara.page.has_selector?(".token-requiredsins-key") do
|
||||
Capybara.page.find(".api-manager-actions-edit", match: :first).click
|
||||
Capybara.page.find(".api-manager-actions-revoke", match: :first).click
|
||||
Capybara.click_button("Confirm Revoke")
|
||||
# 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
|
||||
Capybara.page.go_back
|
||||
Capybara.click_link "API Tokens", match: :first
|
||||
end
|
||||
puts "claim codes cleared"
|
||||
end
|
||||
|
||||
desc "Clear rate limiters from local mongo host"
|
||||
|
||||
task :clear_rate_limiters do
|
||||
puts "clearing rate limiters"
|
||||
client = Mongo::MongoClient.new
|
||||
@ -43,35 +46,7 @@ namespace :bitpay do
|
||||
puts "rate limiters cleared"
|
||||
end
|
||||
|
||||
desc "Clear local pem and token file"
|
||||
task :clear_local_files do
|
||||
puts "clearing local files"
|
||||
HOME_DIR = File.join(Dir.home, '.bitpay')
|
||||
KEY_FILE = File.join(HOME_DIR, 'bitpay.pem')
|
||||
TOKEN_FILE = File.join(HOME_DIR, 'tokens.json')
|
||||
File.delete(KEY_FILE) if File.file?(KEY_FILE)
|
||||
File.delete(TOKEN_FILE) if File.file?(TOKEN_FILE)
|
||||
puts "local files cleared"
|
||||
end
|
||||
|
||||
desc "Clear tokens, rate limiters, and local files."
|
||||
task :clear do
|
||||
["bitpay:clear_local_files", "bitpay:clear_rate_limiters", "bitpay:clear_claim_codes"].each{|task| Rake::Task[task].reenable}
|
||||
["bitpay:clear_local_files", "bitpay:clear_rate_limiters", "bitpay:clear_claim_codes"].each{|task| Rake::Task[task].invoke}
|
||||
end
|
||||
|
||||
desc "Run specs and clear claim codes and rate_limiters."
|
||||
task :spec_clear => ['spec', 'clear_claim_codes', 'clear_rate_limiters']
|
||||
|
||||
desc "Run specs, clear data, run cukes, clear data"
|
||||
task :tests_clear do
|
||||
Rake::Task["bitpay:clear"].invoke
|
||||
Rake::Task["spec"].invoke
|
||||
Rake::Task["bitpay:clear"].reenable
|
||||
Rake::Task["bitpay:clear"].invoke
|
||||
Rake::Task["features"].invoke
|
||||
Rake::Task["bitpay:clear"].reenable
|
||||
Rake::Task["bitpay:clear"].invoke
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
3
bin/bitpay
Executable file
3
bin/bitpay
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env ruby
|
||||
require_relative '../lib/bitpay'
|
||||
require_relative '../lib/bitpay/cli'
|
||||
35
bitpay-client.gemspec
Normal file
35
bitpay-client.gemspec
Normal file
@ -0,0 +1,35 @@
|
||||
require './lib/bitpay/version.rb'
|
||||
Gem::Specification.new do |s|
|
||||
s.name = 'bitpay-client'
|
||||
s.version = BitPay::VERSION
|
||||
s.licenses = ['MIT']
|
||||
s.authors = 'Bitpay, Inc.'
|
||||
s.email = 'info@bitpay.com'
|
||||
s.homepage = 'https://github.com/bitpay/ruby-client'
|
||||
s.summary = 'Official ruby client library for the BitPay API'
|
||||
s.description = 'Powerful, flexible, lightweight, thread-safe interface to the BitPay developers API'
|
||||
|
||||
s.files = `git ls-files`.split("\n")
|
||||
s.require_paths = ["lib"]
|
||||
s.rubyforge_project = s.name
|
||||
s.required_rubygems_version = '>= 1.3.4'
|
||||
s.required_ruby_version = '~> 2.1'
|
||||
s.bindir = 'bin'
|
||||
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
||||
|
||||
s.add_dependency 'json'
|
||||
s.add_dependency 'rack', '>= 0'
|
||||
s.add_dependency 'ecdsa'
|
||||
s.add_dependency 'commander'
|
||||
|
||||
s.add_development_dependency 'rake'
|
||||
s.add_development_dependency 'webmock'
|
||||
s.add_development_dependency 'pry'
|
||||
s.add_development_dependency 'pry-byebug'
|
||||
s.add_development_dependency 'pry-rescue'
|
||||
s.add_development_dependency 'capybara'
|
||||
s.add_development_dependency 'poltergeist'
|
||||
s.add_development_dependency 'airborne'
|
||||
s.add_development_dependency 'rspec'
|
||||
s.add_development_dependency 'mongo'
|
||||
end
|
||||
@ -1,33 +0,0 @@
|
||||
require './lib/bitpay/version.rb'
|
||||
Gem::Specification.new do |s|
|
||||
s.name = 'bitpay-sdk'
|
||||
s.version = BitPay::VERSION
|
||||
s.licenses = ['MIT']
|
||||
s.authors = 'Bitpay, Inc.'
|
||||
s.email = 'info@bitpay.com'
|
||||
s.homepage = 'https://github.com/bitpay/ruby-client'
|
||||
s.summary = 'Official Ruby library for the BitPay API'
|
||||
s.description = 'Powerful, flexible, lightweight, thread-safe interface to the BitPay developers API'
|
||||
|
||||
s.files = `git ls-files`.split("\n")
|
||||
s.require_paths = ["lib"]
|
||||
s.rubyforge_project = s.name
|
||||
s.required_rubygems_version = '>= 1.3.4'
|
||||
s.required_ruby_version = '~> 2.1'
|
||||
s.bindir = 'bin'
|
||||
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
||||
|
||||
s.add_dependency 'bitpay-key-utils', '~>2.0.0'
|
||||
|
||||
s.add_development_dependency 'rack', '~> 2.0'
|
||||
s.add_development_dependency 'rake', '12.0'
|
||||
s.add_development_dependency 'webmock', '1.18.0'
|
||||
s.add_development_dependency 'pry', '0.10.1'
|
||||
s.add_development_dependency 'pry-byebug', '2.0.0'
|
||||
s.add_development_dependency 'pry-rescue', '1.4.1'
|
||||
s.add_development_dependency 'cucumber', '~> 1.3.17'
|
||||
s.add_development_dependency 'airborne', '0.0.20'
|
||||
s.add_development_dependency 'rspec', '3.1.0'
|
||||
s.add_development_dependency 'mongo', '1.11.1'
|
||||
s.add_development_dependency 'coveralls'
|
||||
end
|
||||
5
config/capybara.rb
Normal file
5
config/capybara.rb
Normal file
@ -0,0 +1,5 @@
|
||||
Capybara.javascript_driver = :poltergeist
|
||||
Capybara.default_driver = :poltergeist
|
||||
Capybara.register_driver :poltergeist do |app|
|
||||
Capybara::Poltergeist::Driver.new(app, js_errors: false, phantomjs_options: ['--ignore-ssl-errors=yes'])
|
||||
end
|
||||
@ -1,12 +1,3 @@
|
||||
## Verifies test variables have been set correctly
|
||||
#
|
||||
# Use 'set_constants.sh' to pre-configure test variables
|
||||
# e.g.
|
||||
# source ./spec/set_constants.sh https://test.bitpay.com testuser@gmail.com mypassword
|
||||
#
|
||||
|
||||
APIURI = ENV['BITPAYAPI']
|
||||
|
||||
# Specify a bitpay txid which has 6+ confirmations. Default belongs to 'bitpayrubyclient@gmail.com' test account
|
||||
REFUND_TRANSACTION = ENV['REFUND_TRANSACTION']
|
||||
REFUND_ADDRESS = ENV['REFUND_ADDRESS']
|
||||
ROOT_ADDRESS = ENV['RCROOTADDRESS']
|
||||
TEST_USER = ENV['RCTESTUSER']
|
||||
TEST_PASS = ENV['RCTESTPASSWORD']
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
@invoices
|
||||
Feature: creating an invoice
|
||||
The user won't get any money
|
||||
If they can't
|
||||
Create Invoices
|
||||
|
||||
Background:
|
||||
Given the user is authenticated with BitPay
|
||||
|
||||
Scenario Outline: The request is correct
|
||||
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" |
|
||||
| "10.21" | "EUR" |
|
||||
| "0.225" | "BTC" |
|
||||
|
||||
Scenario Outline: The invoice contains illegal characters
|
||||
When the user creates an invoice for <price> <currency>
|
||||
Then they will receive a BitPay::ArgumentError matching <message>
|
||||
Examples:
|
||||
| price | currency | message |
|
||||
| "5,023" | "USD" | "Price must be formatted as a float" |
|
||||
| "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." |
|
||||
@ -1,19 +0,0 @@
|
||||
Feature: pairing with bitpay
|
||||
In order to access bitpay
|
||||
It is required that the library
|
||||
Is able to pair successfully
|
||||
|
||||
Scenario: the client has a correct pairing code
|
||||
Given the user pairs with BitPay with a valid pairing code
|
||||
Then the user receives a require token from bitpay
|
||||
|
||||
Scenario: the client initiates pairing
|
||||
Given the user performs a client-side pairing
|
||||
Then the user receives an inactive token from bitpay
|
||||
|
||||
Scenario Outline: the client has a bad pairing code
|
||||
Given the user fails to pair with a semantically <valid> code <code>
|
||||
Then they will receive a <error> matching <message>
|
||||
Examples:
|
||||
| valid | code | error | message |
|
||||
| invalid | "a1b2c3d4" | BitPay::ArgumentError | "pairing code is not legal" |
|
||||
@ -1,23 +0,0 @@
|
||||
@refunds
|
||||
Feature: issuing a refund
|
||||
The merchant wants to issue a refund
|
||||
So that they can serve their customers
|
||||
|
||||
Background:
|
||||
Given the user is authenticated with BitPay
|
||||
|
||||
Scenario: creating a refund
|
||||
Given the user creates a refund
|
||||
Then they will receive a refund id
|
||||
|
||||
Scenario: retrieving a refund
|
||||
Given the user requests a specific refund
|
||||
Then they will receive the refund
|
||||
|
||||
Scenario: retrieving all refunds
|
||||
Given the user requests all refunds for an invoice
|
||||
Then they will receive an array of refunds
|
||||
|
||||
Scenario: canceling a refund
|
||||
Given a properly formatted cancellation request
|
||||
Then the refund will be cancelled
|
||||
@ -1,11 +0,0 @@
|
||||
Feature: retrieving an invoice
|
||||
The user may want to retrieve invoices
|
||||
So that they can view them
|
||||
|
||||
Scenario: Correct public request
|
||||
Given that a user knows an invoice id
|
||||
Then they can retrieve the public version of that invoice
|
||||
|
||||
Scenario: Correct merchant request
|
||||
Given that a user knows an invoice id
|
||||
Then they can retrieve the merchant-scoped version of that invoice
|
||||
@ -1,31 +0,0 @@
|
||||
When(/^the user (?:tries to |)creates? an invoice (?:for|without) "(.*?)" (?:or |and |)"(.*?)"$/) do |price, currency|
|
||||
begin
|
||||
@response = @client.create_invoice(price: price, currency: currency, facade: 'merchant')
|
||||
rescue => error
|
||||
@error = error
|
||||
end
|
||||
end
|
||||
|
||||
Then(/^they should recieve an invoice in response for "(.*?)" "(.*?)"$/) do |price, currency|
|
||||
raise "#{@response['price']} != #{price} or #{@response['currency']} != #{currency}" unless (price == @response['price'].to_s && currency == @response['currency'])
|
||||
end
|
||||
|
||||
Given(/^there is an invalid token$/) do
|
||||
pending # express the regexp above with the code you wish you had
|
||||
end
|
||||
|
||||
Given(/^that a user knows an invoice id$/) do
|
||||
@client = new_client_from_stored_values
|
||||
@id = (@client.create_invoice(price: 3, currency: "USD", facade: 'merchant' ))['id']
|
||||
end
|
||||
|
||||
Then(/^they can retrieve the public version of that invoice$/) do
|
||||
invoice = @client.get_public_invoice(id: @id)
|
||||
raise "That's the wrong invoice" unless invoice['id'] == @id
|
||||
end
|
||||
|
||||
Then(/^they can retrieve the merchant\-scoped version of that invoice$/) do
|
||||
invoice = @client.get_invoice(id: @id)
|
||||
raise "That's the wrong invoice" unless invoice['id'] == @id
|
||||
end
|
||||
|
||||
@ -1,56 +0,0 @@
|
||||
@token = nil
|
||||
@error = nil
|
||||
|
||||
When(/^the user pairs with BitPay(?: with a valid pairing code|)$/) do
|
||||
@client = new_client_from_stored_values
|
||||
claim_code = get_claim_code_from_server @client
|
||||
sleep 1 # rate limit compliance
|
||||
@token = @client.pair_pos_client(claim_code)
|
||||
end
|
||||
|
||||
Given(/^the user is authenticated with BitPay$/) do
|
||||
@client = new_client_from_stored_values
|
||||
raise "client not authenticated" unless client_has_tokens(@client)
|
||||
end
|
||||
|
||||
Given(/^the user is paired with BitPay$/) do
|
||||
raise "Client is not paired" unless @client.verify_tokens
|
||||
end
|
||||
|
||||
Then(/^the user receives an? ([A-z]+) token from bitpay$/) do |expected|
|
||||
actual = @token[0]["policies"][0]["method"]
|
||||
raise "Token not correct, #{actual} != #{expected}" unless actual == expected
|
||||
end
|
||||
|
||||
Given(/^the user has a bad pairing_code "(.*?)"$/) do |arg1|
|
||||
# This is a no-op, pairing codes are transient and never actually saved
|
||||
end
|
||||
|
||||
Then(/^the user fails to pair with a semantically (?:in|)valid code "(.*?)"$/) do |code|
|
||||
pem = BitPay::KeyUtils.generate_pem
|
||||
client = BitPay::SDK::Client.new(api_uri: APIURI, pem: pem, insecure: true)
|
||||
begin
|
||||
sleep 1 # rate limit compliance
|
||||
client.pair_pos_client(code)
|
||||
raise "pairing unexpectedly worked"
|
||||
rescue => error
|
||||
@error = error
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
Then(/^they will receive an? (.*?) matching "(.*?)"$/) do |error_class, error_message|
|
||||
raise "Error: #{@error.class}, message: #{@error.message}" unless Object.const_get(error_class) == @error.class && @error.message.include?(error_message)
|
||||
end
|
||||
|
||||
Given(/^the user performs a client\-side pairing$/) do
|
||||
sleep 1
|
||||
pem = BitPay::KeyUtils.generate_pem
|
||||
@client = BitPay::SDK::Client.new(api_uri: APIURI, pem: pem, insecure: true)
|
||||
@token = @client.pair_client({facade: 'merchant'})
|
||||
end
|
||||
|
||||
Then(/^the user has a merchant token$/) do
|
||||
tokens = {'merchant' => @token}
|
||||
raise "Merchant token not authorized" unless @client.verify_tokens(tokens: tokens)
|
||||
end
|
||||
@ -1,37 +0,0 @@
|
||||
Given(/^the user creates a refund$/) do
|
||||
sleep(1)
|
||||
@response = @client.refund_invoice(id: REFUND_TRANSACTION, params: {amount: 1, currency: 'USD', bitcoinAddress: REFUND_ADDRESS})
|
||||
end
|
||||
|
||||
Then(/^they will receive a refund id$/) do
|
||||
@refund_id = @response["id"]
|
||||
expect(@refund_id).not_to be_empty
|
||||
end
|
||||
|
||||
Given(/^the user requests a specific refund$/) do
|
||||
@response = @client.get_refund(invoice_id: REFUND_TRANSACTION, request_id: @refund_id)
|
||||
end
|
||||
|
||||
Then(/^they will receive the refund$/) do
|
||||
expect(@response.first["status"]).not_to be_empty
|
||||
end
|
||||
|
||||
Given(/^the user requests all refunds for an invoice$/) do
|
||||
client = new_client_from_stored_values
|
||||
@response = client.get_all_refunds_for_invoice(id: REFUND_TRANSACTION)
|
||||
end
|
||||
|
||||
Then(/^they will receive an array of refunds$/) do
|
||||
expect(@response).to be_instance_of Array
|
||||
end
|
||||
|
||||
Given(/^a properly formatted cancellation request$/) do
|
||||
sleep(1)
|
||||
client = new_client_from_stored_values
|
||||
@refund_id = client.get_all_refunds_for_invoice(id: REFUND_TRANSACTION).first["id"]
|
||||
@response = client.cancel_refund(invoice_id: REFUND_TRANSACTION, request_id: @refund_id)
|
||||
end
|
||||
|
||||
Then(/^the refund will be cancelled$/) do
|
||||
expect(@response).to eq("Success")
|
||||
end
|
||||
@ -1,31 +0,0 @@
|
||||
require 'pry'
|
||||
require 'fileutils'
|
||||
|
||||
require File.join File.dirname(__FILE__), '..', '..', 'lib', 'bitpay_sdk.rb'
|
||||
require_relative '../../config/constants.rb'
|
||||
|
||||
|
||||
module BitPay
|
||||
# Location for API Credentials
|
||||
BITPAY_CREDENTIALS_DIR = File.join(Dir.home, ".bitpay")
|
||||
PRIVATE_KEY_FILE = 'bitpay.pem'
|
||||
PRIVATE_KEY_PATH = File.join(BITPAY_CREDENTIALS_DIR, PRIVATE_KEY_FILE)
|
||||
TOKEN_FILE = 'tokens.json'
|
||||
TOKEN_FILE_PATH = File.join(BITPAY_CREDENTIALS_DIR, TOKEN_FILE)
|
||||
end
|
||||
|
||||
def new_client_from_stored_values
|
||||
pem = ENV['BITPAYPEM'].gsub("\\n", "\n")
|
||||
BitPay::SDK::Client.new(api_uri: APIURI, pem: pem, insecure: true)
|
||||
end
|
||||
|
||||
def get_claim_code_from_server client
|
||||
token = client.get(path: "tokens")["data"].select{|tuple| tuple["merchant"]}.first.values.first
|
||||
client.post(path: "tokens", token: token, params: {facade: "pos"})["data"][0]["pairingCode"]
|
||||
end
|
||||
|
||||
def client_has_tokens client
|
||||
data = client.get(path: "tokens")["data"]
|
||||
data.select{|tuple| tuple["pos"]}.any? && data.select{|tuple| tuple["merchant"]}.any?
|
||||
end
|
||||
|
||||
@ -18,11 +18,17 @@ module BitPay
|
||||
TEST_API_URI = 'https://test.bitpay.com'
|
||||
CLIENT_REGISTRATION_PATH = '/api-access-request'
|
||||
|
||||
# Location for API Credentials
|
||||
BITPAY_CREDENTIALS_DIR = File.join(Dir.home, ".bitpay")
|
||||
PRIVATE_KEY_FILE = 'bitpay.pem'
|
||||
PRIVATE_KEY_PATH = File.join(BITPAY_CREDENTIALS_DIR, PRIVATE_KEY_FILE)
|
||||
|
||||
# User agent reported to API
|
||||
USER_AGENT = 'ruby-bitpay-sdk '+VERSION
|
||||
USER_AGENT = 'ruby-bitpay-client '+VERSION
|
||||
|
||||
MISSING_KEY = 'No Private Key specified. Pass priv_key or set ENV variable PRIV_KEY'
|
||||
MISSING_PEM = 'No pem file specified. Pass pem or set ENV variable BITPAY_PEM'
|
||||
|
||||
class BitPayError < StandardError; end
|
||||
class ConnectionError < Errno::ECONNREFUSED; end
|
||||
|
||||
end
|
||||
63
lib/bitpay/cli.rb
Normal file
63
lib/bitpay/cli.rb
Normal file
@ -0,0 +1,63 @@
|
||||
# license Copyright 2011-2014 BitPay, Inc., MIT License
|
||||
# see http://opensource.org/licenses/MIT
|
||||
# or https://github.com/bitpay/php-bitpay-client/blob/master/LICENSE
|
||||
|
||||
require 'rubygems'
|
||||
require 'commander/import'
|
||||
|
||||
program :name, 'BitPay Ruby Library CLI'
|
||||
program :version, BitPay::VERSION
|
||||
program :description, 'Official BitPay Ruby API Client. Use to securely register your client with the BitPay API endpoint. '
|
||||
program :help_formatter, :compact
|
||||
|
||||
command :pair do |c|
|
||||
c.syntax = 'bitpay pair <code>'
|
||||
c.summary = "Pair the local keys to a bitpay account."
|
||||
c.option '--test', "Use the bitpay test server"
|
||||
c.option '--custom <custom>', "Use a custom bitpay URI"
|
||||
c.option '--insecure <insecure>', "Use an insecure custom bitpay URI"
|
||||
c.action do |args, options|
|
||||
raise ArgumentError, "Pairing failed, please call argument as 'bitpay pair <code> [options]'" unless args.first
|
||||
case
|
||||
when options.test
|
||||
client = BitPay::Client.new(api_uri: "https://test.bitpay.com")
|
||||
message = "Paired with test.bitpay.com"
|
||||
when options.custom
|
||||
client = BitPay::Client.new(api_uri: options.custom)
|
||||
message = "Paired with #{options.custom}"
|
||||
when options.insecure
|
||||
client = BitPay::Client.new(insecure: true, api_uri: options.insecure)
|
||||
message = "Paired with #{options.insecure}"
|
||||
else
|
||||
client = BitPay::Client.new
|
||||
message = "Paired with bitpay.com"
|
||||
end
|
||||
|
||||
begin
|
||||
client.pair_pos_client args.first
|
||||
puts message
|
||||
rescue Exception => e
|
||||
puts e.message
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
command :show_keys do |c|
|
||||
c.syntax = 'bitpay show_keys'
|
||||
c.summary = "Read current environment's key information to STDOUT"
|
||||
c.description = ''
|
||||
c.example 'description', 'command example'
|
||||
c.action do |args, options|
|
||||
|
||||
pem = BitPay::KeyUtils.get_local_pem_file
|
||||
private_key = BitPay::KeyUtils.get_private_key_from_pem pem
|
||||
public_key = BitPay::KeyUtils.get_public_key_from_pem pem
|
||||
client_id = BitPay::KeyUtils.generate_sin_from_pem pem
|
||||
|
||||
puts "Current BitPay Client Keys:\n"
|
||||
puts "Private Key: #{private_key}"
|
||||
puts "Public Key: #{public_key}"
|
||||
puts "Client ID: #{client_id}"
|
||||
|
||||
end
|
||||
end
|
||||
@ -1,4 +1,4 @@
|
||||
# license Copyright 2011-2015 BitPay, Inc., MIT License
|
||||
# license Copyright 2011-2014 BitPay, Inc., MIT License
|
||||
# see http://opensource.org/licenses/MIT
|
||||
# or https://github.com/bitpay/php-bitpay-client/blob/master/LICENSE
|
||||
|
||||
@ -6,175 +6,152 @@ require 'uri'
|
||||
require 'net/https'
|
||||
require 'json'
|
||||
|
||||
require 'bitpay_key_utils'
|
||||
require_relative 'rest_connector'
|
||||
require_relative 'key_utils'
|
||||
|
||||
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:
|
||||
# client = BitPay::SDK::Client.new
|
||||
def initialize(opts={})
|
||||
@pem = opts[:pem] || ENV['BITPAY_PEM'] || KeyUtils.generate_pem
|
||||
@key = KeyUtils.create_key @pem
|
||||
@priv_key = KeyUtils.get_private_key @key
|
||||
@pub_key = KeyUtils.get_public_key @key
|
||||
@client_id = KeyUtils.generate_sin_from_pem @pem
|
||||
@uri = URI.parse opts[:api_uri] || API_URI
|
||||
@user_agent = opts[:user_agent] || USER_AGENT
|
||||
@https = Net::HTTP.new @uri.host, @uri.port
|
||||
@https.use_ssl = true
|
||||
@https.open_timeout = 10
|
||||
@https.read_timeout = 10
|
||||
class Client
|
||||
|
||||
|
||||
@https.ca_file = CA_FILE
|
||||
@tokens = opts[:tokens] || {}
|
||||
# @return [Client]
|
||||
# @example
|
||||
# # Create a client with a pem file created by the bitpay client:
|
||||
# client = BitPay::Client.new
|
||||
def initialize(opts={})
|
||||
@pem = opts[:pem] || ENV['BITPAY_PEM'] || KeyUtils.retrieve_or_generate_pem
|
||||
@key = KeyUtils.create_key @pem
|
||||
@priv_key = KeyUtils.get_private_key @key
|
||||
@pub_key = KeyUtils.get_public_key @key
|
||||
@client_id = KeyUtils.generate_sin_from_pem @pem
|
||||
@uri = URI.parse opts[:api_uri] || API_URI
|
||||
@user_agent = opts[:user_agent] || USER_AGENT
|
||||
@https = Net::HTTP.new @uri.host, @uri.port
|
||||
@https.use_ssl = true
|
||||
@https.ca_file = CA_FILE
|
||||
|
||||
# Option to disable certificate validation in extraordinary circumstance. NOT recommended for production use
|
||||
@https.verify_mode = opts[:insecure] == true ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
|
||||
|
||||
# Option to enable http request debugging
|
||||
@https.set_debug_output($stdout) if opts[:debug] == true
|
||||
end
|
||||
|
||||
## Pair client with BitPay service
|
||||
# => Pass empty hash {} to retreive client-initiated pairing code
|
||||
# => Pass {pairingCode: 'WfD01d2'} to claim a server-initiated pairing code
|
||||
#
|
||||
def pair_client(params={})
|
||||
tokens = post(path: 'tokens', params: params)
|
||||
return tokens["data"]
|
||||
end
|
||||
|
||||
## Compatibility method for pos pairing
|
||||
#
|
||||
def pair_pos_client(claimCode)
|
||||
raise BitPay::ArgumentError, "pairing code is not legal" unless verify_claim_code(claimCode)
|
||||
pair_client({pairingCode: claimCode})
|
||||
end
|
||||
|
||||
## Create bitcoin invoice
|
||||
#
|
||||
# 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) ||
|
||||
currency == 'BTC' && /^[[:digit:]]+(\.[[:digit:]]{1,8})?$/.match(price)
|
||||
raise BitPay::ArgumentError, "Illegal Argument: Currency is invalid." unless /^[[:upper:]]{3}$/.match(currency)
|
||||
params.merge!({price: price, currency: currency})
|
||||
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:)
|
||||
token = get_token('merchant')
|
||||
invoice = get(path: "invoices/#{id}", token: token)
|
||||
invoice["data"]
|
||||
end
|
||||
|
||||
def get_invoices(params = {})
|
||||
token = get_token('merchant')
|
||||
invoice = get(path: "invoices", token: token, params: params)
|
||||
invoice["data"]
|
||||
end
|
||||
|
||||
## Gets the public version of the invoice
|
||||
def get_public_invoice(id:)
|
||||
invoice = get(path: "invoices/#{id}", public_request: true)
|
||||
invoice["data"]
|
||||
end
|
||||
# Option to disable certificate validation in extraordinary circumstance. NOT recommended for production use
|
||||
@https.verify_mode = opts[:insecure] == true ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
|
||||
|
||||
|
||||
## Refund paid BitPay invoice
|
||||
#
|
||||
# If invoice["data"]["flags"]["refundable"] == true the a refund address was
|
||||
# provided with the payment and the refund_address parameter is an optional override
|
||||
#
|
||||
# Amount and Currency are required fields for fully paid invoices but optional
|
||||
# for under or overpaid invoices which will otherwise be completely refunded
|
||||
#
|
||||
# Requires merchant facade token
|
||||
#
|
||||
# @example
|
||||
# client.refund_invoice(id: 'JB49z2MsDH7FunczeyDS8j', params: {amount: 10, currency: 'USD', bitcoinAddress: '1Jtcygf8W3cEmtGgepggtjCxtmFFjrZwRV'})
|
||||
#
|
||||
def refund_invoice(id:, params:{})
|
||||
invoice = get_invoice(id: id)
|
||||
refund = post(path: "invoices/#{id}/refunds", token: invoice["token"], params: params)
|
||||
refund["data"]
|
||||
end
|
||||
|
||||
## Get All Refunds for Invoice
|
||||
# Returns an array of all refund requests for a specific invoice,
|
||||
#
|
||||
# Requires merchant facade token
|
||||
#
|
||||
# @example:
|
||||
# client.get_all_refunds_for_invoice(id: 'JB49z2MsDH7FunczeyDS8j')
|
||||
#
|
||||
def get_all_refunds_for_invoice(id:)
|
||||
urlpath = "invoices/#{id}/refunds"
|
||||
invoice = get_invoice(id: id)
|
||||
refunds = get(path: urlpath, token: invoice["token"])
|
||||
refunds["data"]
|
||||
end
|
||||
# Option to enable http request debugging
|
||||
@https.set_debug_output($stdout) if opts[:debug] == true
|
||||
|
||||
## Get Refund
|
||||
# Requires merchant facade token
|
||||
#
|
||||
# @example:
|
||||
# client.get_refund(id: 'JB49z2MsDH7FunczeyDS8j', request_id: '4evCrXq4EDXk4oqDXdWQhX')
|
||||
#
|
||||
def get_refund(invoice_id:, request_id:)
|
||||
urlpath = "invoices/#{invoice_id}/refunds/#{request_id}"
|
||||
invoice = get_invoice(id: invoice_id)
|
||||
refund = get(path: urlpath, token: invoice["token"])
|
||||
refund["data"]
|
||||
end
|
||||
|
||||
## Cancel Refund
|
||||
# Requires merchant facade token
|
||||
#
|
||||
# @example:
|
||||
# client.cancel_refund(id: 'JB49z2MsDH7FunczeyDS8j', request_id: '4evCrXq4EDXk4oqDXdWQhX')
|
||||
#
|
||||
def cancel_refund(invoice_id:, request_id:)
|
||||
urlpath = "invoices/#{invoice_id}/refunds/#{request_id}"
|
||||
refund = get_refund(invoice_id: invoice_id, request_id: request_id)
|
||||
deletion = delete(path: urlpath, token: refund["token"])
|
||||
deletion["data"]
|
||||
end
|
||||
|
||||
## Checks that the passed tokens are valid by
|
||||
# comparing them to those that are authorized by the server
|
||||
#
|
||||
# Uses local @tokens variable if no tokens are passed
|
||||
# in order to validate the connector is properly paired
|
||||
#
|
||||
def verify_tokens(tokens: @tokens)
|
||||
server_tokens = refresh_tokens
|
||||
tokens.each{|key, value| return false if server_tokens[key] != value}
|
||||
return true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def verify_claim_code(claim_code)
|
||||
regex = /^[[:alnum:]]{7}$/
|
||||
matches = regex.match(claim_code)
|
||||
!(matches.nil?)
|
||||
end
|
||||
# Load all the available tokens into @tokens
|
||||
load_tokens
|
||||
end
|
||||
|
||||
def pair_pos_client(claimCode)
|
||||
response = set_pos_token(claimCode)
|
||||
case response.code
|
||||
when "200"
|
||||
get_token 'pos'
|
||||
when "500"
|
||||
raise BitPayError, JSON.parse(response.body)["error"]
|
||||
else
|
||||
raise BitPayError, "#{response.code}: #{JSON.parse(response.body)}"
|
||||
end
|
||||
response
|
||||
end
|
||||
|
||||
def create_invoice(id:, price:, currency:, facade: 'pos', params:{})
|
||||
params.merge!({price: price, currency: currency})
|
||||
response = send_request("POST", "invoices", facade: facade, params: params)
|
||||
response["data"]
|
||||
end
|
||||
|
||||
def get_public_invoice(id:)
|
||||
request = Net::HTTP::Get.new("/invoices/#{id}")
|
||||
response = @https.request request
|
||||
(JSON.parse response.body)["data"]
|
||||
end
|
||||
|
||||
## Generates REST request to api endpoint
|
||||
def send_request(verb, path, facade: 'merchant', params: {}, token: nil)
|
||||
token ||= @tokens[facade] || raise(BitPayError, "No token for specified facade: #{facade}")
|
||||
|
||||
# Verb-specific logic
|
||||
case verb.upcase
|
||||
when "GET"
|
||||
urlpath = '/' + path + '?nonce=' + KeyUtils.nonce + '&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[:nonce] = KeyUtils.nonce
|
||||
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['User-Agent'] = @user_agent
|
||||
request['Content-Type'] = 'application/json'
|
||||
request['X-BitPay-Plugin-Info'] = 'Rubylib' + VERSION
|
||||
request['X-Identity'] = @pub_key
|
||||
|
||||
response = @https.request request
|
||||
JSON.parse response.body
|
||||
end
|
||||
|
||||
##### PRIVATE METHODS #####
|
||||
private
|
||||
|
||||
## Requests token by appending nonce and signing URL
|
||||
# Returns a hash of available tokens
|
||||
#
|
||||
def load_tokens
|
||||
|
||||
urlpath = '/tokens?nonce=' + KeyUtils.nonce
|
||||
|
||||
request = Net::HTTP::Get.new(urlpath)
|
||||
request['content-type'] = "application/json"
|
||||
request['user-agent'] = @user_agent
|
||||
request['x-identity'] = @pub_key
|
||||
request['x-signature'] = KeyUtils.sign(@uri.to_s + urlpath, @priv_key)
|
||||
|
||||
response = @https.request request
|
||||
|
||||
# /tokens returns an array of hashes. Let's turn it into a more useful single hash
|
||||
token_array = JSON.parse(response.body)["data"] || {}
|
||||
|
||||
tokens = {}
|
||||
token_array.each do |t|
|
||||
tokens[t.keys.first] = t.values.first
|
||||
end
|
||||
|
||||
@tokens = tokens
|
||||
return tokens
|
||||
|
||||
end
|
||||
|
||||
## Retrieves specified token from hash, otherwise tries to refresh @tokens and retry
|
||||
def set_pos_token(claim_code)
|
||||
params = {pairingCode: claim_code}
|
||||
urlpath = '/tokens'
|
||||
request = Net::HTTP::Post.new urlpath
|
||||
params[:guid] = SecureRandom.uuid
|
||||
params[:id] = @client_id
|
||||
request.body = params.to_json
|
||||
request['User-Agent'] = @user_agent
|
||||
request['Content-Type'] = 'application/json'
|
||||
request['X-BitPay-Plugin-Info'] = 'Rubylib' + VERSION
|
||||
@https.request request
|
||||
end
|
||||
|
||||
def get_token(facade)
|
||||
token = @tokens[facade] || load_tokens[facade] || raise(BitPayError, "Not authorized for facade: #{facade}")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
143
lib/bitpay/key_utils.rb
Normal file
143
lib/bitpay/key_utils.rb
Normal file
@ -0,0 +1,143 @@
|
||||
# license Copyright 2011-2014 BitPay, Inc., MIT License
|
||||
# see http://opensource.org/licenses/MIT
|
||||
# or https://github.com/bitpay/php-bitpay-client/blob/master/LICENSE
|
||||
|
||||
require 'uri'
|
||||
require 'net/https'
|
||||
require 'json'
|
||||
require 'openssl'
|
||||
require 'ecdsa'
|
||||
require 'securerandom'
|
||||
require 'digest/sha2'
|
||||
require 'cgi'
|
||||
|
||||
module BitPay
|
||||
class KeyUtils
|
||||
class << self
|
||||
def nonce
|
||||
Time.now.utc.strftime('%Y%m%d%H%M%S%L')
|
||||
end
|
||||
|
||||
## Generates a new private key and writes to local FS
|
||||
#
|
||||
def retrieve_or_generate_pem
|
||||
begin
|
||||
pem = get_local_pem_file
|
||||
rescue
|
||||
pem = generate_pem
|
||||
end
|
||||
pem
|
||||
end
|
||||
|
||||
def generate_pem
|
||||
key = OpenSSL::PKey::EC.new("secp256k1")
|
||||
key.generate_key
|
||||
write_pem_file(key)
|
||||
key.to_pem
|
||||
end
|
||||
|
||||
def create_key pem
|
||||
OpenSSL::PKey::EC.new(pem)
|
||||
end
|
||||
|
||||
def create_new_key
|
||||
key = OpenSSL::PKey::EC.new("secp256k1")
|
||||
key.generate_key
|
||||
key
|
||||
end
|
||||
|
||||
def write_pem_file key
|
||||
FileUtils.mkdir_p(BITPAY_CREDENTIALS_DIR)
|
||||
File.open(PRIVATE_KEY_PATH, 'w') { |file| file.write(key.to_pem) }
|
||||
end
|
||||
## Gets private key from ENV variable or local FS
|
||||
#
|
||||
def get_local_pem_file
|
||||
ENV['BITPAY_PEM'] || File.read(PRIVATE_KEY_PATH) || (raise BitPayError, MISSING_KEY)
|
||||
end
|
||||
|
||||
def get_private_key key
|
||||
key.private_key.to_int.to_s(16)
|
||||
end
|
||||
|
||||
def get_public_key key
|
||||
key.public_key.group.point_conversion_form = :compressed
|
||||
key.public_key.to_bn.to_s(16).downcase
|
||||
end
|
||||
|
||||
def get_private_key_from_pem pem
|
||||
raise BitPayError, MISSING_KEY unless pem
|
||||
key = OpenSSL::PKey::EC.new(pem)
|
||||
get_private_key key
|
||||
end
|
||||
|
||||
def get_public_key_from_pem pem
|
||||
raise BitPayError, MISSING_KEY unless pem
|
||||
key = OpenSSL::PKey::EC.new(pem)
|
||||
get_public_key key
|
||||
end
|
||||
|
||||
def generate_sin_from_pem(pem = nil)
|
||||
#http://blog.bitpay.com/2014/07/01/bitauth-for-decentralized-authentication.html
|
||||
#https://en.bitcoin.it/wiki/Identity_protocol_v1
|
||||
|
||||
# NOTE: All Digests are calculated against the binary representation,
|
||||
# hence the requirement to use [].pack("H*") to convert to binary for each step
|
||||
|
||||
#Generate Private Key
|
||||
key = pem.nil? ? get_local_pem_file : OpenSSL::PKey::EC.new(pem)
|
||||
key.public_key.group.point_conversion_form = :compressed
|
||||
public_key = key.public_key.to_bn.to_s(2)
|
||||
step_one = Digest::SHA256.hexdigest(public_key)
|
||||
step_two = Digest::RMD160.hexdigest([step_one].pack("H*"))
|
||||
step_three = "0F02" + step_two
|
||||
step_four_a = Digest::SHA256.hexdigest([step_three].pack("H*"))
|
||||
step_four = Digest::SHA256.hexdigest([step_four_a].pack("H*"))
|
||||
step_five = step_four[0..7]
|
||||
step_six = step_three + step_five
|
||||
encode_base58(step_six)
|
||||
end
|
||||
|
||||
|
||||
## Generate ECDSA signature
|
||||
# This is the last method that requires the ecdsa gem, which we would like to replace
|
||||
|
||||
def sign(message, privkey)
|
||||
group = ECDSA::Group::Secp256k1
|
||||
digest = Digest::SHA256.digest(message)
|
||||
signature = nil
|
||||
while signature.nil?
|
||||
temp_key = 1 + SecureRandom.random_number(group.order - 1)
|
||||
signature = ECDSA.sign(group, privkey.to_i(16), digest, temp_key)
|
||||
return ECDSA::Format::SignatureDerString.encode(signature).unpack("H*").first
|
||||
end
|
||||
end
|
||||
|
||||
########## Private Class Methods ################
|
||||
|
||||
## Base58 Encoding Method
|
||||
#
|
||||
private
|
||||
def encode_base58 (data)
|
||||
code_string = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
base = 58
|
||||
x = data.hex
|
||||
output_string = ""
|
||||
|
||||
while x > 0 do
|
||||
remainder = x % base
|
||||
x = x / base
|
||||
output_string << code_string[remainder]
|
||||
end
|
||||
|
||||
pos = 0
|
||||
while data[pos,2] == "00" do
|
||||
output_string << code_string[0]
|
||||
pos += 2
|
||||
end
|
||||
|
||||
output_string.reverse()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,106 +0,0 @@
|
||||
# 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, params: params)
|
||||
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_request: false, params: {})
|
||||
urlpath = '/' + path + '?'
|
||||
urlpath = urlpath + 'token=' + token if token
|
||||
urlpath = urlpath + '&' + params.to_param if params.present?
|
||||
request = Net::HTTP::Get.new urlpath
|
||||
unless public_request
|
||||
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
|
||||
|
||||
def delete(path:, token: nil)
|
||||
urlpath = '/' + path
|
||||
urlpath = urlpath + '?token=' + token if token
|
||||
request = Net::HTTP::Delete.new urlpath
|
||||
request['X-Signature'] = KeyUtils.sign(@uri.to_s + urlpath, @priv_key)
|
||||
request['X-Identity'] = @pub_key
|
||||
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
|
||||
@ -3,5 +3,5 @@
|
||||
# or https://github.com/bitpay/php-bitpay-client/blob/master/LICENSE
|
||||
|
||||
module BitPay
|
||||
VERSION = '2.4.6'
|
||||
VERSION = '2.0.0'
|
||||
end
|
||||
|
||||
40
lib/harness.rb
Normal file
40
lib/harness.rb
Normal file
@ -0,0 +1,40 @@
|
||||
# license Copyright 2011-2014 BitPay, Inc., MIT License
|
||||
# see http://opensource.org/licenses/MIT
|
||||
# or https://github.com/bitpay/php-bitpay-client/blob/master/LICENSE
|
||||
|
||||
require_relative 'bitpay.rb'
|
||||
require_relative 'bitpay/key_utils.rb'
|
||||
|
||||
# Test SIN Generation class methods
|
||||
|
||||
# Generate SIN
|
||||
ENV["PRIV_KEY"] = "16d7c3508ec59773e71ae728d29f41fcf5d1f380c379b99d68fa9f552ce3ebc3"
|
||||
puts "privkey: #{ENV['PRIV_KEY']}"
|
||||
puts "target SIN: TfFVQhy2hQvchv4VVG4c7j4XPa2viJ9HrR8"
|
||||
puts "Derived SIN: #{BitPay::KeyUtils.get_client_id}"
|
||||
|
||||
puts "\n\n------------------\n\n"
|
||||
|
||||
uri = "https://localhost:8088"
|
||||
#name = "Ridonculous.label That shouldn't work really"
|
||||
name = "somethinginnocuous"
|
||||
facade = "pos"
|
||||
client_id = BitPay::KeyUtils.get_client_id
|
||||
|
||||
BitPay::KeyUtils.generate_registration_url(uri,name,facade,client_id)
|
||||
|
||||
puts "\n\n------------------\n\n"
|
||||
|
||||
#### Test Invoice Creation using directly assigned keys
|
||||
## (Ultimately pubkey and SIN should be derived)
|
||||
|
||||
ENV["PRIV_KEY"] = "16d7c3508ec59773e71ae728d29f41fcf5d1f380c379b99d68fa9f552ce3ebc3"
|
||||
#ENV["pub_key"] = "0353a036fb495c5846f26a3727a28198da8336ae4f5aaa09e24c14a4126b5d969d"
|
||||
#ENV['SIN'] = "TfFVQhy2hQvchv4VVG4c7j4XPa2viJ9HrR8"
|
||||
|
||||
client = BitPay::Client.new({insecure: true, debug: false})
|
||||
|
||||
invoice = client.post 'invoices', {:price => 10.00, :currency => 'USD'}
|
||||
|
||||
puts "Here's the invoice: \n" + JSON.pretty_generate(invoice)
|
||||
|
||||
@ -2,35 +2,19 @@ require 'spec_helper'
|
||||
|
||||
def tokens
|
||||
{"data" =>
|
||||
[{"merchant" => "MERCHANT_TOKEN"},
|
||||
{"pos" =>"POS_TOKEN"},
|
||||
[{"merchant" => "MERCHANTTOKEN"},
|
||||
{"pos" =>"POSTOKEN"},
|
||||
{"merchant/invoice" => "9kv7gGqZLoQ2fxbKEgfgndLoxwjp5na6VtGSH3sN7buX"}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
describe BitPay::SDK::Client do
|
||||
let(:bitpay_client) { BitPay::SDK::Client.new({api_uri: BitPay::TEST_API_URI}) }
|
||||
let(:claim_code) { "a12bc3d" }
|
||||
describe BitPay::Client do
|
||||
let(:bitpay_client) { BitPay::Client.new({api_uri: BitPay::TEST_API_URI}) }
|
||||
|
||||
before do
|
||||
# Stub JSON responses from fixtures
|
||||
stub_request(:get, /#{BitPay::TEST_API_URI}\/tokens.*/)
|
||||
.to_return(:status => 200, :body => tokens.to_json, :headers => {})
|
||||
stub_request(:get, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID?token=MERCHANT_TOKEN").
|
||||
to_return(:body => get_fixture('invoices_{id}-GET.json'))
|
||||
stub_request(:get, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID/refunds?token=MERCHANT_INVOICE_TOKEN").
|
||||
to_return(:body => get_fixture('invoices_{id}_refunds-GET.json'))
|
||||
stub_request(:get, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID/refunds/TEST_REQUEST_ID?token=MERCHANT_INVOICE_TOKEN").
|
||||
to_return(:body => get_fixture('invoices_{id}_refunds-GET.json'))
|
||||
stub_request(:post, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID/refunds").
|
||||
to_return(:body => get_fixture('invoices_{id}_refunds-POST.json'))
|
||||
stub_request(:post, "#{BitPay::TEST_API_URI}/nuttin").
|
||||
to_return(:body => get_fixture('response-nodata.json'))
|
||||
stub_request(:get, "#{BitPay::TEST_API_URI}/nuttin").
|
||||
to_return(:body => get_fixture('response-nodata.json'))
|
||||
stub_request(:delete, "#{BitPay::TEST_API_URI}/nuttin").
|
||||
to_return(:body => get_fixture('response-nodata.json'))
|
||||
allow(BitPay::KeyUtils).to receive(:nonce).and_return('1')
|
||||
stub_request(:get, /#{BitPay::TEST_API_URI}\/tokens.*/).to_return(:status => 200, :body => tokens.to_json, :headers => {})
|
||||
end
|
||||
|
||||
describe "#initialize" do
|
||||
@ -42,14 +26,6 @@ describe BitPay::SDK::Client do
|
||||
|
||||
end
|
||||
|
||||
describe "requests to endpoint without data field" do
|
||||
it "should return the json body" do
|
||||
expect(bitpay_client.post(path: "nuttin", params: {})["facile"]).to eq("is easy")
|
||||
expect(bitpay_client.get(path: "nuttin")["facile"]).to eq("is easy")
|
||||
expect(bitpay_client.delete(path: "nuttin")["facile"]).to eq( "is easy")
|
||||
end
|
||||
end
|
||||
|
||||
describe "#send_request" do
|
||||
before do
|
||||
stub_const('ENV', {'BITPAY_PEM' => PEM})
|
||||
@ -59,15 +35,8 @@ describe BitPay::SDK::Client do
|
||||
it 'should generate a get request' do
|
||||
stub_request(:get, /#{BitPay::TEST_API_URI}\/whatever.*/).to_return(:body => '{"awesome": "json"}')
|
||||
bitpay_client.send_request("GET", "whatever", facade: "merchant")
|
||||
expect(WebMock).to have_requested(:get, "#{BitPay::TEST_API_URI}/whatever?token=MERCHANT_TOKEN")
|
||||
expect(WebMock).to have_requested(:get, "#{BitPay::TEST_API_URI}/whatever?nonce=1&token=MERCHANTTOKEN")
|
||||
end
|
||||
|
||||
it 'should handle query parameters gracefully' do
|
||||
stub_request(:get, /#{BitPay::TEST_API_URI}\/ledgers.*/).to_return(:body => '{"awesome": "json"}')
|
||||
bitpay_client.send_request("GET", "ledgers/BTC?startDate=2015-01-01&endDate=2015-02-01", facade: "merchant")
|
||||
expect(WebMock).to have_requested(:get, "#{BitPay::TEST_API_URI}/ledgers/BTC?startDate=2015-01-01&endDate=2015-02-01&token=MERCHANT_TOKEN")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "POST" do
|
||||
@ -81,25 +50,16 @@ describe BitPay::SDK::Client do
|
||||
end
|
||||
|
||||
describe "#pair_pos_client" do
|
||||
before do
|
||||
stub_const('ENV', {'BITPAY_PEM' => PEM})
|
||||
end
|
||||
|
||||
it 'throws a BitPayError with the error message if the token setting fails' do
|
||||
stub_const('ENV', {'BITPAY_PEM' => PEM})
|
||||
stub_request(:any, /#{BitPay::TEST_API_URI}.*/).to_return(status: 500, body: "{\n \"error\": \"Unable to create token\"\n}")
|
||||
expect { bitpay_client.pair_pos_client(claim_code) }.to raise_error(BitPay::BitPayError, '500: Unable to create token')
|
||||
expect { bitpay_client.pair_pos_client(:claim_code) }.to raise_error(BitPay::BitPayError, 'Unable to create token')
|
||||
end
|
||||
|
||||
it 'gracefully handles 4xx errors' do
|
||||
stub_const('ENV', {'BITPAY_PEM' => PEM})
|
||||
stub_request(:any, /#{BitPay::TEST_API_URI}.*/).to_return(status: 403, body: "{\n \"error\": \"this is a 403 error\"\n}")
|
||||
expect { bitpay_client.pair_pos_client(claim_code) }.to raise_error(BitPay::BitPayError, '403: this is a 403 error')
|
||||
end
|
||||
|
||||
it 'short circuits on invalid pairing codes' do
|
||||
100.times do
|
||||
claim_code = an_illegal_claim_code
|
||||
expect { bitpay_client.pair_pos_client(claim_code) }.to raise_error BitPay::ArgumentError, "pairing code is not legal"
|
||||
end
|
||||
expect { bitpay_client.pair_pos_client(:claim_code) }.to raise_error(BitPay::BitPayError, '403: {"error"=>"this is a 403 error"}')
|
||||
end
|
||||
end
|
||||
|
||||
@ -108,83 +68,11 @@ describe BitPay::SDK::Client do
|
||||
before {stub_const('ENV', {'BITPAY_PEM' => PEM})}
|
||||
it { is_expected.to respond_to(:create_invoice) }
|
||||
|
||||
describe "should make the call to the server to create an invoice" do
|
||||
it 'allows numeric input for the price' do
|
||||
stub_request(:post, /#{BitPay::TEST_API_URI}\/invoices.*/).to_return(:body => '{"data": "awesome"}')
|
||||
bitpay_client.create_invoice(price: 20.00, currency: "USD")
|
||||
assert_requested :post, "#{BitPay::TEST_API_URI}/invoices"
|
||||
end
|
||||
|
||||
it 'allows string input for the price' do
|
||||
stub_request(:post, /#{BitPay::TEST_API_URI}\/invoices.*/).to_return(:body => '{"data": "awesome"}')
|
||||
bitpay_client.create_invoice(price: "20.00", currency: "USD")
|
||||
assert_requested :post, "#{BitPay::TEST_API_URI}/invoices"
|
||||
end
|
||||
it 'should make call to the server to create an invoice' do
|
||||
stub_request(:post, /#{BitPay::TEST_API_URI}\/invoices.*/).to_return(:body => '{"data": "awesome"}')
|
||||
bitpay_client.create_invoice(id: "addd", price: 20, currency: "USD")
|
||||
assert_requested :post, "#{BitPay::TEST_API_URI}/invoices"
|
||||
end
|
||||
|
||||
it 'should pass through the API error message from load_tokens' do
|
||||
stub_request(:get, /#{BitPay::TEST_API_URI}\/tokens.*/).to_return(status: 500, body: '{"error": "load_tokens_error"}')
|
||||
expect { bitpay_client.create_invoice(price: 20, currency: "USD") }.to raise_error(BitPay::BitPayError, '500: load_tokens_error')
|
||||
end
|
||||
|
||||
it 'verifies the validity of the price argument' do
|
||||
expect { bitpay_client.create_invoice(price: "3,999", currency: "USD") }.to raise_error(BitPay::ArgumentError, 'Illegal Argument: Price must be formatted as a float')
|
||||
end
|
||||
|
||||
it 'verifies the validity of the currency argument' do
|
||||
expect { bitpay_client.create_invoice(price: "3999", currency: "UASD") }.to raise_error(BitPay::ArgumentError, 'Illegal Argument: Currency is invalid.')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#refund_invoice' do
|
||||
subject { bitpay_client }
|
||||
before { stub_const('ENV', {'BITPAY_PEM' => PEM}) }
|
||||
it { is_expected.to respond_to(:refund_invoice) }
|
||||
|
||||
it 'should get the token for the invoice' do
|
||||
bitpay_client.refund_invoice(id: 'TEST_INVOICE_ID')
|
||||
expect(WebMock).to have_requested :get, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID?token=MERCHANT_TOKEN"
|
||||
end
|
||||
|
||||
it 'should generate a POST to the invoices/refund endpoint' do
|
||||
bitpay_client.refund_invoice(id: 'TEST_INVOICE_ID')
|
||||
expect(WebMock).to have_requested :post, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID/refunds"
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_all_refunds_for_invoice' do
|
||||
subject { bitpay_client }
|
||||
before {stub_const('ENV', {'BITPAY_PEM' => PEM})}
|
||||
it { is_expected.to respond_to(:get_all_refunds_for_invoice) }
|
||||
|
||||
it 'should get the token for the invoice' do
|
||||
bitpay_client.get_all_refunds_for_invoice(id: 'TEST_INVOICE_ID')
|
||||
expect(WebMock).to have_requested :get, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID?token=MERCHANT_TOKEN"
|
||||
end
|
||||
it 'should GET all refunds' do
|
||||
bitpay_client.get_all_refunds_for_invoice(id: 'TEST_INVOICE_ID')
|
||||
expect(WebMock).to have_requested :get, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID/refunds?token=MERCHANT_INVOICE_TOKEN"
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_refund' do
|
||||
subject { bitpay_client }
|
||||
before {stub_const('ENV', {'BITPAY_PEM' => PEM})}
|
||||
it { is_expected.to respond_to(:get_refund) }
|
||||
it 'should get the token for the invoice' do
|
||||
bitpay_client.get_refund(invoice_id: 'TEST_INVOICE_ID', request_id: 'TEST_REQUEST_ID')
|
||||
expect(WebMock).to have_requested :get, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID?token=MERCHANT_TOKEN"
|
||||
end
|
||||
it 'should GET a single refund' do
|
||||
bitpay_client.get_refund(invoice_id: 'TEST_INVOICE_ID', request_id: 'TEST_REQUEST_ID')
|
||||
expect(WebMock).to have_requested :get, "#{BitPay::TEST_API_URI}/invoices/TEST_INVOICE_ID/refunds/TEST_REQUEST_ID?token=MERCHANT_INVOICE_TOKEN"
|
||||
end
|
||||
end
|
||||
|
||||
describe "#verify_tokens" do
|
||||
subject { bitpay_client }
|
||||
before {stub_const('ENV', {'BITPAY_PEM' => PEM})}
|
||||
it { is_expected.to respond_to(:verify_tokens) }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
28
spec/features/pair_spec.rb
Normal file
28
spec/features/pair_spec.rb
Normal file
@ -0,0 +1,28 @@
|
||||
require_relative '../spec_helper.rb'
|
||||
|
||||
describe "pairing a token", javascript: true, type: :feature do
|
||||
let(:claimCode) do
|
||||
visit ROOT_ADDRESS
|
||||
click_link('Login')
|
||||
fill_in 'email', :with => TEST_USER
|
||||
fill_in 'password', :with => TEST_PASS
|
||||
click_button('loginButton')
|
||||
click_link "My Account"
|
||||
click_link "API Tokens", match: :first
|
||||
find(".token-access-new-button").find(".btn").click
|
||||
find(".token-claimcode", match: :first).text
|
||||
end
|
||||
let(:pem) { BitPay::KeyUtils.generate_pem }
|
||||
let(:client) { BitPay::Client.new(api_uri: ROOT_ADDRESS, pem: pem, insecure: true) }
|
||||
|
||||
context "pairing an unpaired client" do
|
||||
it "should have no tokens before pairing" do
|
||||
expect(client.instance_variable_get(:@tokens)).to be_empty
|
||||
end
|
||||
it "should have a pos token after pairing" do
|
||||
client.pair_pos_client(claimCode)
|
||||
expect(client.instance_variable_get(:@tokens)['pos']).not_to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
37
spec/features/pos_spec.rb
Normal file
37
spec/features/pos_spec.rb
Normal file
@ -0,0 +1,37 @@
|
||||
require_relative '../spec_helper.rb'
|
||||
|
||||
describe "create an invoice", javascript: true, type: :feature do
|
||||
before :all do
|
||||
WebMock.allow_net_connect!
|
||||
get_claim_code = -> {
|
||||
visit ROOT_ADDRESS
|
||||
click_link('Login')
|
||||
fill_in 'email', :with => TEST_USER
|
||||
fill_in 'password', :with => TEST_PASS
|
||||
click_button('loginButton')
|
||||
click_link "My Account"
|
||||
click_link "API Tokens", match: :first
|
||||
find(".token-access-new-button").find(".btn").click
|
||||
find(".token-claimcode", match: :first).text
|
||||
}
|
||||
set_client = -> {
|
||||
private_key = BitPay::KeyUtils.get_private_key_from_pem PEM
|
||||
client = BitPay::Client.new(api_uri: ROOT_ADDRESS, pem: PEM, insecure: true)
|
||||
client.pair_pos_client(get_claim_code.call)
|
||||
client
|
||||
}
|
||||
@client ||= set_client.call
|
||||
@invoice_id ||= SecureRandom.uuid
|
||||
@price ||= (100..150).to_a.sample
|
||||
@invoice = @client.create_invoice(id: @invoice_id, currency: "USD", price: @price)
|
||||
end
|
||||
|
||||
it "should create an invoice" do
|
||||
expect(@invoice["status"]).to eq "new"
|
||||
end
|
||||
|
||||
it "should be able to retrieve an invoice" do
|
||||
expect(@client.get_public_invoice(id: @invoice['id'])["price"]).to eq @price
|
||||
end
|
||||
end
|
||||
|
||||
13
spec/features/setup_spec.rb
Normal file
13
spec/features/setup_spec.rb
Normal file
@ -0,0 +1,13 @@
|
||||
require 'spec_helper'
|
||||
|
||||
context 'local variables' do
|
||||
it "should find the root address" do
|
||||
expect(ROOT_ADDRESS).not_to be_nil
|
||||
end
|
||||
it "should find the user" do
|
||||
expect(TEST_USER).not_to be_nil
|
||||
end
|
||||
it "should find the user" do
|
||||
expect(TEST_PASS).not_to be_nil
|
||||
end
|
||||
end
|
||||
29
spec/fixtures/invoices-POST.json
vendored
29
spec/fixtures/invoices-POST.json
vendored
@ -1,29 +0,0 @@
|
||||
{
|
||||
"facade": "pos/invoice",
|
||||
"data": {
|
||||
"url": "https://test.bitpay.com/invoice?id=2RSyNDvsiTrA31rPwnnEcd",
|
||||
"status": "new",
|
||||
"btcPrice": "0.037523",
|
||||
"btcDue": "0.037523",
|
||||
"price": 10,
|
||||
"currency": "USD",
|
||||
"exRates": {
|
||||
"USD": 266.5
|
||||
},
|
||||
"invoiceTime": 1422319964413,
|
||||
"expirationTime": 1422320864413,
|
||||
"currentTime": 1422319964431,
|
||||
"guid": "34d7be05-eb65-4f72-a2ce-79bf23e93f17",
|
||||
"id": "2RSyNDvsiTrA31rPwnnEcd",
|
||||
"btcPaid": "0.000000",
|
||||
"rate": 266.5,
|
||||
"exceptionStatus": false,
|
||||
"paymentUrls": {
|
||||
"BIP21": "bitcoin:mhPM48eieakd6AgCuHMwAtpFXE5yQ3N7om?amount=0.037523",
|
||||
"BIP72": "bitcoin:mhPM48eieakd6AgCuHMwAtpFXE5yQ3N7om?amount=0.037523&r=https://test.bitpay.com/i/2RSyNDvsiTrA31rPwnnEcd",
|
||||
"BIP72b": "bitcoin:?r=https://test.bitpay.com/i/2RSyNDvsiTrA31rPwnnEcd",
|
||||
"BIP73": "https://test.bitpay.com/i/2RSyNDvsiTrA31rPwnnEcd"
|
||||
},
|
||||
"token": "2RPipMRUXAvt5wAfthCzF7Tj4SppBWPHGQ7hCeWYeWDm7RtwUtDds1XUNt11VTf5C6UfCAACBhsKwjW6SAocLsd7"
|
||||
}
|
||||
}
|
||||
35
spec/fixtures/invoices_{id}-GET.json
vendored
35
spec/fixtures/invoices_{id}-GET.json
vendored
@ -1,35 +0,0 @@
|
||||
{
|
||||
"facade": "merchant/invoice",
|
||||
"data": {
|
||||
"url": "https://test.bitpay.com/invoice?id=CcRgegwTMs866Sr7vdLnru",
|
||||
"status": "complete",
|
||||
"btcPrice": "0.074661",
|
||||
"btcDue": "0.000000",
|
||||
"price": 15.79,
|
||||
"currency": "USD",
|
||||
"exRates": {
|
||||
"USD": 211.49
|
||||
},
|
||||
"invoiceTime": 1421719631301,
|
||||
"expirationTime": 1421720531301,
|
||||
"currentTime": 1422316288768,
|
||||
"id": "TEST_INVOICE_ID",
|
||||
"btcPaid": "0.074661",
|
||||
"rate": 211.49,
|
||||
"exceptionStatus": false,
|
||||
"transactions": [
|
||||
{
|
||||
"amount": 7466100,
|
||||
"confirmations": 6,
|
||||
"time": "2015-01-20T02:07:46.000Z",
|
||||
"receivedTime": "2015-01-20T02:07:45.881Z"
|
||||
}
|
||||
],
|
||||
"flags": {
|
||||
"refundable": true
|
||||
},
|
||||
"token": "MERCHANT_INVOICE_TOKEN",
|
||||
"buyer": {
|
||||
}
|
||||
}
|
||||
}
|
||||
17
spec/fixtures/invoices_{id}_refunds-GET.json
vendored
17
spec/fixtures/invoices_{id}_refunds-GET.json
vendored
@ -1,17 +0,0 @@
|
||||
{
|
||||
"facade": "merchant/supportRequest",
|
||||
"data": [
|
||||
{
|
||||
"id": "TEST_REQUEST_ID",
|
||||
"requestDate": "2015-01-27T00:36:12.360Z",
|
||||
"status": "pending",
|
||||
"token": "REFUND_REQUEST_TOKEN"
|
||||
},
|
||||
{
|
||||
"id": "ANOTHER_ID",
|
||||
"requestDate": "2015-01-27T00:36:12.360Z",
|
||||
"status": "OTHER_STATUS",
|
||||
"token": "ANOTHER_REFUND_REQUEST_TOKEN"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
{
|
||||
"facade": "merchant/supportRequest",
|
||||
"data": {
|
||||
"id": "Q6CuxYF83MfV1XgUBQBdbA",
|
||||
"requestDate": "2015-01-27T00:36:12.360Z",
|
||||
"status": "pending",
|
||||
"token": "REFUND_REQUEST_TOKEN"
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
{
|
||||
"facade": "merchant/supportRequest",
|
||||
"data": [
|
||||
{
|
||||
"id": "TEST_REQUEST_ID",
|
||||
"requestDate": "2015-01-27T00:36:12.360Z",
|
||||
"status": "pending",
|
||||
"token": "REFUND_REQUEST_TOKEN"
|
||||
}
|
||||
]
|
||||
}
|
||||
10
spec/fixtures/response-nodata.json
vendored
10
spec/fixtures/response-nodata.json
vendored
@ -1,10 +0,0 @@
|
||||
{
|
||||
"facile": "is easy",
|
||||
"diti": [
|
||||
{
|
||||
"requestDate": "2015-01-27T00:36:12.360Z",
|
||||
"status": "pending"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
81
spec/key_utils_spec.rb
Normal file
81
spec/key_utils_spec.rb
Normal file
@ -0,0 +1,81 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe BitPay::KeyUtils do
|
||||
let(:key_utils) {BitPay::KeyUtils}
|
||||
|
||||
|
||||
describe '.get_local_private_key' do
|
||||
it "should get the key from the ENV['PRIV_KEY'] variable" do
|
||||
stub_const('ENV', {'BITPAY_PEM' => PEM})
|
||||
expect(key_utils.get_local_pem_file).to eq(PEM)
|
||||
end
|
||||
|
||||
it 'should get the key from ~/.bitpay/bitpay.pem if env variable is not set' do
|
||||
allow(File).to receive(:read).with(BitPay::PRIVATE_KEY_PATH) {PEM}
|
||||
expect(key_utils.get_local_pem_file).to eq(PEM)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '.generate_pem' do
|
||||
it 'should write a new key to ~/.bitpay/bitpay.pem' do
|
||||
file = class_double("File").as_stubbed_const
|
||||
double = double("Object").as_null_object
|
||||
allow(file).to receive(:path).with(BitPay::BITPAY_CREDENTIALS_DIR).and_return(double)
|
||||
expect(file).to receive(:open).with(BitPay::PRIVATE_KEY_PATH, 'w')
|
||||
key_utils.generate_pem
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '.retrieve_or_generate_pem' do
|
||||
it 'should write a new key to ~/.bitpay/bitpay.pem if there is no existing file' do
|
||||
file = class_double("File").as_stubbed_const
|
||||
double = double("Object").as_null_object
|
||||
allow(file).to receive(:read).with(BitPay::PRIVATE_KEY_PATH).and_throw(StandardError)
|
||||
allow(file).to receive(:path).with(BitPay::BITPAY_CREDENTIALS_DIR).and_return(double)
|
||||
expect(file).to receive(:open).with(BitPay::PRIVATE_KEY_PATH, 'w')
|
||||
key_utils.retrieve_or_generate_pem
|
||||
end
|
||||
|
||||
it 'should retrieve the pem if there is an existing file' do
|
||||
file = class_double("File").as_stubbed_const
|
||||
double = double("Object").as_null_object
|
||||
allow(file).to receive(:path).with(BitPay::BITPAY_CREDENTIALS_DIR).and_return(double)
|
||||
expect(file).to receive(:open).with(BitPay::PRIVATE_KEY_PATH, 'w')
|
||||
key_utils.generate_pem
|
||||
end
|
||||
end
|
||||
|
||||
describe '.get_public_key_from_pem' do
|
||||
it 'should generate the right public key' do
|
||||
expect(key_utils.get_public_key_from_pem(PEM)).to eq(PUB_KEY)
|
||||
end
|
||||
|
||||
it 'should get pem from the env if none is passed' do
|
||||
expect(key_utils.get_public_key_from_pem(PEM)).to eq(PUB_KEY)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '.generate_sin_from_pem' do
|
||||
let(:pem){PEM}
|
||||
let(:sin){"TeyN4LPrXiG5t2yuSamKqP3ynVk3F52iHrX"}
|
||||
|
||||
it 'will return the right sin for the right pem' do
|
||||
expect(key_utils.generate_sin_from_pem(pem)).to eq sin
|
||||
end
|
||||
end
|
||||
|
||||
context "errors when priv_key is not provided" do
|
||||
before :each do
|
||||
allow(File).to receive(:read).with(BitPay::PRIVATE_KEY_PATH) {nil}
|
||||
end
|
||||
|
||||
it 'will not retrieve public key' do
|
||||
expect{key_utils.get_public_key_from_pem(nil)}.to raise_error(BitPay::BitPayError)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
10
spec/set_constants.sh
Executable file
10
spec/set_constants.sh
Executable file
@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
export RCROOTADDRESS=$1
|
||||
echo $RCROOTADDRESS
|
||||
export RCTESTUSER=$2
|
||||
echo $RCTESTUSER
|
||||
export RCTESTPASSWORD=$3
|
||||
echo $RCTESTPASSWORD
|
||||
export PRIV_KEY=$4
|
||||
echo $PRIV_KEY
|
||||
@ -1,10 +1,13 @@
|
||||
require 'webmock/rspec'
|
||||
require 'pry'
|
||||
require 'coveralls'
|
||||
Coveralls.wear!
|
||||
require 'capybara/rspec'
|
||||
require 'capybara/poltergeist'
|
||||
|
||||
require File.join File.dirname(__FILE__), '..', 'lib', 'bitpay_sdk.rb'
|
||||
require File.join File.dirname(__FILE__), '..', 'lib', 'bitpay', 'client.rb'
|
||||
require File.join File.dirname(__FILE__), '..', 'lib', 'bitpay', 'key_utils.rb'
|
||||
require File.join File.dirname(__FILE__), '..', 'lib', 'bitpay.rb'
|
||||
require_relative '../config/constants.rb'
|
||||
require_relative '../config/capybara.rb'
|
||||
|
||||
#
|
||||
## Test Variables
|
||||
@ -12,23 +15,11 @@ require_relative '../config/constants.rb'
|
||||
PEM = "-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEICg7E4NN53YkaWuAwpoqjfAofjzKI7Jq1f532dX+0O6QoAcGBSuBBAAK\noUQDQgAEjZcNa6Kdz6GQwXcUD9iJ+t1tJZCx7hpqBuJV2/IrQBfue8jh8H7Q/4vX\nfAArmNMaGotTpjdnymWlMfszzXJhlw==\n-----END EC PRIVATE KEY-----\n"
|
||||
|
||||
PUB_KEY = '038d970d6ba29dcfa190c177140fd889fadd6d2590b1ee1a6a06e255dbf22b4017'
|
||||
CLIENT_ID = "TeyN4LPrXiG5t2yuSamKqP3ynVk3F52iHrX"
|
||||
CLIENT_ID = "TfFVQhy2hQvchv4VVG4c7j4XPa2viJ9HrR8"
|
||||
|
||||
def generate_code(number)
|
||||
legal_map = [*'A'..'Z'] + [*'a'..'z'] + [*0..9]
|
||||
Array.new(number) { legal_map.sample }.join
|
||||
end
|
||||
|
||||
|
||||
def an_illegal_claim_code
|
||||
short_code = generate_code(rand(6))
|
||||
long_code = generate_code(rand(8..25))
|
||||
[nil, short_code, long_code].sample
|
||||
end
|
||||
|
||||
## Gets JSON responses from the fixtures directory
|
||||
#
|
||||
def get_fixture(name)
|
||||
#JSON.parse(File.read(File.expand_path("../fixtures/#{name}", __FILE__)))
|
||||
File.read(File.expand_path("../fixtures/#{name}", __FILE__))
|
||||
RSpec.configure do |config|
|
||||
config.before :each do |example|
|
||||
WebMock.allow_net_connect! if example.metadata[:type] == :feature
|
||||
end
|
||||
end
|
||||
|
||||
Loading…
Reference in New Issue
Block a user