ジャンゴとワレットアフリカAPIで財布システムを構築すること



概要
Django迅速なWeb開発のためのPythonフレームワークです.これは、Web開発の面倒の多くの世話をするので、ホイールを再発明することなく、あなたのアプリケーションを書くことに集中することができます.Wallets Africaアフリカとアフリカの所有している企業は、お金を送って、お金を受けて、カード支払いとアクセスローンを作るのを助けます.
今日のアプリケーションの良い数は、ユーザーが電気、チケットやお金を転送するようなサービスを支払うことができるようにデジタル財布を使用します.ウォレットアフリカAPIは、開発者がユーザーの財布を管理し、ユーザーがお客様のアプリケーションでお金を受け取ると引き金を有効にすることが容易になります.

始める
プロジェクトの仮想環境を作成して活性化しましょう.仮想環境は、我々のプロジェクト依存関係を孤立させ続けるのを助けます.
MacOSの
python -m venv env
source env/bin/activate
Windows
python -m venv env
env\scripts\activate
仮想環境(env)の名前をあなたのターミナル行のブラケットに表示する必要があります.
次に、djangoをインストールし、Djangoプロジェクトを作成します
pip install django
django-admin startproject django_wallets
そして、あなたのディレクトリをプロジェクトフォルダに変えてください
cd django_wallets
我々はこのプロジェクトで2つのアプリケーションを持っている.
アカウントのアプリは、ユーザー認証と認証を処理するために、それぞれのユーザーの預金や引き出しを処理するために財布のアプリを処理します.
我々のアカウントと財布アプリケーションを作成しましょう:
python manage.py startapp accounts && python manage.py startapp wallets
これは2つのフォルダを作成します我々のプロジェクト・フォルダの口座と財布.今、我々はプロジェクトで我々のアプリを登録する必要があります.設定ファイルを開き、インストールしたアプリケーションのセクションを見つけます.
# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
新しく作成されたアプリケーションをこれに置き換えて追加します.
# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'accounts.apps.AccountsConfig',
    'wallets.apps.WalletsConfig'
]
今すぐ私たちのアカウントのアプリケーションを構築しましょう.デフォルトでは、django認証中に一意の識別ユーザーにユーザー名を使用します.しかし、このプロジェクトでは、代わりにメールを使用します.これを行うには、Djangoの抽象クラスモデルをサブクラス化することでカスタムユーザーモデルを作成します.まず、マネージャーを作成します.Customuser ManagerのアカウントフォルダのPYファイル
from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import gettext_lazy as _

class CustomUserManager(BaseUserManager):
    def create_user(self, email, password, **extra_fields):
        if not email:
            raise ValueError(_("email address cannot be left empty!"))
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)
        extra_fields.setdefault("is_active", True)
        extra_fields.setdefault("user_type", 'ADMIN')

        if extra_fields.get("is_staff") is not True:
            raise ValueError(_("superuser must set is_staff to True"))
        if extra_fields.get("is_superuser") is not True:
            raise ValueError(_("superuser must set is_superuser to True"))

        return self.create_user(email, password, **extra_fields)

マネージャは、データベースクエリ操作がDjangoモデルに提供されるインターフェイスです.既定では、すべてのDjangoモデルクラスに名前オブジェクトを持つマネージャーを追加します.このカスタムユーザーマネージャをオーバーライドします.このCustomUserManagerは、代わりに電子メールを主な識別子として使用します.
次に、カスタムユーザーモデルを作成します.
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import gettext_lazy as _

from .manager import CustomUserManager

import uuid


class CustomUser(AbstractUser):

    username = None
    uid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    email = models.EmailField(_("email address"), blank=False, unique=True)
    first_name = models.CharField(_("first name"), max_length=150, blank=False)
    last_name = models.CharField(_("last name"), max_length=150, blank=False)
    date_of_birth = models.DateField(_("date of birth"), max_length=150, blank=False)
    verified = models.BooleanField(_("verified"), default=False)


    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

    objects = CustomUserManager()

    def __str__(self):
        return self.email
ここでは、ユーザー名フィールドを削除し、電子メールフィールドを一意にし、ユーザーモデルの一意の識別子を定義するUsernameRankフィールドとして電子メールを設定します.また、UuidCountフィールドを一意の識別子として使用し、PythonのUUIDライブラリを使用して、ランダムなオブジェクトを既定値として生成します.
それから、我々は我々のセッティングにこれを加えます.ので、新しいユーザモデルを認識します.
AUTH_USER_MODEL = "accounts.CustomUser"
それでは、データベースを移行しましょう(このチュートリアルの目的では、デフォルトのSQLiteデータベースを使用します).
python manage.py makemigrations && python manage.py migrate
では、アプリケーションを実行しましょう
python manage.py runserver
ブラウザ上でhttp://127.0.0.1:8000/を開き、以下のイメージと同じように応答します.

