btcpay working! added bitpay client code to source tree to fix a bug
fixed a stripe race condition added account balance warning to account balance page
This commit is contained in:
0
capsulflask/btcpay/__init__.py
Normal file
0
capsulflask/btcpay/__init__.py
Normal file
121
capsulflask/btcpay/client.py
Normal file
121
capsulflask/btcpay/client.py
Normal file
@ -0,0 +1,121 @@
|
||||
from capsulflask.btcpay.exceptions import *
|
||||
from capsulflask.btcpay import key_utils
|
||||
import requests
|
||||
import json
|
||||
import re
|
||||
|
||||
class Client:
|
||||
def __init__(self, api_uri="https://bitpay.com", insecure=False, pem=key_utils.generate_pem(), tokens={}):
|
||||
self.uri = api_uri
|
||||
self.verify = not(insecure)
|
||||
self.pem = pem
|
||||
self.client_id = key_utils.get_sin_from_pem(pem)
|
||||
self.tokens = tokens
|
||||
self.user_agent = 'bitpay-python'
|
||||
|
||||
def pair_pos_client(self, code):
|
||||
if re.match("^\w{7,7}$", code) is None:
|
||||
raise BtcPayArgumentError("pairing code is not legal")
|
||||
payload = {'id': self.client_id, 'pairingCode': code}
|
||||
response = self.unsigned_request('/tokens', payload)
|
||||
if response.ok:
|
||||
self.tokens = self.token_from_response(response.json())
|
||||
return self.tokens
|
||||
self.response_error(response)
|
||||
|
||||
def create_token(self, facade):
|
||||
payload = {'id': self.client_id, 'facade': facade}
|
||||
response = self.unsigned_request('/tokens', payload)
|
||||
if response.ok:
|
||||
self.tokens = self.token_from_response(response.json())
|
||||
return response.json()['data'][0]['pairingCode']
|
||||
self.response_error(response)
|
||||
|
||||
def create_invoice(self, params):
|
||||
self.verify_invoice_params(params['price'], params['currency'])
|
||||
payload = json.dumps(params)
|
||||
uri = self.uri + "/invoices"
|
||||
xidentity = key_utils.get_compressed_public_key_from_pem(self.pem)
|
||||
xsignature = key_utils.sign(uri + payload, self.pem)
|
||||
headers = {"content-type": "application/json", 'X-Identity': xidentity, 'X-Signature': xsignature, 'X-accept-version': '2.0.0'}
|
||||
try:
|
||||
response = requests.post(uri, data=payload, headers=headers, verify=self.verify)
|
||||
except Exception as pro:
|
||||
raise BtcPayConnectionError(pro.args)
|
||||
if response.ok:
|
||||
return response.json()['data']
|
||||
self.response_error(response)
|
||||
|
||||
def get_invoice(self, invoice_id):
|
||||
uri = self.uri + "/invoices/" + invoice_id
|
||||
xidentity = key_utils.get_compressed_public_key_from_pem(self.pem)
|
||||
xsignature = key_utils.sign(uri, self.pem)
|
||||
headers = {"content-type": "application/json", 'X-Identity': xidentity, 'X-Signature': xsignature, 'X-accept-version': '2.0.0'}
|
||||
|
||||
try:
|
||||
response = requests.get(uri, headers=headers, verify=self.verify)
|
||||
except Exception as pro:
|
||||
raise BtcPayConnectionError(pro.args)
|
||||
if response.ok:
|
||||
return response.json()['data']
|
||||
self.response_error(response)
|
||||
|
||||
def verify_tokens(self):
|
||||
"""
|
||||
Deprecated, will be made private in 2.4
|
||||
"""
|
||||
xidentity = key_utils.get_compressed_public_key_from_pem(self.pem)
|
||||
url = self.uri + "/tokens"
|
||||
xsignature = key_utils.sign(self.uri + "/tokens", self.pem)
|
||||
headers = {"content-type": "application/json", 'X-Identity': xidentity, 'X-Signature': xsignature, 'X-accept-version': '2.0.0'}
|
||||
response = requests.get(self.uri + "/tokens", headers=headers, verify=self.verify)
|
||||
if response.ok:
|
||||
allTokens = response.json()['data']
|
||||
selfKeys = self.tokens.keys()
|
||||
matchedTokens = [token for token in allTokens for key in selfKeys if token.get(key) == self.tokens.get(key)]
|
||||
if not matchedTokens:
|
||||
return False
|
||||
return True
|
||||
|
||||
def token_from_response(self, responseJson):
|
||||
"""
|
||||
Deprecated, will be made private in 2.4
|
||||
"""
|
||||
token = responseJson['data'][0]['token']
|
||||
facade = responseJson['data'][0]['facade']
|
||||
return {facade: token}
|
||||
raise BtcPayBtcPayError('%(code)d: %(message)s' % {'code': response.status_code, 'message': response.json()['error']})
|
||||
|
||||
def verify_invoice_params(self, price, currency):
|
||||
"""
|
||||
Deprecated, will be made private in 2.4
|
||||
"""
|
||||
if re.match("^[A-Z]{3,3}$", currency) is None:
|
||||
raise BtcPayArgumentError("Currency is invalid.")
|
||||
try:
|
||||
float(price)
|
||||
except:
|
||||
raise BtcPayArgumentError("Price must be formatted as a float")
|
||||
def response_error(self, response):
|
||||
raise BtcPayBtcPayError('%(code)d: %(message)s' % {'code': response.status_code, 'message': response.json()['error']})
|
||||
|
||||
def unsigned_request(self, path, payload=None):
|
||||
"""
|
||||
generic btcpay usigned wrapper
|
||||
passing a payload will do a POST, otherwise a GET
|
||||
"""
|
||||
headers = {"content-type": "application/json", "X-accept-version": "2.0.0"}
|
||||
try:
|
||||
if payload:
|
||||
response = requests.post(self.uri + path, verify=self.verify, data=json.dumps(payload), headers=headers)
|
||||
else:
|
||||
response = requests.get(self.uri + path, verify=self.verify, headers=headers)
|
||||
except Exception as pro:
|
||||
raise BtcPayConnectionError('Connection refused')
|
||||
return response
|
||||
|
||||
def unsigned_get_request(self, path, payload=None):
|
||||
"""
|
||||
Deprecated, will be removed in 2.4
|
||||
"""
|
||||
return self.unsigned_request('/tokens', payload)
|
3
capsulflask/btcpay/exceptions.py
Normal file
3
capsulflask/btcpay/exceptions.py
Normal file
@ -0,0 +1,3 @@
|
||||
class BtcPayArgumentError(Exception): pass
|
||||
class BtcPayBtcPayError(Exception): pass
|
||||
class BtcPayConnectionError(Exception): pass
|
68
capsulflask/btcpay/key_utils.py
Normal file
68
capsulflask/btcpay/key_utils.py
Normal file
@ -0,0 +1,68 @@
|
||||
from ecdsa import SigningKey, SECP256k1, VerifyingKey
|
||||
from ecdsa import util as ecdsaUtil
|
||||
import binascii
|
||||
import hashlib
|
||||
|
||||
def generate_pem():
|
||||
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
|
||||
|
Reference in New Issue
Block a user