Skip to content

Commit 046c975

Browse files
committed
BitPay setup and invoice
1 parent 9f5b89d commit 046c975

File tree

13 files changed

+733
-0
lines changed

13 files changed

+733
-0
lines changed

setup/bitpay_setup.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import os
2+
import sys
3+
import json
4+
import requests
5+
6+
# TODO: Need to remove below 2 lines
7+
root_dir = os.path.dirname(os.path.abspath(__file__))
8+
sys.path.append(os.path.dirname(root_dir))
9+
10+
from src.bitpay_sdk.utils.key_utils import *
11+
from src.bitpay_sdk.exceptions.bitpay_exception import BitPayException
12+
13+
is_prod = False # Set to true if the environment for which the configuration file will be generated is Production.
14+
# Will be set to Test otherwise
15+
16+
private_key_name = 'private_key.pem' # Add here the name for your Private key
17+
generate_merchant_token = True # Set to true to generate a token for the Merchant facade
18+
generate_payout_token = True # Set to true to generate a token for the Payout facade
19+
your_master_password = 'YourMasterPassword' # Will be used to encrypt your PrivateKey
20+
proxy = None
21+
base_url = 'https://bitpay.com' if is_prod else 'https://test.bitpay.com'
22+
env = 'Prod' if is_prod else 'Test'
23+
merchant_token = None
24+
payout_token = None
25+
26+
root_path = os.path.abspath(os.curdir)
27+
28+
try:
29+
private_key = generate_pem()
30+
with open(os.path.join(root_path, private_key_name), "wb") as f:
31+
f.write(private_key.encode())
32+
except BitPayException as e:
33+
print(e)
34+
35+
# Generate the public key from the private key every time (no need to store the public key).
36+
try:
37+
public_key = get_compressed_public_key_from_pem(private_key)
38+
except BitPayException as e:
39+
print(e)
40+
41+
try:
42+
sin = get_sin_from_pem(private_key)
43+
except BitPayException as e:
44+
print(e)
45+
46+
try:
47+
if generate_merchant_token:
48+
facade = 'merchant'
49+
payload = {'id': sin, 'facade': facade}
50+
51+
url = base_url + "/tokens"
52+
headers = {"content-type": "application/json", "X-accept-version": "2.0.0"}
53+
response = requests.post(url, verify=True, data=json.dumps(payload), headers=headers)
54+
55+
if response.ok:
56+
merchant_token = response.json()['data'][0]['token']
57+
print("Merchant Token: ", response.json()['data'][0]['token'])
58+
print("Merchant Token Pairing Code: ", response.json()['data'][0]['pairingCode'])
59+
60+
if generate_payout_token:
61+
facade = 'payout'
62+
payload = {'id': sin, 'facade': facade}
63+
64+
url = base_url + "/tokens"
65+
headers = {"content-type": "application/json", "X-accept-version": "2.0.0"}
66+
response = requests.post(url, verify=True, data=json.dumps(payload), headers=headers)
67+
68+
if response.ok:
69+
payout_token = response.json()['data'][0]['token']
70+
print("Payout Token: ", response.json()['data'][0]['token'])
71+
print("Payout Token Pairing Code: ", response.json()['data'][0]['pairingCode'])
72+
73+
except BitPayException as e:
74+
print(e)
75+
76+
print("\r\nPlease, copy the above pairing code/s and approve on your BitPay Account at the following link:\r\n")
77+
print(f"{base_url}/dashboard/merchant/api-tokens\r\n")
78+
print("\r\nOnce you have this Pairing Code/s approved you can move the generated files to a secure location and start "
79+
"using the Client.\r\n")
80+
81+
# Generate config file
82+
config = {
83+
"BitPayConfiguration": {
84+
"Environment": env,
85+
"EnvConfig": {
86+
"Test": {
87+
"PrivateKeyPath": None if is_prod else os.path.abspath("private_key.pem"),
88+
"PrivateKeySecret": None if is_prod else your_master_password,
89+
"ApiTokens": {
90+
"merchant": None if is_prod else merchant_token,
91+
"payout": None if is_prod else payout_token
92+
},
93+
"proxy": proxy
94+
},
95+
"Prod": {
96+
"PrivateKeyPath": os.path.abspath("private.pem") if is_prod else None,
97+
"PrivateKeySecret": your_master_password if is_prod else None,
98+
"ApiTokens": {
99+
"merchant": merchant_token if is_prod else None,
100+
"payout": payout_token if is_prod else None
101+
},
102+
"proxy": proxy
103+
}
104+
}
105+
}
106+
}
107+
108+
try:
109+
with open(os.path.abspath("bitpay.config.json"), 'w') as outfile:
110+
json.dump(config, outfile)
111+
except BitPayException as e:
112+
print(e)

