Skip to content
This repository has been archived by the owner on Jun 1, 2019. It is now read-only.

Commit

Permalink
Add CoinPayments integration
Browse files Browse the repository at this point in the history
  • Loading branch information
r4victor committed Nov 11, 2018
1 parent 83a4af3 commit 9898c6c
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 0 deletions.
2 changes: 2 additions & 0 deletions app/__init__.py
Expand Up @@ -5,6 +5,7 @@

from .integrations.bitpay.views import bp as bitpay_bp
from .integrations.CoinGate.views import bp as CoinGate_bp
from .integrations.CoinPayments.views import bp as CoinPayments_bp


def create_app(test_config=None):
Expand All @@ -24,5 +25,6 @@ def create_app(test_config=None):

app.register_blueprint(bitpay_bp)
app.register_blueprint(CoinGate_bp)
app.register_blueprint(CoinPayments_bp)

return app
Empty file.
80 changes: 80 additions & 0 deletions app/integrations/CoinPayments/api.py
@@ -0,0 +1,80 @@
import os
import requests
import hmac

from flask import request, abort

from .db import db


PUBLIC_KEY = os.getenv('COINPAYMENTS_PUBLIC_KEY')
if PUBLIC_KEY is None:
raise ValueError('COINPAYMENTS_PUBLIC_KEY is not set')


PRIVATE_KEY = os.getenv('COINPAYMENTS_PRIVATE_KEY')
if PRIVATE_KEY is None:
raise ValueError('COINPAYMENTS_PRIVATE_KEY is not set')


IPN_SECRET = os.getenv('COINPAYMENTS_IPN_SECRET')
if IPN_SECRET is None:
raise ValueError('COINPAYMENTS_IPN_SECRET is not set')


API_ENDPOINT = 'https://www.coinpayments.net/api.php'


def create_invoice(order, callback_url):
data = {
'version': 1,
'key': PUBLIC_KEY,
'cmd': 'create_transaction',
'amount': order['item']['price'].decimal_repr(),
'currency1': order['item']['price'].currency.code,
'currency2': 'LTCT',
'buyer_email': order['customer']['email'],
'buyer_name': f'{order["customer"]["first_name"]} {order["customer"]["last_name"]}',
'item_name': order['item']['name'],
'item_number': order['item']['code'],
'ipn_url': callback_url
}
prepped_request = requests.Request('POST', API_ENDPOINT, data=data).prepare()
signature = hmac.digest(PRIVATE_KEY.encode(), prepped_request.body.encode(), 'sha512').hex()
prepped_request.headers['HMAC'] = signature
with requests.Session() as session:
response = session.send(prepped_request)

if response.status_code is not 200:
return None

response_data = response.json()
if response_data['error'] != 'ok':
return None

invoice = {
'id': response_data['result']['txn_id'],
'status': 0,
'status_text': 'new',
'amount': response_data['result']['amount'],
'address': response_data['result']['address'],
'url': response_data['result']['status_url'],
'qrcode_url': response_data['result']['qrcode_url'],
}
db.create_invoice(invoice)
return invoice


def handle_callback():
provided_signature = request.headers.get('Hmac')
expecetd_signarure = hmac.digest(IPN_SECRET.encode(), request.get_data(), 'sha512').hex()
if provided_signature != expecetd_signarure:
abort(401)

data = request.form
db.update_invoice(
data['txn_id'],
status=data['status'],
status_text=data['status_text']
)
return 'Thank you, CoinPayments, for secure API!'
19 changes: 19 additions & 0 deletions app/integrations/CoinPayments/db.py
@@ -0,0 +1,19 @@
from app.db import Database