登録フォームとログインフォームを作成しましょう.フォームを作成します.アカウントフォルダのPYファイルをコピーします.
from django import forms
from django.forms.widgets import PasswordInput, TextInput, EmailInput, FileInput, NumberInput
from .models import CustomUser


from .models import CustomUser




class UserRegistrationForm(forms.ModelForm):
    password1 = forms.CharField(widget=PasswordInput(attrs={'class':'form-control', 'placeholder':'Password', 'required':'required'}))
    password2 = forms.CharField(widget=PasswordInput(attrs={'class':'form-control', 'placeholder':'Confirm Password', 'required':'required'}))

    class Meta:
        model = CustomUser
        fields = ('first_name','last_name','email','date_of_birth') 
        widgets = {
        'first_name':TextInput(attrs={'class':'form-control', 'placeholder':'First Name', 'required':'required'}),
        'last_name':TextInput(attrs={'class':'form-control', 'placeholder':'Last Name', 'required':'required'}),
        'email': EmailInput(attrs={'class':'form-control', 'placeholder':'Email', 'required':'required'}),
        'date_of_birth': DateInput(attrs={'class':'form-control', 'placeholder':'Date of Birth', 'required':'required','type': 'date'}),
    }

    def clean_password2(self):
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("Passwords don't match")
        return password2

    def save(self, commit=True):
        user = super().save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user

class CustomAuthForm(forms.Form): 
    email = forms.CharField(widget=EmailInput(attrs={'class':'form-control', 'placeholder':'Email', 'required':'required'}))
    password = forms.CharField(widget=PasswordInput(attrs={'class':'form-control','placeholder':'Password', 'required':'required'}))

カスタムフィールドに追加されたカスタムCSSクラスに注目する必要があります.フォームをスタイルにブートストラップを使用します.ブートストラップはCSSのフレームワークを応答、モバイル最初のフロントエンドのWeb開発を監督です.次に、登録ビューを作成します.
from django.shortcuts import render

from .forms import UserCreationForm



def register(request):
    form = UserRegistrationForm(request.POST or None)
    if request.method == 'POST':
        if form.is_valid():
            new_user = form.save()
            return redirect('accounts:register')
    return render(request, "accounts/register.html", context = {"form":form})
プロジェクトディレクトリにテンプレートフォルダを作成しました.HTMLテンプレートはGithub hereからコピーできます.設定に移動します.PYと更新のテンプレートセクション
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
これにより、プロジェクトディレクトリのテンプレートフォルダからテンプレートを読み込むことができます.アプリケーションに登録URLを追加します
from django.urls import path

from .views import register
app_name = "accounts"

urlpatterns = [
    path('register/', register, name="register"),
]

次に、プロジェクトにアカウントアプリURLを含める
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('account/', include('accounts.urls', namespace='accounts'))
]

あなたのブラウザで127.0.0.1 : 8000/account/register/を開きます.

ユーザーが登録できます.では、ログインビューを作成しましょう.
def login_user(request):
    form = CustomAuthForm(request.POST or None)
    if request.method == 'POST':
        if form.is_valid():
            cd = form.cleaned_data
            user = authenticate(request, email = cd['email'], password=cd['password']) 
            if user is not None:
                login(request, user)
                return redirect('accounts:dashboard')
            else:
                messages.error(request, 'Account does not exist')
    return render(request, "accounts/login.html", context = {"form":form})

@login_required
def dashboard(request):
    return render(request, "dashboard.html", context={})
次に、URLを追加します.
urlpatterns = [
    path('register/', register, name="register"),
    path('login/', login_user, name="login"),
    path('', dashboard, name="dashboard"),
]

今、我々のログインと登録ルートは、働いていなければなりません.成功したログインの後、ユーザーはダッシュボードにリダイレクトされなければなりません.CustomUserモデルの検証フィールドが既定でfalseに設定されていることに気づいたかもしれません.ユーザーが彼らのBVNを提供したあと、財布はつくられました、確かめられたフィールドはtrueに変わりました.しかし、その前に、登録経路を更新して、登録後にログインをリダイレクトするようにしましょう.
def register(request):
    form = UserRegistrationForm(request.POST or None)
    if request.method == 'POST':
        if form.is_valid():
            new_user = form.save()
            messages.success(request, 'Account succesfully created. You can now login')
            return redirect('accounts:login')
    return render(request, "accounts/register.html", context = {"form":form})
