Skip to content

Crypto Exchange

Enda Kelly edited this page Jan 7, 2022 · 24 revisions

Welcome to the CryptoExchange wiki! ₿

📕 Table of Contents

Django

Web Pages (incl. static files)

Coinbase

Heroku

Django

  • The application should be registered in settings.p ⚙️
# core/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'payments.apps.PaymentsConfig'
]
  • A function view called home_view 🏠 was is added to views.py
# payments/views.py

from django.shortcuts import render

def home_view(request):
    return render(request, 'home.html', {})
  • Create a file inside the payments app named urls.py
# payments/urls.py

from django.urls import path

from . import views

urlpatterns = [
    path('', views.home_view, name='payments-home'),
]
  • The project-level URLs file with the payments app should also be updated
# core/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('', include('payments.urls')),
    path('admin/', admin.site.urls)
]

Web Pages 📄

Home

  • A web front should be created. This can be achieved by a HTML homepage in the templates folder
(ecrypto)$ mkdir templates
(ecrypto)$ touch templates/home.html

Below is the example HTML that is used to achieve the web front end homepage:

<!-- templates/home.html -->

{% load static %}
<html lang="en">
  <head>
    <title>Django + Coinbase Pay</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" crossorigin="anonymous">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.min.js" crossorigin="anonymous"></script>
  </head>
  <body>
    <div class="container mt-5">
      <div class="card w-25">
        <div class="card-body">
          <h3 class="card-title">{{ charge.name }}</h3>
          <img src="static/img/whiskey_1.jpeg" alt="the finest" width="250" height="200">
          <p class="card-text">
            <span>{{ charge.description }}</span>
            <br>
            <span>€{{ charge.pricing.local.amount }} {{ charge.pricing.local.currency }}</span>
          </p>
          <div>
            <a class="btn btn-primary w-100" href="{{ charge.hosted_url }}">Purchase<a>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>
  • At this point Django will not know about this newly created templates folder. As such, this needs to be updated via the settings.py
# core/settings.py

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['templates'],
        ......

Redirections

  • For more completeness, two additional pages were created to support web page redirects. This is to support redirect_url and cancel_url which can be noted in the views.py
# payments/views.py

def success_view(request):
    return render(request, 'success.html', {})


def cancel_view(request):
    return render(request, 'cancel.html', {})
  • Each view has to be registered inside urls.py
# payments/urls.py

from django.urls import path

from . import views

urlpatterns = [
    path("", views.home_view, name="payments-home"),
    path("success/", views.success_view, name="payments-success"),
    path("cancel/", views.cancel_view, name="payments-cancel"),
    path("webhook/", views.coinbase_webhook),
    path('ping/', views.ping, name="ping"),
]

Success ✅

Below is the code snippet used for the success page:

{% load static %}
<html lang="en">
  <head>
    <title>E-Crypto: Purchase</title>
    <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400" rel="stylesheet">
    <!-- <link rel="stylesheet" type="text/html" href="{% static 'css/base.css' %}" crossorigin="anonymous"> -->
    <link rel="stylesheet" type="text/css" href='../static/css/base.css'>
  </head>
  <body>
    <div class="box"> 
      <div class="success alert">
        <div class="alert-body">
          <p>Your payment has been successful</p>
          <a href="/">Continue Shopping</a>
        </div>
      </div>
    </div>
  </body>
</html>

Cancel ⛔️

Below is the code snippet used in the cancel page:

{% load static %}
<html lang="en">
  <head>
    <title>E-Crypto: Cancel</title>
    <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400" rel="stylesheet">
    <!-- <link rel="stylesheet" type="text/html" href="{% static 'css/base.css' %}" crossorigin="anonymous"> -->
    <link rel="stylesheet" type="text/css" href='../static/css/base.css'>
  </head>
  <body>
    <div class="box"> 
      <div class="error alert">
        <div class="alert-body">
          <p>Your payment has been cancelled!</p>
          <a href="/">Continue Shopping</a>
        </div>
      </div>
    </div>
  </body>
</html>
  • Noting that custom static files are used in both success and cancel web pages. As such, serving these file was done using Django specific logic via {% load static %}. This is referenced in settings.py
  • For further details on managing static files in Django please review: Django: Static How to documentation

Static Files

  • The python package WhiteNoise was used to manage static assets and files (images and custom css)

Please note the code section below, detailing where this is initialised:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
STATICFILES_STORAGE = "whitenoise.storage.CompressedStaticFilesStorage"

Coinbase Integration

API Key 🔐

  • Coinbase should be added via pip
(ecrypto)$ pip install coinbase-commerce==1.0.1
  • The API key that is generated via the Coinbase Commerce account should be added to a local file named config.py

Generate API key

For local development, when using Docker compose, an env file was created in the project root

touch /app/env.dev

Below is an example of the env.dev file and it contents.

SECRET_KEY='foo'
DEBUG=False
COINBASE_COMMERCE_API_KEY = 'eeb11339-****-****-****-************'

Note, this is a minimum requirement for the app to function correctly

from core import config

COINBASE_COMMERCE_API_KEY = os.environ.get('COINBASE_API_KEY')

Coinbase Charges

home_view is updated in views.py

# payments/views.py

from coinbase_commerce.client import Client
from django.shortcuts import render

from core import settings

def home_view(request):
    logger = logging.getLogger(__name__)

    client = Client(api_key=settings.COINBASE_COMMERCE_API_KEY)
    domain_url = "http://localhost:8000/"

    hostname = request.get_host().split(":", 1)[0]
    if hostname in ("testserver", "localhost", "127.0.0.1"):
        domain_url = "http://localhost:8000/"
    else:
        domain_url = f"https://{hostname}/"

    product = {
        "name": "₿ig (Ξ)'s Whiskey",
        "description": "Sweet sweet nectar",
        "local_price": {"amount": "5.00", "currency": "EUR"},
        "pricing_type": "fixed_price",
        "redirect_url": domain_url + "success/",
        "cancel_url": domain_url + "cancel/",
        "metadata": {
            "customer_id": request.user.id if request.user.is_authenticated else None,
            "customer_username": request.user.username
            if request.user.is_authenticated
            else None,
        },
    }
    charge = client.charge.create(**product)

    return render(request, "home.html", {"charge": charge,})
  • The client is first initialised by passing COINBASE_COMMERCE_API_KEY to it.
  • A JSON object is created which represents the product (we provided the name, description, etc.). Including redirect_url and cancel_url to the charge.
  • The JSON object is unpacked and then used the client to create a charge.
  • Finally, it is passed to the home template as context.

Heroku

This application was published to Heroku for proof of concept purposes. As such, if someone wants to make a fork of this repository for their own purposes. Please note the below Heroku & Docker commands that might be of assistance for deployment:

Heroku: Set up

  • Create an app (i.e. dyno)
$ heroku create
Creating app... done, ⬢ fierce-aholl-45578
https://fierce-aholl-45578.herokuapp.com/ | https://git.heroku.com/fierce-aholl-45578.git
  • Add environmental variable(s)
$ heroku config:set SECRET_KEY=SOME_SECRET_VALUE -a fierce-aholl-45578

Heroku: Deployment

  • Deployment can be done directly from a local development environment. Below examples of how to achieve this:
$ docker build -t registry.heroku.com/fierce-aholl-45578/web .

$ docker push registry.heroku.com/fierce-aholl-45578/web

$ heroku container:release -a fierce-aholl-45578 web