Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
joeblackwaslike committed Mar 7, 2018
0 parents commit f9a9ce2
Show file tree
Hide file tree
Showing 14 changed files with 323 additions and 0 deletions.
14 changes: 14 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

Copyright 2017 Jose Black

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# btcpay-python


## Pairing
* Generate and save private key:
```python
import btcpay.crypto
privkey = btcpay.crypto.generate_privkey()
```
* Create client:
```python
from btcpay import BTCPayClient
client = BTCPayClient(host='http://hostname', pem=privkey)
```
* On BTCPay server > shop > access tokens > create new token, copy pairing code:
* Pair client to server and save returned token:
```python
client.pair_client(<pairing-code>)
>>> {'merchant': "xdr9vw3v5wc0w90859v45"}
```
* Recreate client:
```python
client = BTCPayClient(
host='http://hostname',
pem=privkey,
tokens={'merchant': "xdr9vw3v5wc0w90859v45"}
)
```


## Creating a client
```python
client = BTCPayClient(
host='http://hostname',
pem=privkey,
tokens={'merchant': "xdr9vw3v5wc0w90859v45"}
)
```

## Get rates
```python
client.get_rates()
```


## Create specific rate
```python
client.get_rate('USD')
```


## Create invoice
See bitpay api documentation: https://bitpay.com/api#resource-Invoices
```python
client.create_invoice({"price": 20, "currency": "USD"})
```


## Get invoice
```python
client.get_invoice(<invoice-id>)
```
21 changes: 21 additions & 0 deletions btcpay.egg-info/PKG-INFO
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Metadata-Version: 1.1
Name: btcpay
Version: 1.0.0
Summary: Accept bitcoin with BTCPay
Home-page: https://github.com/joeblackwaslike/btcpay-python
Author: Joe Black
Author-email: [email protected]
License: Apache 2.0
Download-URL: https://github.com/joeblackwaslike/btcpay-python/tarball/v1.0.0
Description: UNKNOWN
Keywords: bitcoin,payments,crypto
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache2 License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Office/Business :: Financial
8 changes: 8 additions & 0 deletions btcpay.egg-info/SOURCES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
btcpay/__init__.py
btcpay/client.py
btcpay/crypto.py
btcpay.egg-info/PKG-INFO
btcpay.egg-info/SOURCES.txt
btcpay.egg-info/dependency_links.txt
btcpay.egg-info/requires.txt
btcpay.egg-info/top_level.txt
1 change: 1 addition & 0 deletions btcpay.egg-info/dependency_links.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

2 changes: 2 additions & 0 deletions btcpay.egg-info/requires.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ecdsa
requests
1 change: 1 addition & 0 deletions btcpay.egg-info/top_level.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
btcpay
2 changes: 2 additions & 0 deletions btcpay/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import crypto, client
from .client import BTCPayClient
Binary file added btcpay/__pycache__/__init__.cpython-35.pyc
Binary file not shown.
Binary file added btcpay/__pycache__/client.cpython-35.pyc
Binary file not shown.
Binary file added btcpay/__pycache__/crypto.cpython-35.pyc
Binary file not shown.
94 changes: 94 additions & 0 deletions btcpay/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""btcpay.client
BTCPay API Client.
"""

import re
import json

import requests

from . import crypto


class BTCPayClient:
def __init__(self, host, pem, insecure=False, tokens=None):
self.host = host
self.verify = not(insecure)
self.pem = pem
self.tokens = tokens or dict()
self.client_id = crypto.get_sin_from_pem(pem)
self.user_agent = 'btcpay-python'
self.s = requests.Session()
self.s.verify = self.verify
self.s.headers.update(
{'Content-Type': 'application/json',
'accept': 'application/json',
'X-accept-version': '2.0.0'})

def _create_signed_headers(self, uri, payload):
return {
"X-Identity": crypto.get_compressed_public_key_from_pem(self.pem),
"X-Signature": crypto.sign(uri + payload, self.pem)
}

def _signed_get_request(self, path, token=None):
token = token or list(self.tokens.values())[0]
uri = self.host + path
payload = "?token=%s" % token
headers = self._create_signed_headers(uri, payload)
r = self.s.get(uri + payload, headers=headers)
r.raise_for_status()
return r.json()['data']

def _signed_post_request(self, path, payload, token=None):
token = token or list(self.tokens.values())[0]
uri = self.host + path
payload['token'] = token
payload = json.dumps(payload)
headers = self._create_signed_headers(uri, payload)
r = self.s.post(uri, headers=headers, data=payload)
r.raise_for_status()
return r.json()['data']

def _unsigned_request(self, path, payload=None):
uri = self.host + path
if payload:
payload = json.dumps(payload)
r = self.s.post(uri, headers=headers, data=payload)
else:
r = self.s.get(uri, headers=headers)
r.raise_for_status()
return r.json()['data']

def get_rates(self):
return self._signed_get_request('/rates/')

def get_rate(self, currency):
rates = self.get_rates()
rate = [rate for rate in rates if rate['code'] == currency.upper()][0]
return rate['rate']

def create_invoice(self, payload, token=None):
if re.match(r'^[A-Z]{3,3}$', payload['currency']) is None:
raise ValueError('Currency is invalid.')
try:
float(payload['price'])
except ValueError as e:
raise ValueError('Price must be a float') from e
return self._signed_post_request('/invoices/', payload, token=token)

def get_invoice(self, invoice_id, token=None):
return self._signed_get_request('/invoices/' + invoice_id, token=token)

def pair_client(self, code):
if re.match(r'^\w{7,7}$', code) is None:
raise ValueError("pairing code is not legal")
payload = {'id': self.client_id, 'pairingCode': code}
return self._unsigned_request('/tokens', payload)

def __repr__(self):
return '{}({})'.format(
type(self).__name__,
self.host
)
86 changes: 86 additions & 0 deletions btcpay/crypto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""btcpay.crypto
These are various crytography related utility functions borrowed from:
bitpay-python: https://github.com/bitpay/bitpay-python
"""