私たちの財布モデルを作成しましょう:
from django.db import models, transaction
from django.utils.translation import gettext_lazy as _
from accounts.models import CustomUser

import uuid

class Wallet(models.Model):
    uid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.OneToOneField(CustomUser, on_delete=models.SET_NULL, null=True)
    balance = models.DecimalField(_("balance"), max_digits=100, decimal_places=2)
    account_name = models.CharField(_("account name"), max_length=250)
    account_number = models.CharField(_("account number"), max_length=100)
    bank = models.CharField(_("bank"), max_length=100)
    phone_number = models.CharField(_("phone number"), max_length=15)
    password = models.CharField(_("password"), max_length=200)
    created = models.DateTimeField(auto_now_add=True)

次に、アプリケーションの移行を実行します.ユーザーフィールドのOnClose削除はnullに設定されます.なぜなら、ユーザーアカウントが削除された後でも、削除したくないからです.ユーザーは、彼が確認された後に財布を持つことができます.さあ、ウォルクレヒ創造のフォームとビューを作成しましょう.
フォーム
class BVNForm(forms.Form): 
    bvn = forms.CharField(widget=NumberInput(attrs={'class':'form-control', 'placeholder':'Your BVN', 'required':'required'}))

ビュー
from wallets.api import WalletsClient
from wallets.models import Wallet

from cryptography.fernet import Fernet



wallet = WalletsClient(secret_key="hfucj5jatq8h", public_key="uvjqzm5xl6bw")
fernet = Fernet(settings.ENCRYPTION_KEY)


@login_required
def create_wallet(request):
    form = BVNForm(request.POST or None)
    if request.method == 'POST':
        if form.is_valid():
            cd = form.cleaned_data
            user = request.user
            bvn = cd["bvn"]
            new_wallet = wallet.create_user_wallet(
                    first_name= user.first_name,
                    last_name= user.last_name,
                    email=user.email,
                    date_of_birth= user.date_of_birth.strftime('%Y-%m-%d'),
                    bvn= str(bvn)
                )
            if new_wallet["response"]["responseCode"] == '200':
                user.verified = True
                user.save()
                Wallet.objects.create(
                    user = user,
                    balance = new_wallet["data"]["availableBalance"],
                    account_name = new_wallet["data"]["accountName"],
                    account_number = new_wallet["data"]["accountNumber"],
                    bank = new_wallet["data"]["bank"],
                    phone_number = new_wallet["data"]["phoneNumber"],
                    password = fernet.encrypt(new_wallet["data"]["password"].encode())
                )
                messages.success(request, "Account verified, wallet successfully created")
                return redirect("accounts:dashboard")
            else:
                messages.error(request, new_wallet["response"]["message"])

    return render(request, "accounts/bvn.html", context = {"form":form})
私はウォレットアフリカAPIのためのシンプルなAPIラッパーを書いている、あなたはGithubでそれをチェックアウトすることができます.このチュートリアルの目的のために、我々は、テストキーとトークンを使用してアフリカによって提供される、あなたはあなたの秘密と公開キーの生産のためのウォレットアフリカアカウントを作成する必要があります:

CreateMountウォレットビューは、BVNを受け取り、APIを使用して財布を作成し、データベースに財布の詳細を保存します.暗号化パッケージを使用してデータベースに保存する前に財布のパスワードを暗号化しました.設定に暗号キーを追加します.また、暗号パッケージで暗号鍵を生成することもできます.
from cryptography.fernet import Fernet

key = Fernet.generate_key()
ENCRYPTION_KEY = key
では、確認されていないユーザーがダッシュボードにアクセスしないようにする許可を追加しましょう.デコレータを作成します.アカウントフォルダのPyファイル
from functools import wraps
from django.shortcuts import redirect
from django.contrib import messages

def verified(function):
  @wraps(function)
  def wrap(request, *args, **kwargs):

        if request.user.verified:
             return function(request, *args, **kwargs)
        else:
            messages.error(request, "Your account hasn't been verified")
            return redirect("accounts:verify")

  return wrap
アカウントが検証されていない場合、ユーザーは検証ページにリダイレクトするカスタムデコレータです.カスタムデコレータをダッシュボードビューに追加できます.
from .decorators import verified

@login_required
@verified
def dashboard(request):
    wallet = get_object_or_404(Wallet, user=request.user)
    return render(request, "dashboard.html", context={"wallet":wallet})
