From 83ccef5c7df9258751746b64e4408df59366d2bb Mon Sep 17 00:00:00 2001 From: Thomas Steen Rasmussen Date: Tue, 10 May 2016 21:06:38 +0200 Subject: [PATCH] add vendor folder with coinify sdk files from 9569fa65fc1d65d62d78cf141a54e9ff053885d3 https://github.com/CoinifySoftware/python-sdk/blob/9569fa65fc1d65d62d78cf141a54e9ff053885d3/coinify_callback.py https://raw.githubusercontent.com/CoinifySoftware/python-sdk/9569fa65fc1d65d62d78cf141a54e9ff053885d3/coinify_api.py --- vendor/__init__.py | 0 vendor/coinfy_api.py | 268 +++++++++++++++++++++++++++++++++++++ vendor/coinify_callback.py | 23 ++++ 3 files changed, 291 insertions(+) create mode 100644 vendor/__init__.py create mode 100644 vendor/coinfy_api.py create mode 100644 vendor/coinify_callback.py diff --git a/vendor/__init__.py b/vendor/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/vendor/coinfy_api.py b/vendor/coinfy_api.py new file mode 100644 index 00000000..4241e48a --- /dev/null +++ b/vendor/coinfy_api.py @@ -0,0 +1,268 @@ +import requests, json, time, hashlib, hmac + + +class CoinifyAPI: + api_key = None + """Coinify API key. Get yours at https://www.coinify.com/merchant/api""" + + api_secret = None + """Coinify API secret. Get yours at https://www.coinify.com/merchant/api""" + + api_base_url = None + """Base URL to the Coinify API""" + + API_DEFAULT_BASE_URL = "https://api.coinify.com" + + def __init__(self, api_key=None, api_secret=None, api_base_url=None): + """ + Create an instance of the CoinifyAPI class. + Provide your API key and API secret. + + Set api_base_url to None to use default + """ + self.api_key = api_key + self.api_secret = api_secret + self.api_base_url = api_base_url or self.API_DEFAULT_BASE_URL + + def invoices_list(self, limit=None, offset=None, include_expired=None): + """ + Returns an array of your Coinify invoices + + See https://www.coinify.com/docs/api/#list-all-invoices + """ + query_params = {} + + if limit is not None: + query_params['limit'] = limit + if offset is not None: + query_params['offset'] = offset + if include_expired is not None: + query_params['include_expired'] = include_expired + + return self.call_api_authenticated('/v3/invoices', query_params=query_params) + + def invoice_create(self, amount, currency, plugin_name, plugin_version, + description=None, custom=None, callback_url=None, callback_email=None, + return_url=None, cancel_url=None, input_currency=None, input_return_address=None): + + """ + Create a new invoice. + + See https://www.coinify.com/docs/api/#create-an-invoice + """ + params = { + 'amount': amount, + 'currency': currency, + 'plugin_name': plugin_name, + 'plugin_version': plugin_version + } + + if description is not None: + params['description'] = description + if custom is not None: + params['custom'] = custom + if callback_url is not None: + params['callback_url'] = callback_url + if callback_email is not None: + params['callback_email'] = callback_email + if return_url is not None: + params['return_url'] = return_url + if cancel_url is not None: + params['cancel_url'] = cancel_url + if input_currency is not None: + params['input_currency'] = input_currency + if input_return_address is not None: + params['input_return_address'] = input_return_address + + return self.call_api_authenticated('/v3/invoices', 'POST', params) + + def invoice_get(self, invoice_id): + """ + Get a specific invoice + + See https://www.coinify.com/docs/api/#get-a-specific-invoice + """ + path = '/v3/invoices/%d' % (invoice_id,) + + return self.call_api_authenticated(path) + + def invoice_update(self, invoice_id, description=None, custom=None): + """ + Update the description and custom data of an invoice + + See https://www.coinify.com/docs/api/#update-an-invoice + """ + params = {} + + if description is not None: + params['description'] = description + if custom is not None: + params['custom'] = custom + + path = '/v3/invoices/%d' % (invoice_id,) + + return self.call_api_authenticated(path, 'PUT', params) + + def invoice_input_create(self, invoice_id, currency, return_address): + """ + Request for an invoice to be paid with another input currency. + + See https://www.coinify.com/docs/api/#pay-with-another-input-currency + """ + params = { + 'currency': currency, + 'return_address': return_address + } + + path = '/v3/invoices/%d/inputs' % (invoice_id,) + + return self.call_api_authenticated(path, 'POST', params) + + def buy_orders_list(self, limit=None, offset=None, include_cancelled=None): + """ + Returns an array of your Coinify buy orders + + See https://www.coinify.com/docs/api/#list-all-buy-orders + """ + + query_params = {} + + if limit is not None: + query_params['limit'] = limit + if offset is not None: + query_params['offset'] = offset + if include_cancelled is not None: + query_params['include_cancelled'] = include_cancelled + + return self.call_api_authenticated('/v3/buys', query_params=query_params) + + def buy_order_create(self, amount, currency, btc_address, instant_order=None, + callback_url=None, callback_email=None): + """ + Create a new buy order + + See https://www.coinify.com/docs/api/#create-a-buy-order + """ + params = { + 'amount': amount, + 'currency': currency, + 'btc_address': btc_address + } + + if instant_order is not None: + params['instant_order'] = instant_order + if callback_url is not None: + params['callback_url'] = callback_url + if callback_email is not None: + params['callback_email'] = callback_email + + return self.call_api_authenticated('/v3/buys', 'POST', params) + + def buy_order_confirm(self, buy_order_id): + """ + Confirm a buy order + + See https://www.coinify.com/docs/api/#buy-order-confirm + """ + path = '/v3/buys/%d/actions/confirm' % (buy_order_id,) + + return self.call_api_authenticated(path, 'PUT') + + def buy_order_get(self, buy_order_id): + """ + Get a specific buy order + + See https://www.coinify.com/docs/api/#get-a-specific-buy-order + """ + path = '/v3/buys/%d' % (buy_order_id,) + + return self.call_api_authenticated(path) + + def rates_get(self, currency=None): + """ + Return buy and sell rates for all available currencies or for the specified currency. + :param self: + :param currency|None: A 3-char currency code + :return: + """ + if currency is None: + path = '/v3/rates' + else: + path = '/v3/rates/%s' % (currency,) + + return self.call_api_authenticated(path) + + def balance_get(self): + """ + Get the balance of a merchant + :param path: + :return: An array as described in https://www.coinify.com/docs/api/#check-account-balance . If success, + then the 'data' value contains the balance in BTC and fiat currency and also the base currency + of the merchant that requests it. + """ + path = '/v3/balance' + return self.call_api_authenticated(path) + + def input_currencies_list(self): + """ + Receive a list of supported input currencies + """ + + return self.call_api('/v3/input-currencies') + + def call_api_authenticated(self, path, method='GET', params={}, query_params={}): + """ + Perform an authenticated API call, using the + API key and secret provided in the constructor. + + path: API path, WITH a leading slash, e.g. '/v3/invoices' + params: dict with parameters to send with the API call + + Returns a dict as described in https://www.coinify.com/docs/api/#response-format, + or None if the HTTP call couldn't be performed correctly. + """ + extra_headers = { + 'Authorization': self.generate_authorization_header() + } + + return self.call_api(path, method, params, query_params, extra_headers) + + def call_api(self, path, method='GET', params={}, query_params={}, headers={}): + """ + Perform an API call + + path: API path, WITH a leading slash, e.g. '/v3/invoices' + params: dict with parameters to send with the API call + + Returns a dict as described in https://www.coinify.com/docs/api/#response-format, + or None if the HTTP call couldn't be performed correctly. + """ + url = self.api_base_url + path + + headers['Content-Type'] = 'application/json' + + if self.api_key is not None: + headers['Authorization'] = self.generate_authorization_header() + + r = requests.request(method, url, json=params, headers=headers, params=query_params) + return r.json() + + def generate_authorization_header(self): + """ + Generate a nonce and a signature for an API call + and wrap those in a HTTP header + """ + # Generate nonce, based on the current Unix timestamp + nonce = str(int(time.time() * 1000000)) + + # Concatenate nonce and API key + message = nonce + self.api_key + + # Compute signature + signature = hmac.new(self.api_secret, msg=message, digestmod=hashlib.sha256).hexdigest() + + # Construct the header value + header_value = 'Coinify apikey="%s", nonce="%s", signature="%s"' % (self.api_key, nonce, signature) + + return header_value + diff --git a/vendor/coinify_callback.py b/vendor/coinify_callback.py new file mode 100644 index 00000000..d55e9dba --- /dev/null +++ b/vendor/coinify_callback.py @@ -0,0 +1,23 @@ +import hashlib, hmac + +class CoinifyCallback: + """ + Class to validate callbacks from Coinfy + """ + + ipn_secret = None + """Coinify IPN callback secret. Get yours at https://www.coinify.com/merchant/ipn""" + + def __init__( self, ipn_secret ): + self.ipn_secret = ipn_secret + + def validate_callback( self, callback_raw, signature ): + """ + Validates a callback and it's signature based on the IPN secret given in the constructor. + + callback_raw must contain the raw JSON POST data sent with the callback (before any JSON decoding) + signature must contain the signature as extracted from the 'X-Coinify-Callback-Signature' header, + which should be a 64-byte hexadecimal string + """ + return signature == hmac.new(self.ipn_secret, msg=callback_raw, digestmod=hashlib.sha256).hexdigest() +