Initial Addons

This commit is contained in:
vandekul
2020-04-10 23:04:44 +02:00
commit 169b913804
26 changed files with 1764 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
import main
#import merchant_facade
#import crypto
+162
View File
@@ -0,0 +1,162 @@
"""btcpay.crypto
These are various crytography related utility functions borrowed from:
bitpay-python: https://github.com/bitpay/bitpay-python
"""
import re
import json
import urllib
import urlparse
import logging
import pprint
#from urllib.parse import urlencode
import requests
from requests.exceptions import HTTPError
from . import crypto
_logger = logging.getLogger(__name__)
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, params=None, token=None):
#token = token or list(self.tokens.values())[0]
token = self.tokens
params = params or dict()
params['token'] = token
uri = self.host + path
#payload = '?' + urlencode(params)
payload ="?token=" + params['token']
headers = self._create_signed_headers(uri, payload)
r = self.s.get(uri, params=params, 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]
token = self.tokens
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)
if not r.ok:
if 400 <= r.status_code < 500:
http_error_msg = u'%s Client Error: \
%s for url: %s | body: %s' % (
r.status_code,
r.reason,
r.url,
r.text
)
elif 500 <= r.status_code < 600:
http_error_msg = u'%s Server Error: \
%s for url: %s | body: %s' % (
r.status_code,
r.reason,
r.url,
r.text
)
if http_error_msg:
raise HTTPError(http_error_msg, response=r)
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, data=payload)
else:
r = self.s.get(uri)
r.raise_for_status()
return r.json()['data']
def get_rates(self, crypto='BTC', store_id=None):
params = dict(
cryptoCode=crypto
)
if store_id:
params['storeID'] = store_id
return self._signed_get_request('/rates/', params=params)
def get_rate(self, currency, crypto='BTC', store_id=None):
rates = self.get_rates(crypto=crypto, store_id=store_id)
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')
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 get_invoices(self, status=None, order_id=None, item_code=None, date_start=None, date_end=None, limit=None, offset=None, token=None):
params = dict()
if status is not None:
params['status'] = status
if order_id is not None:
params['orderId'] = order_id
if item_code is not None:
params['itemCode'] = item_code
if date_start is not None:
params['dateStart'] = date_start
if date_end is not None:
params['dateEnd'] = date_end
if limit is not None:
params['limit'] = limit
if offset is not None:
params['offset'] = offset
return self._signed_get_request('/invoices', params=params, 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}
data = self._unsigned_request('/tokens', payload)
data = data[0]
return {
data['facade']: data['token']
}
@classmethod
def create_client(cls, code, host):
pem = crypto.generate_privkey()
client = BTCPayClient(host=host, pem=pem)
token = client.pair_client(code)
return BTCPayClient(host=host, pem=pem, tokens=token)
def __repr__(self):
return '{}({})'.format(
type(self).__name__,
self.host
)
+86
View File
@@ -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
+110
View File
@@ -0,0 +1,110 @@
#******************************************************************************
# PAYMENT BTCPAY FOR ODOO
#
# Copyright (C) 2020 Susanna Fort <susannafm@gmail.com>
#
#******************************************************************************
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of
# the License, or any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# For a full copy of the GNU General Public License see the LICENSE.txt file.
#
#******************************************************************************
import json
import logging
import pprint
import requests
import werkzeug
import client
import crypto as bku
from client import BTCPayClient
import urllib2,cookielib
from openerp import api, fields, models, _
from openerp.osv import osv
from openerp import http, SUPERUSER_ID
from openerp.addons.payment.models.payment_acquirer import ValidationError
from openerp.http import request
_logger = logging.getLogger(__name__)
class BtcpayController(http.Controller):
_notify_url = '/payment/btcpay/ipn'
@http.route('/payment/btcpay/ipn', type='json', auth='none')
def btcpay_ipn(self, **post):
""" BTCPay IPN. """
cr, uid, context, env = request.cr, SUPERUSER_ID, request.context, request.env
acquirer = request.env['payment.acquirer'].search([('provider', '=', 'btcpay')])
#_logger.info('REQUEST JSONREQUEST %s',pprint.pformat(request.jsonrequest))
invoiceId = request.jsonrequest['data']['id']
client = BTCPayClient(host=acquirer.location, pem=acquirer.privateKey, tokens=acquirer.token)
self.invoice = client.get_invoice(invoiceId)
#_logger.info('SELF INVOICE IPN %s',pprint.pformat(self.invoice))
tx = None
if self.invoice['orderId']:
tx_ids = request.registry['payment.transaction'].search(cr, uid, [('reference', '=', self.invoice['orderId'])], context=context)
if tx_ids:
tx = request.registry['payment.transaction'].browse(cr, uid, tx_ids[0], context=context)
tx.btcpay_status = self.invoice['status']
if self.invoice['status'] in ['confirmed']:
tx.state = 'done'
tx.sale_order_id.state = 'sale'
if not tx.btcpay_buyerMailNotification and acquirer.buyerNotification:
tx.sale_order_id.force_quotation_send()
tx.btcpay_buyerMailNotification = "Send"
tx.sale_order_id.order_line._action_procurement_create()
elif self.invoice['status'] in ['paid']:
tx.state = 'pending'
tx.btcpay_invoiceId =self.invoice['id']
tx.btcpay_txid =((((self.invoice['cryptoInfo'])[0])['payments'])[0])['id']
return ''
@http.route(['/btcpay/checkout'], type='http', auth='none', csrf=None, website=True)
def checkout(self, **post):
cr, uid, context, env = request.cr, SUPERUSER_ID, request.context, request.env
acquirer = env['payment.acquirer'].search([('provider', '=', 'btcpay')])
currency = env['res.currency'].browse(eval(post.get('currency_id'))).name
base_url = request.env['ir.config_parameter'].get_param('web.base.url')
return_url = base_url + self._notify_url
client = BTCPayClient(host=acquirer.location, pem=acquirer.privateKey, tokens=acquirer.token)
acquirer.invoice = client.create_invoice(
{"price": post.get('amount'),
"currency": currency,
"orderId": post.get('reference'),
"token": acquirer.token,
"redirectURL": acquirer.confirmationURL,
"notificationURL": return_url,
"extendedNotifications": True,
"buyer": { "email": post.get('email'),
"name": post.get('name'),
"address1": post.get('street'),
"locality": post.get('city'),
"postalCode": post.get('zip'),
"country": post.get('country'),
"notify": False}})
invoiceId = dict(acquirer.invoice)['id']
self.invoice = client.get_invoice(invoiceId)
return werkzeug.utils.redirect(self.invoice['url'])