src/__init__.py

Whitespace-only changes.

src/bitpay_sdk/client.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import os
2+
import json
3+
from .config import Config
4+
from .tokens import Tokens
5+
from .models.facade import Facade
6+
from .utils.rest_cli import RESTcli
7+
from .models.invoice.invoice import Invoice
8+
from .exceptions.bitpay_exception import BitPayException
9+
10+
11+
class Client:
12+
__configuration = ""
13+
__env = ""
14+
__eckey = ""
15+
__token_cache = ""
16+
__restcli = None
17+
18+
def __init__(self, config_file_path):
19+
self.build_config_from_file(config_file_path)
20+
self.init_keys()
21+
self.init()
22+
23+
def build_config_from_file(self, config_file_path):
24+
self.__configuration = Config()
25+
26+
if os.path.exists(config_file_path):
27+
28+
try:
29+
read_file = open(config_file_path, 'r')
30+
content = read_file.read()
31+
json_data = json.loads(content)
32+
self.__configuration.set_environment(json_data['BitPayConfiguration']['Environment'])
33+
self.__env = self.__configuration.get_environment()
34+
tokens = json_data["BitPayConfiguration"]["EnvConfig"][self.__env]["ApiTokens"]
35+
private_key_path = json_data["BitPayConfiguration"]["EnvConfig"][self.__env]["PrivateKeyPath"]
36+
private_key_secret = json_data["BitPayConfiguration"]["EnvConfig"][self.__env]["PrivateKeySecret"]
37+
proxy = json_data["BitPayConfiguration"]["EnvConfig"][self.__env]["proxy"]
38+
39+
env_config = {self.__env: {
40+
"PrivateKeyPath": private_key_path,
41+
"PrivateKeySecret": private_key_secret,
42+
"ApiTokens": tokens,
43+
"Proxy": proxy
44+
}}
45+
self.__configuration.set_envconfig(env_config)
46+
47+
except BitPayException as e:
48+
print(e)
49+
50+
else:
51+
raise BitPayException("Configuration file not found")
52+
53+
def init_keys(self):
54+
private_key_path = self.__configuration.get_envconfig()[self.__env]["PrivateKeyPath"]
55+
try:
56+
if not self.__eckey:
57+
with open(private_key_path) as f:
58+
self.__eckey = f.read()
59+
60+
except BitPayException as e:
61+
raise BitPayException("failed to build configuration : ")
62+
63+
def init(self):
64+
try:
65+
proxy = self.__configuration.get_envconfig()[self.__env]["Proxy"]
66+
self.__restcli = RESTcli(self.__env, self.__eckey, proxy)
67+
self.load_access_tokens()
68+
# TODO :load currencies
69+
except BitPayException as e:
70+
print(e)
71+
72+
def load_access_tokens(self):
73+
try:
74+
self.clear_access_token_cache()
75+
self.__token_cache = self.__configuration.get_envconfig()[self.__env]["ApiTokens"]
76+
except BitPayException as e:
77+
print(e)
78+
79+
def clear_access_token_cache(self):
80+
self.__token_cache = Tokens()
81+
82+
def create_invoice(self, invoice: Invoice, facade, sign_request=True):
83+
try:
84+
invoice.set_token(self.get_access_token(facade))
85+
print(invoice)
86+
invoice_json = invoice.to_json()
87+
88+
response = self.__restcli.post("invoices", invoice_json, sign_request)
89+
print(response)
90+
91+
except BitPayException as e:
92+
print(e)
93+
94+
def get_invoice(self, invoice_id, facade, sign_request=True):
95+
try:
96+
params = {"token": self.get_access_token(facade)}
97+
response_json = self.__restcli.get("invoices/%s" % invoice_id, params, sign_request)
98+
except BitPayException as e:
99+
print(e)
100+
101+
def get_invoices(self, date_start, date_end, status, order_id, limit, offset):
102+
try:
103+
params = {"token": self.get_access_token(Facade.Merchant), "dateStart": date_start, "date_end": date_end}
104+
response_json = self.__restcli.get("invoices/", parameters=params)
105+
except BitPayException as e:
106+
print(e)
107+
108+
def update_invoice(self, invoice_id, buyer_sms, sms_code, buyer_email):
109+
try:
110+
params = {'token': self.get_access_token(Facade.Merchant)}
111+
112+
if buyer_sms & sms_code:
113+
pass
114+
115+
if buyer_sms is not None:
116+
params['buyer_sms'] = buyer_sms
117+
118+
if sms_code is not None:
119+
params['sms_code'] = sms_code
120+
121+
if buyer_email is not None:
122+
params['buyer_email'] = buyer_email
123+
124+
response_json = self.__restcli.update("invoices/%s" % invoice_id, json.dumps(params))
125+
126+
except BitPayException as e:
127+
print(e)
128+
129+
def cancel_invoice(self, invoice_id):
130+
try:
131+
params = {'token': self.get_access_token(Facade.Merchant)}
132+
response_json = self.__restcli.delete("invoices/%s" % invoice_id, params)
133+
except BitPayException as e:
134+
print(e)
135+
136+
def get_invoice_webhook(self, invoice_id):
137+
try:
138+
self.get_invoice(invoice_id)
139+
params = {}
140+
# Conditions missing look from node
141+
response_json = self.__restcli.post("invoices/%s" % invoice_id + "/notifications", params)
142+
except BitPayException as e:
143+
print(e)
144+
145+
def get_access_token(self, key: str):
146+
try:
147+
return self.__token_cache[key]
148+
except BitPayException as e:
149+
print(e)