class ExtendedDatabase(Database):
def get_invoice(self, invoice_id):
if not self.db_connection.exists(f'{self.processor}:invoice:{invoice_id}'):
return None
return {
'id': invoice_id,
'status': int(self.db_connection.get(f'{self.processor}:invoice:{invoice_id}:status').decode()),
'status_text': self.db_connection.get(f'{self.processor}:invoice:{invoice_id}:status_text').decode(),
'amount': self.db_connection.get(f'{self.processor}:invoice:{invoice_id}:amount').decode(),
'address': self.db_connection.get(f'{self.processor}:invoice:{invoice_id}:address').decode(),
'url': self.db_connection.get(f'{self.processor}:invoice:{invoice_id}:url').decode(),
'qrcode_url': self.db_connection.get(f'{self.processor}:invoice:{invoice_id}:qrcode_url').decode()
}


db = ExtendedDatabase('CoinPayments')
16 changes: 16 additions & 0 deletions app/integrations/CoinPayments/templates/CoinPayments.html
@@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% block content %}
<div class="example-block store">
<h2>Store</h2>
<div class="content">
<div>
<img src="{{ url_for('static', filename='images/items/pipe.jpg') }}">
</div>
<div>
<h3>Tobacco Pipe</h3>
<span class="price">$15</span>
<a href="{{ url_for('.checkout') }}" class="button-fill">Buy</a>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1 @@
{% extends 'checkout.html' %}
34 changes: 34 additions & 0 deletions app/integrations/CoinPayments/templates/CoinPayments_order.html
@@ -0,0 +1,34 @@
{% extends 'order.html' %}
{% block order_info %}
<div class="info-item">
<span>Order number:</span>
<span>{{ order.id }}</span>
</div>
<div class="info-item">
<span>Status:</span>
<span>{{ invoice.status_text }}</span>
</div>
{% endblock %}
{% block payment %}
{% if invoice.status >= 0 and invoice.status < 100 %}
<h3>Payment details</h3>
<div class="payment-details">
<div class="info-item">
<span>Amount:</span>
<span>{{ invoice.amount }} LTCT</span>
</div>
<div class="info-item">
<span>Address to send:</span>
<span>{{ invoice.address }}</span>
</div>
<div class="info-item">
<img src="{{ invoice.qrcode_url }}" class="qr-code">
</div>
</div>
{% endif %}
<div class="pay-button">
<a class="button-fill" href="{{ invoice.url }}" target="_blank">
See payment status
</a>
</div>
{% endblock %}
74 changes: 74 additions & 0 deletions app/integrations/CoinPayments/views.py
@@ -0,0 +1,74 @@
from flask import Blueprint, render_template, redirect, url_for, abort, request

from app.data import customers, items
from .db import db
from . import api


bp = Blueprint(
'CoinPayments',
__name__,
url_prefix='/coinpayments',
static_folder='static',
template_folder='templates'
)


processor = {
'name': 'CoinPayments',
'github_path': 'CoinPayments',
'website_url': 'https://www.coinpayments.net'
}


@bp.route('/')
def page():
return render_template(
'CoinPayments.html',
processor=processor,
item1=items['pipe']
)


@bp.route('/checkout/')
def checkout():
return render_template(
'CoinPayments_checkout.html',
processor=processor,
item=items['pipe'],
customer=customers['Holmes']
)


@bp.route('/new_order/', methods=['POST'])
def new_order():
new_order = {
'id': db.get_next_order_id(),
'item': items['pipe'],
'customer': customers['Holmes']
}
db.create_order(new_order)
invoice = api.create_invoice(
new_order,
url_for('.callback', _external=True)
)
db.update_order(new_order['id'], invoice_id=invoice['id'])
return redirect(url_for('.order', order_id=new_order['id']))


@bp.route('/order/<int:order_id>/')
def order(order_id):
order = db.get_order(order_id)
if order is None:
abort(404)
invoice = db.get_invoice(order['invoice_id'])
return render_template(
'CoinPayments_order.html',
processor=processor,
item=items['pipe'],
order=order,
invoice=invoice
)


bp.add_url_rule('/callback/', 'callback', api.handle_callback, methods=['POST'])

0 comments on commit 9898c6c

Please sign in to comment.