import binascii
import hashlib

from ecdsa import SigningKey, SECP256k1, VerifyingKey
from ecdsa import util as ecdsaUtil


def generate_privkey():
sk = SigningKey.generate(curve=SECP256k1)
pem = sk.to_pem()
pem = pem.decode('utf-8')
return pem


def get_sin_from_pem(pem):
public_key = get_compressed_public_key_from_pem(pem)
version = get_version_from_compressed_key(public_key)
checksum = get_checksum_from_version(version)
return base58encode(version + checksum)


def get_compressed_public_key_from_pem(pem):
vks = SigningKey.from_pem(pem).get_verifying_key().to_string()
bts = binascii.hexlify(vks)
compressed = compress_key(bts)
return compressed


def sign(message, pem):
message = message.encode()
sk = SigningKey.from_pem(pem)
signed = sk.sign(message, hashfunc=hashlib.sha256,
sigencode=ecdsaUtil.sigencode_der)
return binascii.hexlify(signed).decode()


def base58encode(hexastring):
chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
int_val = int(hexastring, 16)
encoded = encode58('', int_val, chars)
return encoded


def encode58(string, int_val, chars):
if int_val == 0:
return string
else:
(new_val, rem) = divmod(int_val, 58)
new_string = chars[rem] + string
return encode58(new_string, new_val, chars)


def get_checksum_from_version(version):
return sha_digest(sha_digest(version))[0:8]


def get_version_from_compressed_key(key):
sh2 = sha_digest(key)
rphash = hashlib.new('ripemd160')
rphash.update(binascii.unhexlify(sh2))
rp1 = rphash.hexdigest()
return '0F02' + rp1


def sha_digest(hexastring):
return hashlib.sha256(binascii.unhexlify(hexastring)).hexdigest()


def compress_key(bts):
intval = int(bts, 16)
prefix = find_prefix(intval)
return prefix + bts[0:64].decode('utf-8')


def find_prefix(intval):
if intval % 2 == 0:
prefix = '02'
else:
prefix = '03'
return prefix
32 changes: 32 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from setuptools import setup, find_packages


setup(
name="btcpay",
packages=find_packages(),
version="1.0.0",
description="Accept bitcoin with BTCPay",
author="Joe Black",
author_email="[email protected]",
url="https://github.com/joeblackwaslike/btcpay-python",
download_url="https://github.com/joeblackwaslike/btcpay-python/tarball/v1.0.0",
license='Apache 2.0',
keywords=["bitcoin", "payments", "crypto"],
install_requires=[
"requests",
"ecdsa"
],
package_data={'': ['LICENSE']},
include_package_data=True,
classifiers=[
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3 :: Only",
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache2 License",
"Operating System :: OS Independent",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Office/Business :: Financial"
]
)

0 comments on commit f9a9ce2

Please sign in to comment.