私もダッシュボードにレンダリングするユーザーの財布を追加しました.あなたのブラウザで127.0.0.1 : 8000/アカウントを参照してください.ダッシュボードページは次のようになります.

ログアウトビューを追加します.
@login_required
def logout_user(request):
    logout(request)
    return redirect("accounts:login")
次に、ログアウトURLを追加します.
urlpatterns = [
    ...
    path('logout/', logout_user, name="logout"),

]

現在、ユーザーは彼らの財布にリンクされた口座への銀行振替をすることによって、彼らの財布に資金を供給することができます.我々は、転送が成功するとすぐに彼らの財布のバランスを更新する必要があります.これはwebhooksを通して行うことができます.Webhookはペイロードが第三者のサービス(このケースでは、Walletアフリカ)から特定のトランザクションアクションが各財布に発生するたびに送信されるサーバー上のURLです.まず、各トランザクションを保存するWalletTransactionモデルを作成します.
class WalletTransaction(models.Model):
    class STATUS(models.TextChoices):
        PENDING = 'pending', _('Pending')
        SUCCESS = 'success', _('Success')
        FAIL = 'fail', _('Fail')

    class TransactionType(models.TextChoices):
        BANK_TRANSFER_FUNDING = 'funding', _('Bank Transfer Funding')
        BANK_TRANSFER_PAYOUT = 'payout', _('Bank Transfer Payout')
        DEBIT_USER_WALLET = 'debit user wallet', _('Debit User Wallet')
        CREDIT_USER_WALLET = 'credit user wallet', _('Credit User Wallet')

    transaction_id = models.CharField(_("transaction id"), max_length=250)
    status = models.CharField(max_length=200, null=True, 
        choices=STATUS.choices, 
        default=STATUS.PENDING
    )
    transaction_type = models.CharField(max_length=200, null=True,
        choices=TransactionType.choices
        )
    wallet = models.ForeignKey(Wallet, on_delete=models.SET_NULL, 
        null=True
    )
    amount = models.DecimalField(_("amount"), max_digits=100, decimal_places=2)
    date = models.CharField(_("date"), max_length=200)

ペイロードの不確実なデータ型のため、文字列データで日付を保存します.
次に、Webhookを消費するビューを作成します.
from django.db import transaction
from django.http import HttpResponse, HttpResponseForbidden
from django.shortcuts import render, get_object_or_404
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST

from ipaddress import ip_address, ip_network
import json

from .models import Wallet, WalletTransaction


@csrf_exempt
@require_POST
def webhook(request):
    whitelist_ip = "18.158.59.198"
    forwarded_for = u'{}'.format(request.META.get('HTTP_X_FORWARDED_FOR'))
    client_ip_address = ip_address(forwarded_for)

    if client_ip_address != ip_network(whitelist_ip):
        return HttpResponseForbidden('Permission denied.')

    payload = json.loads(request.body)

    if payload['EventType'] == "BankTransferFunding":
        wallet = get_object_or_404(Wallet, phone_number = payload["phoneNumber"])
        wallet.balance += payload["amount"]
        wallet.save()
        transaction =  WalletTransaction.objects.create(
            transaction_id = payload["transactionRef"],
            transaction_type = "funding",
            wallet = wallet,
            status = "success",
            amount = payload["amount"],
            date = payload["DateCredited"]
        )
    else:
        pass
    return HttpResponse(status=200)


このビューは、Webhookが信頼されたIPアドレス(すべてのウォレットアフリカWebhookは、ホストIPから来る:18.158.59.198)からの場合、財布のバランスを更新し、また、財布のトランザクションを作成するチェックします.Webhookを我々のアプリURLに加えましょう:
from django.urls import path

from .views import webhook

urlpatterns = [
    path(
        "webhooks/wallets_africa/aDshFhJjmIalgxCmXSj/",
         webhook,
         name = "webhook"
    ),
]
私たちは少しのセキュリティのためにURLにランダムなストリングを加えました.そして、あなたの財布アフリカ・ダッシュボードにWebhook URLを加えます:

我々の財布のアプリは今準備ができています:


結論
Dangangoとウォレットアフリカを統合することにより、我々は、ユーザーが銀行の移動を行うことによって、彼らのデジタル財布に資金を供給することができる財布のアプリケーションを構築しました.また、我々は認証のためのユーザー名ではなく、電子メールを使用することができますDjangoのカスタムユーザーマネージャー機能を介して行った.
ソースコードはGithubにあります.
何か質問があれば、躊躇しないでください.