├── .coveragec ├── .gitignore ├── .python-version ├── .travis.yml ├── CHANGELOG.md ├── GUIDE.md ├── LICENSE.txt ├── MANIFEST ├── README.md ├── bitpay ├── __init__.py ├── bitpay_client.py ├── bitpay_exceptions.py └── bitpay_key_utils.py ├── examples ├── test.py ├── test_get_ledgers.py ├── test_merchant_facade.py ├── test_payouts.py └── test_payouts_delete.py ├── features ├── creating_invoices.feature ├── pairing.feature ├── retrieving_invoices.feature └── steps │ └── pair_steps.py ├── requirements.txt ├── setup.cfg ├── setup.py ├── tasks └── set_constants.sh └── test ├── client_test.py └── key_utils_test.py /.coveragec: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | 4 | [report] 5 | exclude_lines = 6 | # Have to re-enable the standard pragma 7 | pragma: no cover 8 | 9 | # Don't complain about missing debug-only code: 10 | def __repr__ 11 | if self\.debug 12 | 13 | # Don't complain if tests don't hit defensive assertion code: 14 | raise AssertionError 15 | raise NotImplementedError 16 | 17 | # Don't complain if non-runnable code isn't run: 18 | if 0: 19 | if __name__ == .__main__.: 20 | 21 | ignore_errors = True 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | temp/ 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .cache 41 | nosetests.xml 42 | coverage.xml 43 | 44 | # Translations 45 | *.mo 46 | *.pot 47 | 48 | # Django stuff: 49 | *.log 50 | 51 | # Sphinx documentation 52 | docs/_build/ 53 | 54 | # PyBuilder 55 | target/ 56 | 57 | cover/ 58 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 2.7.8 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: "2.7" 3 | # command to install dependencies 4 | install: 5 | - "pip install ." 6 | - "pip install -r requirements.txt" 7 | # command to run tests 8 | script: 9 | - nosetests --with-coverage --cover-package=bitpay --cover-html 10 | - behave 11 | after_success: 12 | - coveralls 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [2.3.1] - 2015-03-20 6 | ### Added 7 | - Feature: get invoice by id 8 | 9 | ## [2.3.0] - 2015-03-13 10 | ### Added 11 | - Feature: client initiated pairing 12 | 13 | ### Changed 14 | - Long sleeps in pairing test to compensate for rate limiters 15 | -------------------------------------------------------------------------------- /GUIDE.md: -------------------------------------------------------------------------------- 1 | # Using the BitPay Python Client Library 2 | 3 | 4 | ## Prerequisites 5 | You must have BitPay or test.bitpay merchant account to use this library. [Signing up for a merchant account](https://bitpay.com/start) is free. 6 | 7 | ## Quick Start 8 | ### Installation 9 | 10 | BitPay's python library was developed in Python 3.4.2. The recommended method of installion is using pip. 11 | 12 | `pip install 'bitpay-py2'` 13 | 14 | ### Basic Usage 15 | 16 | The bitpay library allows authenticating with BitPay, creating invoices, and retrieving invoices. 17 | 18 | #### Pairing with Bitpay.com 19 | 20 | Before pairing with BitPay.com, you'll need to log in to your BitPay account and navigate to /api-tokens. Generate a new pairing code and use it in the next step. You can try out various functions using the Python REPL. In this example, it's assumed that we are working against the bitpay test server and have generated the pairing code "abcdefg". 21 | 22 | > from bitpay.bitpay_client import Client 23 | > client = Client(api_uri="https://test.bitpay.com") #if api_uri is not passed, it defaults to "https://bitpay.com" 24 | > client.pair_pos_client("abcdefg") 25 | 26 | To perform a second pairing method - client-side pairing - you will need to specify the facade you want to create and call create_token(). The JSON response will include a pairing code to append to the end of the API access request URL specified in the documentation. For example, if you wanted to create a token for the merchant facade: 27 | 28 | > from bitpay.bitpay_client import Client 29 | > client = Client(api_uri="https://test.bitpay.com") 30 | > client.create_token("merchant") 31 | 32 | The response will include the pairing code which you'll use for the access request URL: 33 | 34 | ``` 35 | {'data': [{'pairingExpiration': 1426204460704, 'dateCreated': 1426118060704, 'pairingCode': 'AAAAAA', 36 | 'token': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'policies': [{'policy': 'id', 'params': 37 | ['xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'], 'method': 'inactive'}], 'facade': 'merchant'}]} 38 | ``` 39 | 40 | So in the example above, the URL would look like this: http://test.bitpay.com/api-access-request?pairingCode=AAAAAA 41 | 42 | When you nagivate to this address, you will be prompted to approve this request. Once approved, you can use the token returned to perform any API calls needed. To learn more about this method, see the [BitPay REST API documentation](https://bitpay.com/api#facades) for details. 43 | 44 | #### To create an invoice with a paired client: 45 | 46 | Using the same web client from the last step: 47 | 48 | > client.create_invoice({"price": 20, "currency": "USD", "token": client.tokens['pos']}) 49 | 50 | That will return the invoice as JSON. Other parameters can be sent, see the [BitPay REST API documentation](https://bitpay.com/api#resource-Invoices) for details. 51 | 52 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 BitPay, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | setup.cfg 3 | setup.py 4 | bitpay/__init__.py 5 | bitpay/bitpay_client.py 6 | bitpay/bitpay_exceptions.py 7 | bitpay/bitpay_key_utils.py 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repository is no longer maintained, please refer to the latest version [Here](https://github.com/bitpay/bitpay-python) 2 | 3 | # BitPay Library for Python 2 4 | 5 | Powerful, flexible, lightweight interface to the cryptographically secure BitPay Bitcoin Payment Gateway API. 6 | 7 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/bitpay/bitpay-python-py2/master/LICENSE.txt) 8 | [![Travis](https://img.shields.io/travis/bitpay/bitpay-python-py2.svg?style=flat-square)](https://travis-ci.org/bitpay/bitpay-python-py2) 9 | [![Latest Version](https://pypip.in/version/bitpay_py2/badge.svg?style=flat-square)](https://pypi.python.org/pypi/bitpay-py2/) 10 | [![Supported Python versions](https://pypip.in/py_versions/bitpay_py2/badge.svg?style=flat-square)](https://pypi.python.org/pypi/bitpay-py2/) 11 | [![Code Climate](https://img.shields.io/codeclimate/github/bitpay/bitpay-python-py2.svg?style=flat-square)](https://codeclimate.com/github/bitpay/bitpay-python-py2) 12 | [![Scrutinizer](https://img.shields.io/scrutinizer/g/bitpay/bitpay-python-py2.svg?style=flat-square)](https://scrutinizer-ci.com/g/bitpay/bitpay-python-py2/) 13 | [![Coveralls](https://img.shields.io/coveralls/bitpay/bitpay-python-py2.svg?style=flat-square)](https://coveralls.io/r/bitpay/bitpay-python-py2) 14 | 15 | This library is only compatible with Python 2. If you're using Python 3.x, please use our separate [bitpay-python](https://github.com/ionux/bitpay-python) library. 16 | 17 | ## Getting Started 18 | To get up and running with this library quickly, see these getting started guides: 19 | * https://github.com/bitpay/bitpay-python-py2/blob/master/GUIDE.md 20 | * https://support.bitpay.com/hc/en-us/articles/115003001203-How-do-I-configure-and-use-the-BitPay-Python-Library- 21 | * https://bitpay.com/docs/testing 22 | 23 | ## API Documentation 24 | 25 | API Documentation is available on the [BitPay site](https://bitpay.com/api). 26 | 27 | ## Running the Tests 28 | 29 | Before running the behavior tests, you will need a test.bitpay.com account and you will need to set the local constants. 30 | 31 | To set constants: 32 | > source tasks/set_constants.sh "https://test.bitpay.com" your@email yourpassword 33 | 34 | To run unit tests: 35 | > nosetests 36 | 37 | To run behavior tests: 38 | > behave 39 | 40 | ## Found a bug? 41 | 42 | Let us know! Send a pull request or a patch. Questions? Ask! We're here to help. We will respond to all filed issues. 43 | 44 | **BitPay Support:** 45 | 46 | * [GitHub Issues](https://github.com/bitpay/bitpay-python-py2/issues) 47 | * Open an issue if you are having issues with this library 48 | * [Support](https://help.bitpay.com) 49 | * BitPay merchant support documentation 50 | 51 | Sometimes a download can become corrupted for various reasons. However, you can verify that the release package you downloaded is correct by checking the md5 checksum "fingerprint" of your download against the md5 checksum value shown on the Releases page. Even the smallest change in the downloaded release package will cause a different value to be shown! 52 | * If you are using Windows, you can download a checksum verifier tool and instructions directly from Microsoft here: http://www.microsoft.com/en-us/download/details.aspx?id=11533 53 | * If you are using Linux or OS X, you already have the software installed on your system. 54 | * On Linux systems use the md5sum program. For example: 55 | * md5sum filename 56 | * On OS X use the md5 program. For example: 57 | * md5 filename 58 | -------------------------------------------------------------------------------- /bitpay/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitpay/bitpay-python-py2/0317208231f3bfd78f8dc118cd409a604871c443/bitpay/__init__.py -------------------------------------------------------------------------------- /bitpay/bitpay_client.py: -------------------------------------------------------------------------------- 1 | from bitpay_exceptions import * 2 | import bitpay_key_utils as key_utils 3 | import requests 4 | import json 5 | import re 6 | 7 | 8 | class Client: 9 | 10 | def __init__(self, api_uri="https://bitpay.com", insecure=False, 11 | pem=key_utils.generate_pem(), tokens={}): 12 | 13 | self.uri = api_uri 14 | self.verify = not(insecure) 15 | self.pem = pem 16 | self.client_id = key_utils.get_sin_from_pem(pem) 17 | self.tokens = tokens 18 | self.user_agent = 'bitpay-python' 19 | 20 | def pair_pos_client(self, code): 21 | """ 22 | POST /tokens 23 | https://bitpay.com/api#resource-Tokens 24 | """ 25 | if re.match("^\w{7,7}$", code) is None: 26 | raise BitPayArgumentError("pairing code is not legal") 27 | payload = {'id': self.client_id, 'pairingCode': code} 28 | headers = {"content-type": "application/json", 29 | "X-accept-version": "2.0.0"} 30 | try: 31 | response = requests.post(self.uri + "/tokens", verify=self.verify, 32 | data=json.dumps(payload), headers=headers) 33 | except Exception: 34 | raise BitPayConnectionError('Connection refused') 35 | if response.ok: 36 | self.tokens = self.token_from_response(response.json()) 37 | return self.tokens 38 | self.response_error(response) 39 | 40 | def create_token(self, facade): 41 | """ 42 | POST /tokens 43 | https://bitpay.com/api#resource-Tokens 44 | """ 45 | payload = {'id': self.client_id, 'facade': facade} 46 | headers = {"content-type": "application/json", 47 | "X-accept-version": "2.0.0"} 48 | try: 49 | response = requests.post(self.uri + "/tokens", verify=self.verify, 50 | data=json.dumps(payload), headers=headers) 51 | except Exception: 52 | raise BitPayConnectionError('Connection refused') 53 | if response.ok: 54 | self.tokens = self.token_from_response(response.json()) 55 | return response.json()['data'][0]['pairingCode'] 56 | self.response_error(response) 57 | 58 | def create_invoice(self, params): 59 | """ 60 | POST /invoices 61 | https://bitpay.com/api#resource-Invoices 62 | """ 63 | self.verify_invoice_params(params['price'], params['currency']) 64 | payload = json.dumps(params) 65 | uri = self.uri + "/invoices" 66 | xidentity = key_utils.get_compressed_public_key_from_pem(self.pem) 67 | xsignature = key_utils.sign(uri + payload, self.pem) 68 | headers = {"content-type": "application/json", 69 | "X-Identity": xidentity, 70 | "X-Signature": xsignature, "X-accept-version": "2.0.0"} 71 | try: 72 | response = requests.post(uri, data=payload, headers=headers, 73 | verify=self.verify) 74 | except Exception as pro: 75 | raise BitPayConnectionError(pro.args) 76 | if response.ok: 77 | return response.json()['data'] 78 | self.response_error(response) 79 | 80 | def get_invoice(self, invoice_id): 81 | """ 82 | GET /invoices/:invoiceId 83 | https://bitpay.com/api#resource-Invoices 84 | """ 85 | uri = self.uri + "/invoices/" + invoice_id 86 | try: 87 | response = requests.get(uri, verify=self.verify) 88 | except Exception as pro: 89 | raise BitPayConnectionError(pro.args) 90 | if response.ok: 91 | return response.json()['data'] 92 | self.response_error(response) 93 | 94 | def verify_tokens(self): 95 | """ 96 | GET /tokens 97 | https://bitpay.com/api#resource-Tokens 98 | """ 99 | xidentity = key_utils.get_compressed_public_key_from_pem(self.pem) 100 | xsignature = key_utils.sign(self.uri + "/tokens", self.pem) 101 | headers = {"content-type": "application/json", 102 | "X-Identity": xidentity, 103 | "X-Signature": xsignature, "X-accept-version": '2.0.0'} 104 | response = requests.get(self.uri + "/tokens", headers=headers, 105 | verify=self.verify) 106 | if response.ok: 107 | allTokens = response.json()['data'] 108 | selfKeys = self.tokens.keys() 109 | matchedTokens = [token for token in allTokens for key in selfKeys 110 | if token.get(key) == self.tokens.get(key)] 111 | if not matchedTokens: 112 | return False 113 | return True 114 | 115 | def token_from_response(self, responseJson): 116 | token = responseJson['data'][0]['token'] 117 | facade = responseJson['data'][0]['facade'] 118 | return {facade: token} 119 | raise BitPayBitPayError( 120 | '%(code)d: %(message)s' % {'code': response.status_code, 121 | 'message': response.json()['error']}) 122 | 123 | def verify_invoice_params(self, price, currency): 124 | if re.match("^[A-Z]{3,3}$", currency) is None: 125 | raise BitPayArgumentError("Currency is invalid.") 126 | try: 127 | float(price) 128 | except: 129 | raise BitPayArgumentError("Price must be formatted as a float") 130 | 131 | def response_error(self, response): 132 | raise BitPayBitPayError( 133 | '%(code)d: %(message)s' % {'code': response.status_code, 134 | 'message': response.json()['error']}) 135 | -------------------------------------------------------------------------------- /bitpay/bitpay_exceptions.py: -------------------------------------------------------------------------------- 1 | class BitPayArgumentError(Exception): 2 | pass 3 | 4 | 5 | class BitPayBitPayError(Exception): 6 | pass 7 | 8 | 9 | class BitPayConnectionError(Exception): 10 | pass 11 | -------------------------------------------------------------------------------- /bitpay/bitpay_key_utils.py: -------------------------------------------------------------------------------- 1 | from ecdsa import SigningKey, SECP256k1 2 | from ecdsa import util as ecdsaUtil 3 | import binascii 4 | import hashlib 5 | 6 | 7 | def generate_pem(): 8 | sk = SigningKey.generate(curve=SECP256k1) 9 | pem = sk.to_pem() 10 | pem = pem.decode("utf-8") 11 | return pem 12 | 13 | 14 | def get_sin_from_pem(pem): 15 | public_key = get_compressed_public_key_from_pem(pem) 16 | version = get_version_from_compressed_key(public_key) 17 | checksum = get_checksum_from_version(version) 18 | return base58encode(version + checksum) 19 | 20 | 21 | def get_compressed_public_key_from_pem(pem): 22 | vks = SigningKey.from_pem(pem).get_verifying_key().to_string() 23 | bts = binascii.hexlify(vks) 24 | compressed = compress_key(bts) 25 | return compressed 26 | 27 | 28 | def sign(message, pem): 29 | message = message.encode() 30 | sk = SigningKey.from_pem(pem) 31 | signed = sk.sign(message, hashfunc=hashlib.sha256, 32 | sigencode=ecdsaUtil.sigencode_der) 33 | return binascii.hexlify(signed).decode() 34 | 35 | 36 | def base58encode(hexastring): 37 | chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' 38 | int_val = int(hexastring, 16) 39 | encoded = encode58("", int_val, chars) 40 | return encoded 41 | 42 | 43 | def encode58(string, int_val, chars): 44 | if int_val == 0: 45 | return string 46 | else: 47 | new_val, rem = divmod(int_val, 58) 48 | new_string = (chars[rem]) + string 49 | return encode58(new_string, new_val, chars) 50 | 51 | 52 | def get_checksum_from_version(version): 53 | return sha_digest(sha_digest(version))[0:8] 54 | 55 | 56 | def get_version_from_compressed_key(key): 57 | sh2 = sha_digest(key) 58 | rphash = hashlib.new('ripemd160') 59 | rphash.update(binascii.unhexlify(sh2)) 60 | rp1 = rphash.hexdigest() 61 | return '0F02' + rp1 62 | 63 | 64 | def sha_digest(hexastring): 65 | return hashlib.sha256(binascii.unhexlify(hexastring)).hexdigest() 66 | 67 | 68 | def compress_key(bts): 69 | intval = int(bts, 16) 70 | prefix = find_prefix(intval) 71 | return prefix + bts[0:64].decode("utf-8") 72 | 73 | 74 | def find_prefix(intval): 75 | if(intval % 2 == 0): 76 | prefix = '02' 77 | else: 78 | prefix = '03' 79 | return prefix 80 | -------------------------------------------------------------------------------- /examples/test.py: -------------------------------------------------------------------------------- 1 | from bitpay.bitpay_exceptions import * 2 | import bitpay.bitpay_key_utils as bku 3 | from bitpay.bitpay_client import * 4 | import pprint 5 | import requests 6 | import json 7 | import re 8 | import os.path 9 | 10 | #API_HOST = "https://bitpay.com" #for production, live bitcoin 11 | API_HOST = "https://test.bitpay.com" #for testing, testnet bitcoin 12 | KEY_FILE = "/tmp/key.priv" 13 | TOKEN_FILE = "/tmp/token.priv" 14 | 15 | # check if there is a preexisting key file and token file 16 | if os.path.isfile(KEY_FILE) and os.path.isfile(TOKEN_FILE): 17 | f = open(KEY_FILE, 'r') 18 | key = f.read() 19 | f.close() 20 | f = open(TOKEN_FILE, 'r') 21 | token = f.read() 22 | f.close() 23 | else: 24 | key = bku.generate_pem() 25 | token = "" 26 | 27 | f = open(KEY_FILE, 'w') 28 | f.write(key) 29 | f.close() 30 | 31 | if token == "": 32 | client = Client(API_HOST, False, key) 33 | pairingCode = client.create_token("merchant") 34 | print "Please go to: %s/dashboard/merchant/api-tokens then enter \"%s\" then click the \"Find\" button, then click \"Approve\"" % (API_HOST, pairingCode) 35 | raw_input("When you've complete the above, hit enter to continue...") 36 | print "token is: %s" % client.tokens['merchant'] 37 | f = open(TOKEN_FILE, 'w') 38 | f.write(client.tokens['merchant']) 39 | f.close() 40 | else: 41 | print "Creating a bitpay client using existing tokens and private key from disk." 42 | client = Client(API_HOST, False, key, {'merchant': token}) 43 | 44 | print "Now we assume that the pairing code that we generated along with the crypto keys is paired with your merchant account" 45 | 46 | print "We will create an invoice using the merchant facade" 47 | 48 | invoice = client.create_invoice({"price": 1.00, "currency": "EUR", "token": client.tokens['merchant']}) 49 | 50 | pp = pprint.PrettyPrinter(indent=4) 51 | pp.pprint(invoice) 52 | 53 | 54 | print "hopefully the above looks OK?" 55 | 56 | print "continuing if we can..." 57 | 58 | print "Attempting to get settlements..." 59 | 60 | def get_stuff_from_bitpays_restful_api(client, uri, token): 61 | payload = "?token=%s" % token 62 | xidentity = bku.get_compressed_public_key_from_pem(client.pem) 63 | xsignature = bku.sign(uri + payload, client.pem) 64 | headers = {"content-type": "application/json", 65 | "X-Identity": xidentity, 66 | "X-Signature": xsignature, "X-accept-version": "2.0.0"} 67 | try: 68 | pp.pprint(headers) 69 | print uri + payload 70 | response = requests.get(uri + payload, headers=headers, verify=client.verify) 71 | except Exception as pro: 72 | raise BitPayConnectionError(pro.args) 73 | if response.ok: 74 | return response.json()['data'] 75 | client.response_error(response) 76 | 77 | 78 | """ 79 | GET /settlements 80 | https://bitpay.com/api#resource-Settlements 81 | """ 82 | settlements = get_stuff_from_bitpays_restful_api(client, client.uri + "/settlements/", token) 83 | pp.pprint("These are your settlements: %s" % settlements) 84 | 85 | 86 | print "Now that we have settlements, let's do ledger pages..." 87 | """ 88 | GET /ledgers 89 | https://bitpay.com/api#resource-Ledgers 90 | """ 91 | ledgers = get_stuff_from_bitpays_restful_api(client, client.uri + "/ledgers/", token) 92 | pp.pprint("These are your ledgers: %s" % ledgers) 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /examples/test_get_ledgers.py: -------------------------------------------------------------------------------- 1 | 2 | import bitpay.bitpay_key_utils as bku 3 | from bitpay.bitpay_client import * 4 | import pprint 5 | import requests 6 | import json 7 | import re 8 | import os.path 9 | 10 | #API_HOST = "https://bitpay.com" #for production, live bitcoin 11 | API_HOST = "https://test.bitpay.com" #for testing, testnet bitcoin 12 | KEY_FILE = "/tmp/key.priv" 13 | TOKEN_FILE = "/tmp/token.priv" 14 | 15 | # check if there is a preexisting key file 16 | if os.path.isfile(KEY_FILE): 17 | f = open(KEY_FILE, 'r') 18 | key = f.read() 19 | f.close() 20 | print("Creating a bitpay client using existing private key from disk.") 21 | else: 22 | key = bku.generate_pem() 23 | f = open(KEY_FILE, 'w') 24 | f.write(key) 25 | f.close() 26 | 27 | client = Client(API_HOST, False, key) 28 | 29 | def fetch_token(facade): 30 | if os.path.isfile(TOKEN_FILE + facade): 31 | f = open(TOKEN_FILE + facade, 'r') 32 | token = f.read() 33 | f.close() 34 | print("Reading " + facade + " token from disk.") 35 | #global client 36 | #client = Client(API_HOST, False, key, {facade: token}) 37 | client.tokens[facade] = token 38 | else: 39 | pairingCode = client.create_token(facade) 40 | print("Creating " + facade + " token.") 41 | print("Please go to: %s/dashboard/merchant/api-tokens then enter \"%s\" then click the \"Find\" button, then click \"Approve\"" % (API_HOST, pairingCode)) 42 | raw_input("When you've complete the above, hit enter to continue...") 43 | print("token is: %s" % client.tokens[facade]) 44 | f = open(TOKEN_FILE + facade, 'w') 45 | f.write(client.tokens[facade]) 46 | f.close() 47 | 48 | def get_from_bitpay_api(client, uri, token): 49 | if "?" in uri: 50 | payload = "&token=%s" % token 51 | else: 52 | payload = "?token=%s" % token 53 | xidentity = bku.get_compressed_public_key_from_pem(client.pem) 54 | xsignature = bku.sign(uri + payload, client.pem) 55 | headers = {"content-type": "application/json", 56 | "X-Identity": xidentity, 57 | "X-Signature": xsignature, "X-accept-version": "2.0.0"} 58 | try: 59 | pp.pprint(headers) 60 | print(uri + payload) 61 | response = requests.get(uri + payload, headers=headers, verify=client.verify) 62 | except Exception as pro: 63 | raise BitPayConnectionError(pro.args) 64 | if response.ok: 65 | response = response.json() 66 | return response 67 | client.response_error(response) 68 | 69 | """ 70 | POST to any resource 71 | Make sure to include the proper token in the params 72 | """ 73 | def post_to_bitpay_api(client, uri, resource, params): 74 | payload = json.dumps(params) 75 | print("payload = " + payload) 76 | uri = uri + "/" + resource 77 | xidentity = key_utils.get_compressed_public_key_from_pem(client.pem) 78 | xsignature = key_utils.sign(uri + payload, client.pem) 79 | headers = {"content-type": "application/json", 80 | "X-Identity": xidentity, 81 | "X-Signature": xsignature, "X-accept-version": "2.0.0"} 82 | try: 83 | response = requests.post(uri, data=payload, headers=headers, 84 | verify=client.verify) 85 | except Exception as pro: 86 | raise BitPayConnectionError(pro.args) 87 | if response.ok: 88 | return response.json()['data'] 89 | client.response_error(response) 90 | 91 | fetch_token("merchant") 92 | 93 | pp = pprint.PrettyPrinter(indent=4) 94 | 95 | #Now we assume that the pairing code that we generated along with the crypto keys is paired with your merchant account 96 | 97 | print("reading ledger balances") 98 | token = client.tokens['merchant'] 99 | ledger_balances = get_from_bitpay_api(client, client.uri + "/ledgers/", token) 100 | pp.pprint(ledger_balances) 101 | 102 | print("reading ledger entries") 103 | token = client.tokens['merchant'] 104 | ledger_entries = get_from_bitpay_api(client, client.uri + "/ledgers/USD?startDate=2017-09-01&endDate=2017-09-20", token) 105 | pp.pprint(ledger_entries) 106 | -------------------------------------------------------------------------------- /examples/test_merchant_facade.py: -------------------------------------------------------------------------------- 1 | from bitpay.bitpay_exceptions import * 2 | import bitpay.bitpay_key_utils as bku 3 | from bitpay.bitpay_client import * 4 | import pprint 5 | import requests 6 | import json 7 | import re 8 | import os.path 9 | 10 | #API_HOST = "https://bitpay.com" #for production, live bitcoin 11 | API_HOST = "https://test.bitpay.com" #for testing, testnet bitcoin 12 | KEY_FILE = "/tmp/key.priv" 13 | TOKEN_FILE = "/tmp/token.priv" 14 | 15 | # check if there is a preexisting key file 16 | if os.path.isfile(KEY_FILE): 17 | f = open(KEY_FILE, 'r') 18 | key = f.read() 19 | f.close() 20 | print("Creating a bitpay client using existing private key from disk.") 21 | else: 22 | key = bku.generate_pem() 23 | f = open(KEY_FILE, 'w') 24 | f.write(key) 25 | f.close() 26 | 27 | client = Client(API_HOST, False, key) 28 | 29 | def fetch_token(facade): 30 | if os.path.isfile(TOKEN_FILE + facade): 31 | f = open(TOKEN_FILE + facade, 'r') 32 | token = f.read() 33 | f.close() 34 | print("Reading " + facade + " token from disk.") 35 | #global client 36 | #client = Client(API_HOST, False, key, {facade: token}) 37 | client.tokens[facade] = token 38 | else: 39 | pairingCode = client.create_token(facade) 40 | print("Creating " + facade + " token.") 41 | print("Please go to: %s/dashboard/merchant/api-tokens then enter \"%s\" then click the \"Find\" button, then click \"Approve\"" % (API_HOST, pairingCode)) 42 | raw_input("When you've complete the above, hit enter to continue...") 43 | print("token is: %s" % client.tokens[facade]) 44 | f = open(TOKEN_FILE + facade, 'w') 45 | f.write(client.tokens[facade]) 46 | f.close() 47 | 48 | def get_from_bitpay_api(client, uri, token): 49 | payload = "?token=%s" % token 50 | xidentity = bku.get_compressed_public_key_from_pem(client.pem) 51 | xsignature = bku.sign(uri + payload, client.pem) 52 | headers = {"content-type": "application/json", 53 | "X-Identity": xidentity, 54 | "X-Signature": xsignature, "X-accept-version": "2.0.0"} 55 | try: 56 | pp.pprint(headers) 57 | print(uri + payload) 58 | response = requests.get(uri + payload, headers=headers, verify=client.verify) 59 | except Exception as pro: 60 | raise BitPayConnectionError(pro.args) 61 | if response.ok: 62 | return response.json()['data'] 63 | client.response_error(response) 64 | 65 | """ 66 | POST to any resource 67 | Make sure to include the proper token in the params 68 | """ 69 | def post_to_bitpay_api(client, uri, resource, params): 70 | payload = json.dumps(params) 71 | uri = uri + "/" + resource 72 | xidentity = key_utils.get_compressed_public_key_from_pem(client.pem) 73 | xsignature = key_utils.sign(uri + payload, client.pem) 74 | headers = {"content-type": "application/json", 75 | "X-Identity": xidentity, 76 | "X-Signature": xsignature, "X-accept-version": "2.0.0"} 77 | try: 78 | response = requests.post(uri, data=payload, headers=headers, 79 | verify=client.verify) 80 | except Exception as pro: 81 | raise BitPayConnectionError(pro.args) 82 | if response.ok: 83 | return response.json()['data'] 84 | client.response_error(response) 85 | 86 | fetch_token("merchant") 87 | 88 | #Now we assume that the pairing code that we generated along with the crypto keys is paired with your merchant account 89 | # 90 | print("We will create an invoice using the merchant facade") 91 | 92 | invoice = client.create_invoice({"price": 50.00, "currency": "USD", "token": client.tokens['merchant']}) 93 | 94 | pp = pprint.PrettyPrinter(indent=4) 95 | pp.pprint(invoice) 96 | 97 | print("hopefully the above looks OK?") 98 | 99 | print("continuing if we can...") 100 | 101 | 102 | invoiceId = "REPLACE_BY_VALID_INVOICEID" 103 | print("**") 104 | print("Now fetching an invoice with invoiceId " + invoiceId) 105 | print("**") 106 | token = client.tokens['merchant'] 107 | invoice = get_from_bitpay_api(client, client.uri + "/invoices/" + invoiceId, token) 108 | pp.pprint(invoice) 109 | 110 | -------------------------------------------------------------------------------- /examples/test_payouts.py: -------------------------------------------------------------------------------- 1 | from bitpay.bitpay_exceptions import * 2 | import bitpay.bitpay_key_utils as bku 3 | from bitpay.bitpay_client import * 4 | import pprint 5 | import requests 6 | import json 7 | import re 8 | import os.path 9 | 10 | #API_HOST = "https://bitpay.com" #for production, live bitcoin 11 | API_HOST = "https://test.bitpay.com" #for testing, testnet bitcoin 12 | KEY_FILE = "/tmp/key.priv" 13 | TOKEN_FILE = "/tmp/token.priv" 14 | pp = pprint.PrettyPrinter(indent=4) 15 | 16 | # check if there is a preexisting key file 17 | if os.path.isfile(KEY_FILE): 18 | f = open(KEY_FILE, 'r') 19 | key = f.read() 20 | f.close() 21 | print("Creating a bitpay client using existing private key from disk.") 22 | else: 23 | key = bku.generate_pem() 24 | f = open(KEY_FILE, 'w') 25 | f.write(key) 26 | f.close() 27 | 28 | client = Client(API_HOST, False, key) 29 | 30 | def fetch_token(facade): 31 | if os.path.isfile(TOKEN_FILE + facade): 32 | f = open(TOKEN_FILE + facade, 'r') 33 | token = f.read() 34 | f.close() 35 | print("Reading " + facade + " token from disk.") 36 | client.tokens[facade] = token 37 | else: 38 | pairingCode = client.create_token(facade) 39 | print("Creating " + facade + " token.") 40 | print("Please go to: %s/dashboard/merchant/api-tokens then enter \"%s\" then click the \"Find\" button, then click \"Approve\"" % (API_HOST, pairingCode)) 41 | raw_input("When you've complete the above, hit enter to continue...") 42 | print("token is: %s" % client.tokens[facade]) 43 | f = open(TOKEN_FILE + facade, 'w') 44 | f.write(client.tokens[facade]) 45 | f.close() 46 | 47 | def get_from_bitpay_api(client, uri, token): 48 | payload = "?token=%s" % token 49 | xidentity = bku.get_compressed_public_key_from_pem(client.pem) 50 | xsignature = bku.sign(uri + payload, client.pem) 51 | headers = {"content-type": "application/json", 52 | "X-Identity": xidentity, 53 | "X-Signature": xsignature, "X-accept-version": "2.0.0"} 54 | try: 55 | pp.pprint(headers) 56 | print(uri + payload) 57 | response = requests.get(uri + payload, headers=headers, verify=client.verify) 58 | except Exception as pro: 59 | raise BitPayConnectionError(pro.args) 60 | if response.ok: 61 | return response.json()['data'] 62 | client.response_error(response) 63 | 64 | """ 65 | POST to any resource 66 | Make sure to include the proper token in the params 67 | """ 68 | def post_to_bitpay_api(client, uri, resource, params): 69 | payload = json.dumps(params) 70 | uri = uri + "/" + resource 71 | xidentity = key_utils.get_compressed_public_key_from_pem(client.pem) 72 | xsignature = key_utils.sign(uri + payload, client.pem) 73 | headers = {"content-type": "application/json", 74 | "X-Identity": xidentity, 75 | "X-Signature": xsignature, "X-accept-version": "2.0.0"} 76 | try: 77 | response = requests.post(uri, data=payload, headers=headers, 78 | verify=client.verify) 79 | except Exception as pro: 80 | raise BitPayConnectionError(pro.args) 81 | if response.ok: 82 | return response.json()['data'] 83 | client.response_error(response) 84 | 85 | fetch_token("payroll") 86 | 87 | #create a payroll token 88 | token = client.tokens['payroll'] 89 | print("Creating a payout batch now") 90 | print("token = " + token) 91 | # posting a payout batch 92 | params = {"token":token, "notificationURL":"http://test.merchant.com/IPNlogger.php", "notificationEmail":"test@merchant.com", "effectiveDate":"2017-08-23", "amount":"400","currency":"USD","instructions":[ {"label":"Test1","address":"mzDTjhkfJfatXHRUWKcE2BXxHt4Pfz2PK7","amount":"300"},{"label":"Test2","address":"mfadguj41aYgEwPATAFnkKcKQNqhpNTrdi","amount":"100"}]} 93 | payoutBatch = post_to_bitpay_api(client, "https://test.bitpay.com", "payouts", params) 94 | pp.pprint(payoutBatch) 95 | 96 | -------------------------------------------------------------------------------- /examples/test_payouts_delete.py: -------------------------------------------------------------------------------- 1 | from bitpay.bitpay_exceptions import * 2 | import bitpay.bitpay_key_utils as bku 3 | from bitpay.bitpay_client import * 4 | import pprint 5 | import requests 6 | import json 7 | import re 8 | import os.path 9 | 10 | #API_HOST = "https://bitpay.com" #for production, live bitcoin 11 | API_HOST = "https://test.bitpay.com" #for testing, testnet bitcoin 12 | KEY_FILE = "/tmp/key.priv" 13 | TOKEN_FILE = "/tmp/token.priv" 14 | pp = pprint.PrettyPrinter(indent=4) 15 | 16 | # check if there is a preexisting key file 17 | if os.path.isfile(KEY_FILE): 18 | f = open(KEY_FILE, 'r') 19 | key = f.read() 20 | f.close() 21 | print("Creating a bitpay client using existing private key from disk.") 22 | else: 23 | key = bku.generate_pem() 24 | f = open(KEY_FILE, 'w') 25 | f.write(key) 26 | f.close() 27 | 28 | client = Client(API_HOST, False, key) 29 | 30 | def fetch_token(facade): 31 | if os.path.isfile(TOKEN_FILE + facade): 32 | f = open(TOKEN_FILE + facade, 'r') 33 | token = f.read() 34 | f.close() 35 | print("Reading " + facade + " token from disk.") 36 | client.tokens[facade] = token 37 | else: 38 | pairingCode = client.create_token(facade) 39 | print("Creating " + facade + " token.") 40 | print("Please go to: %s/dashboard/merchant/api-tokens then enter \"%s\" then click the \"Find\" button, then click \"Approve\"" % (API_HOST, pairingCode)) 41 | raw_input("When you've complete the above, hit enter to continue...") 42 | print("token is: %s" % client.tokens[facade]) 43 | f = open(TOKEN_FILE + facade, 'w') 44 | f.write(client.tokens[facade]) 45 | f.close() 46 | 47 | def get_from_bitpay_api(client, uri, token): 48 | payload = "?token=%s" % token 49 | xidentity = bku.get_compressed_public_key_from_pem(client.pem) 50 | xsignature = bku.sign(uri + payload, client.pem) 51 | headers = {"content-type": "application/json", 52 | "X-Identity": xidentity, 53 | "X-Signature": xsignature, "X-accept-version": "2.0.0"} 54 | try: 55 | pp.pprint(headers) 56 | print(uri + payload) 57 | response = requests.get(uri + payload, headers=headers, verify=client.verify) 58 | except Exception as pro: 59 | raise BitPayConnectionError(pro.args) 60 | if response.ok: 61 | return response.json()['data'] 62 | client.response_error(response) 63 | 64 | """ 65 | POST to any resource 66 | Make sure to include the proper token in the params 67 | """ 68 | def post_to_bitpay_api(client, uri, resource, params): 69 | payload = json.dumps(params) 70 | uri = uri + "/" + resource 71 | xidentity = key_utils.get_compressed_public_key_from_pem(client.pem) 72 | xsignature = key_utils.sign(uri + payload, client.pem) 73 | headers = {"content-type": "application/json", 74 | "X-Identity": xidentity, 75 | "X-Signature": xsignature, "X-accept-version": "2.0.0"} 76 | try: 77 | response = requests.post(uri, data=payload, headers=headers, 78 | verify=client.verify) 79 | except Exception as pro: 80 | raise BitPayConnectionError(pro.args) 81 | if response.ok: 82 | return response.json()['data'] 83 | client.response_error(response) 84 | 85 | """ 86 | DELETE to any resource 87 | Make sure to include the proper token in the params 88 | """ 89 | def delete_from_bitpay_api(client, uri, token): 90 | payload = "?token=%s" % token 91 | xidentity = bku.get_compressed_public_key_from_pem(client.pem) 92 | xsignature = bku.sign(uri + payload, client.pem) 93 | headers = {"content-type": "application/json", 94 | "X-Identity": xidentity, 95 | "X-Signature": xsignature, "X-accept-version": "2.0.0"} 96 | try: 97 | pp.pprint(headers) 98 | print(uri + payload) 99 | response = requests.delete(uri + payload, headers=headers, verify=client.verify) 100 | except Exception as pro: 101 | raise BitPayConnectionError(pro.args) 102 | if response.ok: 103 | return response.json()['data'] 104 | client.response_error(response) 105 | 106 | fetch_token("payroll") 107 | 108 | # Fetch the payroll token 109 | token = client.tokens['payroll'] 110 | print("token = " + token) 111 | 112 | batchId = "C61hxf14PKFWtChQ8Sgcne" 113 | print("Fetch a payout batch") 114 | # GET the payout batch 115 | 116 | payoutBatch = get_from_bitpay_api(client, "https://test.bitpay.com/payouts/"+batchId, token) 117 | pp.pprint(payoutBatch) 118 | 119 | # now use the token received from the GET payout batch to cancel the payout batch 120 | token = payoutBatch["token"] 121 | 122 | print("Deleting payout batch " + batchId) 123 | response = delete_from_bitpay_api(client, "https://test.bitpay.com/payouts/"+batchId, token) 124 | pp.pprint(response) 125 | 126 | -------------------------------------------------------------------------------- /features/creating_invoices.feature: -------------------------------------------------------------------------------- 1 | Feature: creating an invoice 2 | The user won't get any money 3 | If they can't 4 | Create Invoices 5 | 6 | Background: 7 | Given the user is authenticated with BitPay 8 | 9 | Scenario Outline: The request is correct 10 | When the user creates an invoice for with float input 11 | Then they should recieve an invoice in response for 12 | Examples: 13 | | price | currency | 14 | | 5.23 | USD | 15 | | 10.21 | EUR | 16 | | 0.231 | BTC | 17 | 18 | Scenario Outline: The request is correct 19 | When the user creates an invoice for with integer input 20 | Then they should recieve an invoice in response for 21 | Examples: 22 | | price | currency | 23 | | 10 | USD | 24 | 25 | Scenario Outline: The request is correct 26 | When the user creates an invoice for with string input 27 | Then they should recieve an invoice in response for 28 | Examples: 29 | | price | currency | 30 | | 5.23 | USD | 31 | | 10.21 | EUR | 32 | | 10 | USD | 33 | 34 | Scenario Outline: The invoice contains illegal characters 35 | When the user creates an invoice for with string input 36 | Then they will receive a BitPayArgumentError matching 37 | Examples: 38 | | price | currency | message | 39 | | 5,023 | USD | Price must be formatted as a float | 40 | | 3.21 | EaUR | Currency is invalid. | 41 | | "" | USD | Price must be formatted as a float | 42 | | Ten | USD | Price must be formatted as a float | 43 | | 10 | "" | Currency is invalid. | 44 | 45 | -------------------------------------------------------------------------------- /features/pairing.feature: -------------------------------------------------------------------------------- 1 | Feature: pairing with bitpay 2 | In order to access bitpay 3 | It is required that the library 4 | Is able to pair successfully 5 | 6 | Scenario: the client has a correct pairing code 7 | Given the user waits 1 seconds 8 | And the user pairs with BitPay with a valid pairing code 9 | Then the user is paired with BitPay 10 | 11 | Scenario: the client initiates pairing 12 | Given the user waits 1 seconds 13 | And the user requests a client-side pairing 14 | Then they will receive a claim code 15 | 16 | Scenario Outline: the client has a bad pairing code 17 | Given the user fails to pair with a semantically code 18 | Then they will receive a matching 19 | Examples: 20 | | valid | code | error | message | 21 | | valid | a1b2c3d | BitPayBitPayError | 500: Unable to create token | 22 | | invalid | a1b2c3d4 | BitPayArgumentError | pairing code is not legal | 23 | 24 | -------------------------------------------------------------------------------- /features/retrieving_invoices.feature: -------------------------------------------------------------------------------- 1 | Feature: retrieving an invoice 2 | The user may want to retrieve invoices 3 | So that they can view them 4 | 5 | Scenario: The request is correct 6 | Given that a user knows an invoice id 7 | Then they can retrieve that invoice 8 | 9 | -------------------------------------------------------------------------------- /features/steps/pair_steps.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'bitpay'))) 4 | from splinter import Browser 5 | import time 6 | import six 7 | import json 8 | from bitpay_client import Client 9 | import bitpay_key_utils as key_utils 10 | import re 11 | 12 | ROOT_ADDRESS = os.environ['RCROOTADDRESS'] 13 | USER_NAME = os.environ['RCTESTUSER'] 14 | PASSWORD = os.environ['RCTESTPASSWORD'] 15 | PEM = '-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEICg7E4NN53YkaWuAwpoqjfAofjzKI7Jq1f532dX+0O6QoAcGBSuBBAAK\noUQDQgAEjZcNa6Kdz6GQwXcUD9iJ+t1tJZCx7hpqBuJV2/IrQBfue8jh8H7Q/4vX\nfAArmNMaGotTpjdnymWlMfszzXJhlw==\n-----END EC PRIVATE KEY-----\n' 16 | client = Client() 17 | invoice = None 18 | exception = None 19 | 20 | @given(u'the user pairs with BitPay with a valid pairing code') 21 | def step_impl(context): 22 | time.sleep(1) 23 | claim_code = get_claim_code_from_server() 24 | global client 25 | client = Client(api_uri=ROOT_ADDRESS, insecure=True, pem=PEM) 26 | try: 27 | client.pair_pos_client(claim_code) 28 | except Exception as error: 29 | if error.args[0] == "500: Unable to create token because of too many requests.": 30 | time.sleep(60) 31 | client.pair_pos_client(claim_code) 32 | assert client.tokens['pos'] 33 | 34 | @given(u'the user requests a client-side pairing') 35 | def step_impl(context): 36 | global pairing_code 37 | time.sleep(1) 38 | client = Client(api_uri=ROOT_ADDRESS, insecure=True, pem=PEM) 39 | try: 40 | pairing_code = client.create_token("merchant") 41 | except Exception as error: 42 | if error.args[0] == "500: Unable to create token because of too many requests.": 43 | time.sleep(60) 44 | pairing_code = client.create_token("merchant") 45 | 46 | @then(u'they will receive a claim code') 47 | def step_impl(context): 48 | assert re.match("^\w{7,7}$", pairing_code) != None 49 | 50 | @then(u'the user is paired with BitPay') 51 | def step_impl(context): 52 | assert client.verify_tokens() 53 | 54 | @given(u'the user fails to pair with a semantically {valid} code {code}') 55 | def step_impl(context, code, valid): 56 | time.sleep(1) 57 | try: 58 | client.pair_pos_client(code) 59 | except Exception as error: 60 | global exception 61 | exception = error 62 | if exception.args[0] == "500: Unable to create token because of too many requests.": 63 | time.sleep(60) 64 | try: 65 | client.pair_pos_client(code) 66 | except Exception as error: 67 | global exception 68 | exception = error 69 | 70 | @given(u'that a user knows an invoice id') 71 | def step_impl(context): 72 | global client 73 | global invoice 74 | client = client_from_stored_values() 75 | create_invoice(10, "USD") 76 | 77 | @then(u'they can retrieve that invoice') 78 | def step_impl(context): 79 | global client 80 | global invoice 81 | amount = invoice['price'] 82 | invoice_id = invoice['id'] 83 | retrieved_invoice = client.get_invoice(invoice_id) 84 | assert amount == retrieved_invoice['price'] 85 | 86 | @then(u'they will receive a {error} matching {message}') 87 | def step_impl(context, error, message): 88 | assert exception.__class__.__name__ == error and exception.args[0] == message, "%s != %s" % (exception.args[0], message) 89 | 90 | @given(u'the user is authenticated with BitPay') 91 | def step_impl(context): 92 | global client 93 | client = client_from_stored_values() 94 | assert client.verify_tokens() 95 | 96 | @given(u'the user waits {wait:d} seconds') 97 | def step_impl(context, wait): 98 | time.sleep(wait) 99 | 100 | @when(u'the user creates an invoice for {amount:f} {currency} with float input') 101 | def step_impl(context, amount, currency): 102 | create_invoice(amount, currency) 103 | 104 | @when(u'the user creates an invoice for {amount:d} {currency} with integer input') 105 | def step_impl(context, amount, currency): 106 | create_invoice(amount, currency) 107 | 108 | @when(u'the user creates an invoice for {amount} {currency} with string input') 109 | def step_impl(context, amount, currency): 110 | if amount == '""': 111 | amount = "" 112 | if currency == '""': 113 | currency == "" 114 | create_invoice(amount, currency) 115 | 116 | @then(u'they should recieve an invoice in response for {amount:g} {currency}') 117 | def step_impl(context, amount, currency): 118 | global invoice 119 | assert invoice['price'] == amount and invoice['currency'] == currency 120 | 121 | def create_invoice(amount, currency): 122 | global client 123 | global invoice 124 | try: 125 | token = client.tokens['pos'] 126 | invoice = client.create_invoice({"price": amount, "currency": currency, "token": token }) 127 | except Exception as error: 128 | global exception 129 | print(error.__class__.__name__) 130 | print(error.args[0]) 131 | exception = error 132 | 133 | def client_from_stored_values(): 134 | for f in ["local.pem", "tokens.json"]: 135 | try: 136 | open("temp/" + f) 137 | exists = True 138 | except: 139 | exists = False 140 | break 141 | if exists: 142 | f = open("temp/local.pem", 'r') 143 | pem = f.read() 144 | f = open("temp/tokens.json", 'r') 145 | token = f.read() 146 | token = json.loads(token) 147 | client = Client(api_uri=ROOT_ADDRESS, insecure=True, pem=pem, tokens=token) 148 | else: 149 | claim_code = get_claim_code_from_server() 150 | pem = key_utils.generate_pem() 151 | client = Client(api_uri=ROOT_ADDRESS, insecure=True, pem=pem) 152 | token = json.dumps(client.pair_pos_client(claim_code)) 153 | if not os.path.exists("temp"): 154 | os.makedirs("temp") 155 | f = open("temp/local.pem", 'w') 156 | f.write(pem) 157 | f = open("temp/tokens.json", 'w') 158 | f.write(token) 159 | return client 160 | 161 | def get_claim_code_from_server(): 162 | browser = Browser('phantomjs', service_args=['--ignore-ssl-errors=true']) 163 | browser.visit(ROOT_ADDRESS + "/merchant-login") 164 | time.sleep(5) 165 | browser.fill_form({"email": USER_NAME, "password": PASSWORD}) 166 | browser.find_by_id("loginButton")[0].click() 167 | time.sleep(1) 168 | browser.visit(ROOT_ADDRESS + "/api-tokens") 169 | browser.find_by_css(".token-access-new-button").find_by_css(".btn").find_by_css(".icon-plus")[0].click() 170 | browser.find_by_id("token-new-form").find_by_css(".btn")[0].click() 171 | return browser.find_by_css(".token-claimcode")[0].html 172 | 173 | 174 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | behave==1.2.5 2 | ecdsa==0.13 3 | httmock==1.2.2 4 | nose==1.3.4 5 | parse==1.6.6 6 | parse-type==0.3.4 7 | pymongo==2.8 8 | requests==2.5.1 9 | selenium==2.44.0 10 | six==1.9.0 11 | splinter==0.7.0 12 | virtualenv==12.0.7 13 | coveralls 14 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from distutils.core import setup 3 | setup( 4 | name="bitpay-py2", 5 | packages=["bitpay"], 6 | version="2.3.5", 7 | description="Accept bitcoin with BitPay", 8 | author="BitPay Integrations Team", 9 | author_email="integrations@bitpay.com", 10 | url="https://github.com/bitpay/bitpay-python-py2", 11 | download_url="https://github.com/bitpay/bitpay-python-py2/tarball/v2.3.5", 12 | keywords=["bitcoin", "payments", "crypto"], 13 | license="MIT License", 14 | classifiers=["Programming Language :: Python", 15 | "Programming Language :: Python :: 2.7", 16 | "Programming Language :: Python :: 2 :: Only", 17 | "Development Status :: 5 - Production/Stable", 18 | "Environment :: Web Environment", 19 | "Intended Audience :: Developers", 20 | "License :: OSI Approved :: MIT License", 21 | "Operating System :: OS Independent", 22 | "Topic :: Software Development :: Libraries :: Python Modules", 23 | "Topic :: Office/Business :: Financial"], 24 | long_description="""\ 25 | Python Library for integrating with BitPay 26 | ------------------------------------- 27 | 28 | This library is compatible with Python 2.7.8. It is not compatible with Python 3. 29 | 30 | This library is a simple way to integrate your application with 31 | BitPay for taking bitcoin payments. It exposes three basic 32 | functions, authenticating with bitpay, creating invoices, 33 | and retrieving invoices. It is not meant as a replacement for 34 | the entire BitPay API. However, the key_utils module contains 35 | all of the tools you need to use the BitPay API for other 36 | purposes. 37 | 38 | © 2015 BitPay, Inc. 39 | """ 40 | ) 41 | -------------------------------------------------------------------------------- /tasks/set_constants.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export RCROOTADDRESS=$1 4 | echo $RCROOTADDRESS 5 | export RCTESTUSER=$2 6 | echo $RCTESTUSER 7 | export RCTESTPASSWORD=$3 8 | echo $RCTESTPASSWORD 9 | -------------------------------------------------------------------------------- /test/client_test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'bitpay'))) 4 | from bitpay_exceptions import * 5 | from bitpay_client import Client 6 | from httmock import urlmatch, HTTMock 7 | import requests 8 | import unittest 9 | 10 | class TestClient(unittest.TestCase): 11 | def test_pair_code_check(self): 12 | """tests whether the pairing code is syntatically correct""" 13 | new_client = Client() 14 | with self.assertRaisesRegexp(BitPayArgumentError, "pairing code is not legal"): 15 | new_client.pair_pos_client("abcd") 16 | 17 | def test_passes_errors_when_pairing(self): 18 | """web errors should be gracefully passed to the client""" 19 | new_client = Client() 20 | def a_request(url, request): 21 | return {'status_code': 403, 'content': b'{"error": "this is a 403 error"}'} 22 | with HTTMock(a_request): 23 | with self.assertRaisesRegexp(BitPayBitPayError, "403: this is a 403 error"): 24 | new_client.pair_pos_client("a1B2c3d") 25 | 26 | def test_passes_errors_when_creating_invoice(self): 27 | """web errors should be gracefully passed to the client""" 28 | new_client = Client() 29 | def a_request(url, request): 30 | return {'status_code': 403, 'content': b'{"error": "this is a 403 error"}'} 31 | with HTTMock(a_request): 32 | with self.assertRaisesRegexp(BitPayBitPayError, "403: this is a 403 error"): 33 | new_client.create_invoice({"price": 20, "currency": "USD"}) 34 | 35 | 36 | -------------------------------------------------------------------------------- /test/key_utils_test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'bitpay'))) 4 | import bitpay_key_utils as utils 5 | import re 6 | import unittest 7 | from ecdsa import SigningKey, SECP256k1, VerifyingKey 8 | from ecdsa import util as ecdsaUtil 9 | import binascii 10 | import hashlib 11 | 12 | class TestKeyUtils(unittest.TestCase): 13 | 14 | def test_generate_pem(self): 15 | pem = utils.generate_pem() 16 | match = re.match(r"-----BEGIN EC PRIVATE KEY-----", pem) 17 | self.assertIsNotNone(match) 18 | 19 | def test_sin_from_pem(self): 20 | pem = '-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEICg7E4NN53YkaWuAwpoqjfAofjzKI7Jq1f532dX+0O6QoAcGBSuBBAAK\noUQDQgAEjZcNa6Kdz6GQwXcUD9iJ+t1tJZCx7hpqBuJV2/IrQBfue8jh8H7Q/4vX\nfAArmNMaGotTpjdnymWlMfszzXJhlw==\n-----END EC PRIVATE KEY-----\n' 21 | assert utils.get_sin_from_pem(pem) == 'TeyN4LPrXiG5t2yuSamKqP3ynVk3F52iHrX' 22 | 23 | def test_sign(self): 24 | pem = '-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEICg7E4NN53YkaWuAwpoqjfAofjzKI7Jq1f532dX+0O6QoAcGBSuBBAAK\noUQDQgAEjZcNa6Kdz6GQwXcUD9iJ+t1tJZCx7hpqBuJV2/IrQBfue8jh8H7Q/4vX\nfAArmNMaGotTpjdnymWlMfszzXJhlw==\n-----END EC PRIVATE KEY-----\n' 25 | signed = utils.sign("message", pem) 26 | sk = SigningKey.from_pem(pem) 27 | vk = sk.get_verifying_key() 28 | print(signed) 29 | signed = binascii.unhexlify(signed) 30 | vk.verify(signed, "message".encode(), hashfunc=hashlib.sha256, sigdecode=ecdsaUtil.sigdecode_der) 31 | 32 | --------------------------------------------------------------------------------