src/bitpay_sdk/config.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
class Config:
2+
__environment = ""
3+
__envconfig = ""
4+
5+
def __init__(self):
6+
pass
7+
8+
def get_environment(self):
9+
return self.__environment
10+
11+
def set_environment(self, environment):
12+
self.__environment = environment
13+
14+
def get_envconfig(self):
15+
return self.__envconfig
16+
17+
def set_envconfig(self, envconfig):
18+
self.__envconfig = envconfig
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
class BitPayException(Exception):
2+
__bitpay_message = "Unexpected Bitpay exception."
3+
__bitpay_code = "BITPAY-GENERIC";
4+
__apicode = ""
5+
6+
"""
7+
Construct the BitPayException.
8+
9+
message (string) message [optional] The Exception message to throw.
10+
code (int) $code [optional] The Exception code to throw.
11+
@param string $apiCode [optional] The API Exception code to throw.
12+
"""
13+
14+
def __init__(self, message="", code=100, apicode="000000"):
15+
if not message:
16+
message = self.__bitpay_code + ": " + self.__bitpaymessage + ":" + message
17+
self.__apicode = apicode
18+
super().__init__(message)
19+
20+
"""
21+
@return string Error code provided by the BitPay REST API
22+
"""
23+
def get_apicode(self):
24+
return self.__apicode
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from bitpay_exception import BitPayException
2+
3+
4+
class InvoiceException(BitPayException):
5+
__bitpay_message = "An unexpected error occurred while trying to manage the invoice"
6+
__bitpay_code = "BITPAY-INVOICE-GENERIC"
7+
__apicode = ""
8+
9+
'''
10+
Construct the InvoiceException.
11+
@param string $message [optional] The Exception message to throw.
12+
@param int $code [optional] The Exception code to throw.
13+
@param string $apicode [optional] The API Exception code to throw.
14+
'''
15+
16+
def __init__(self, message="", code=100, apicode="000000"):
17+
if not message:
18+
message = self.__bitpay_code + ": " + self.__bitpay_message + ":" + message
19+
self.__apicode = apicode
20+
super().__init__(message)
21+
22+
"""
23+
@return string Error code provided by the BitPay REST API
24+
"""
25+
26+
def get_apicode(self):
27+
return self.__apicode

src/bitpay_sdk/models/facade.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class Facade:
2+
Merchant = "merchant"
3+
Payout = "payout"

0 commit comments

Comments
 (0)