Contributor

Thanks to our contributor for this guide, Santos Hernandez.

Introduction

Welcome to a walkthrough of how to build your first pay lightning address web app! This walkthrough assumes you have some python programming skills and understand basic backend development.

Now, let’s get started!

Getting a Code Editor

Setting up a web app is no trivial feat. You’ll want to use a code editor to properly setup this project.

I use VS Code and that’s what I’ll be using for the remainder of this course. Download it here.

Setting up Django

You’ll need python installed. You can download python here and run the installer.

If you’ve never used Django before, check out the documentation here.

Open VS Code, our code editor, open a folder where you want to store the project and then select Yes, I trust the authors.


Let’s go ahead and create our own virtual environment. Close the welcome screen now and start a new terminal.


Now, go ahead and create a virtual environment. This ensures that all the libraries and packages that you install are only related to this specific app. It’ll also help if you share the code, like I am.

python3 -m venv env

Now let’s go ahead and activate the virtual env.

source ./env/bin/activate

You’ll now see a prefix of (env) in front of your username in the terminal or command prompt.


Let’s install Django, now that we have our virtual environment setup.

python3 -m pip install Django

Awesome! Now we have Django installed. Let’s go ahead and start a project, which is our top-level structure for Django. If you want to follow a verbose guide on Django setup, check out this page.

Run the following command:

django-admin startproject lightning_app

Navigate to the core project directory by entering cd lightning_app into your terminal.


Run the following command to verify your Django installation.

python3 manage.py runserver

And then in your web browser open the link http://127.0.0.1:8000 and you should see the following site.


w00t! We’ve now setup most of Django. Go ahead and close the webserver by heading back to VS Code and pressing CMD (Ctrl) + C. This will stop the webserver.

Let’s now create an app within the Django project . Run the following command.

python manage.py startapp lightning_address_payment

You’ll now see a new folder created for our app. This is where we’ll house most of the code for the Send Payment to Lightning Address 😀.

Now head to the lightning_app/settings.py file and add 'lightning_address_payment' to the INSTALLED_APPS list.

...
INSTALLED_APPS = [
  'django.contrib.admin',
  'django.contrib.auth',
  'django.contrib.contenttypes',
  'django.contrib.sessions',
  'django.contrib.messages',
  'django.contrib.staticfiles',
  # app added
  'lightning_address_payment'
]
...

Setting up the App Structure

Creating Views

Open up the lightning_app/lightning_address_payment/views.py file.


Let’s import the HttpResponse function and create two new views that we’ll use through-out our project.

index & success . You can do so by replacing everything in the file with the following.

from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.

def index(request):
    return HttpResponse("Hello, world. You're at the Lightning Address Payment home page.")

def success(request):
    return HttpResponse("Hello, world. You're at the Lightning Address Payment success page.")

Now, save the file. We’ll return back to these views later on. This is where we’ll do most of our code.

Creating Routes

Routes are what is used to handle page requests. So for example, when a user tries to go to https://yoursite.com/ it’ll rely upon the index ****View******** that is used to render the route / , or the root route. Another example could be https://yoursite.com/success/ , which is what would render the route success using the ******View********** success in the views.py file.

If it’s a bit fuzzy, that’s okay. It’ll make more sense as we work through the the tutorial. Start by creating a new file in the lightning_address_payment folder called [urls.py](http://urls.py) .


Now paste in the following code. Each item of the urlpatterns points to a function or class in the views.py file located in the lightning_address_payment directory.

from django.urls import path

from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('success/', views.success, name='success'),
]

The next step is to point the root URLconf at the lightning_address_payments**.urls**module. In lightning_apps**/urls.py**, add an import for django.urls.include and insert an [include()](https://docs.djangoproject.com/en/4.1/ref/urls/#django.urls.include) in the urlpatterns list, so you have:

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

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

This imports all the views we’ll be creating in our other urls.py file.

The [include()](https://docs.djangoproject.com/en/4.1/ref/urls/#django.urls.include) function allows referencing other URLconfs. Whenever Django encounters [include()](https://docs.djangoproject.com/en/4.1/ref/urls/#django.urls.include), it chops off whatever part of the URL matched up to that point and sends the remaining string to the included URLconf for further processing.

The idea behind [include()](https://docs.djangoproject.com/en/4.1/ref/urls/#django.urls.include) is to make it easy to plug-and-play URLs. Since lightning_address_payment are in their own URLconf (lightning_address_payment**/urls.py**), they can be placed under “/pay_lightning_address/”, or under “/fun_lightning/”, or under “/content/lightningAddress/”, or any other path root, and the app will still work.

Now let’s go ahead and try this out!

Run the following command in your terminal to start the webserver

python3 manage.py runserver

Let’s head to the following URLs to verify our changes worked.

http://127.0.0.1:8000/

http://127.0.0.1:8000/success/



YAY! It worked. Let’s get back to hacking now. This is where the fun begins.

Writing our First Lightning App!

Creating our Templates

A template is what our views render and show to the user. They can be populated programmatically or hard-coded. This is what we’ll use to take a form input from the user and use it on our backend. They are predominately HTML with some Jinja2 / templating code to make it programmatic mixed in. For the purpose of our template, we’ll only make use of this slightly.

To get started, create a new folder called templates in the lightning_address_payment folder.


Next in the templates folder, create another folder called lightning_address_payment inside the templates folder. We know it’s weird, but that’s just how Django works with the templates.

Let’s start by creating the base HTML file that can be extended to all of our other templates.

Base, Parent, or Skeleton Template

In the lightning_address_payment/templates/lightning_address_payment directory, create a new file called base.html .

Here is the base skeleton template file that we will use to extend to our other templates. It’ll make it faster to develop and populate the skeleton template using our block tags. This will overall, speed up development significantly. This skeleton template uses Bootstrap and a couple of the templating code we previously discussed. You can review the documentation here.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <title>{% block title %}{% endblock %}</title>

    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
      crossorigin="anonymous"
    />
  </head>
  <body>
    {% block content %}{% endblock %}

    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN"
      crossorigin="anonymous"
    ></script>
  </body>
</html>

This template, which we’ll call base.html, defines an HTML skeleton document that you might use for a two-column page. It’s the job of “child” templates to fill the empty blocks with content.

Check out the screenshot to see the folder structure with the file.


💡 In this example, the **`[block](https://docs.djangoproject.com/en/4.1/ref/templates/builtins/#std-templatetag-block)\`\*\* tag defines three blocks that child templates can fill in. All the **`[block](https://docs.djangoproject.com/en/4.1/ref/templates/builtins/#std-templatetag-block)\`\*\* tag does is to tell the template engine that a child template may override those portions of the template.

Now let’s create the first child template.

Index / Home — Child Template

The home child template will need to contain the following requirements.

  • Header
  • A form including:
    • Input field for the Lightning Address
    • Amount input field for the amount of bitcoin we want to send, specified in sats.

It’ll look like the following once we’ve added the title.

{% extends "base.html" %} {% block title %}Home{% endblock %} {% block content %}
<div class="container">
  <div class="row">
    <div class="col">
      <h3>Make a Lightning Address Payment</h3>
    </div>
  </div>
</div>
{% endblock %}
  • The [extends](https://docs.djangoproject.com/en/4.1/ref/templates/builtins/#std-templatetag-extends) tag is the key here. It tells the template engine that this template “extends” another template. When the template system evaluates this template, first it locates the parent – in this case, “base.html”.

Now let’s add the form.

{% extends "lightning_address_payment/base.html" %} {% block title %}Home{% endblock %} {% block content %}
<div class="container">
  <div class="row">
    <div class="col">
      <h3>Make a Lightning Address Payment</h3>
    </div>
  </div>

  <div class="row">
    <form action="{% url 'index' %}" method="post">
      {% csrf_token %}
      <div class="col-4">
        <div class="mb-3">
          <label class="form-label">Lightning Address</label>
          <input
            type="email"
            class="form-control"
            placeholder="name@example.com"
            name="lnaddress"
            required
          />
        </div>
      </div>
      <div class="col-4">
        <label class="form-label" name="amount">Amount</label>

        <div class="input-group mb-3">
          <input type="number" class="form-control" required />
          <span class="input-group-text">sats</span>
        </div>
      </div>
      <div class="col-4">
        <button type="submit" class="btn btn-primary">Submit</button>
      </div>
    </form>
  </div>
</div>
{% endblock %}

Awesome! Let’s get our last template setup. It’ll be much easier.

Success — Child Template

All we need for the success page, is a header and a description. Create a new template file called success.html in the same directory.

{% extends "base.html" %} {% block title %}Success{% endblock %} {% block
content %}
<div class="container">
  <div class="row">
    <div class="col">
      <h3>Success!</h3>
      <p>You just made a Lightning Address payment!</p>
    </div>
  </div>
</div>
{% endblock %}

Now we’re all setup. We will only need to make one minor change once we have our views and routes setup in the index.html file.

Modifying our Views

Head back to the lightning_address_payment/views.py file. Let’s update this file with everything that we need.

Let’s start off simple by rendering our templates in our views. Update your file to now replace the HttpResponse with a render.

from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.

def index(request):
        # return render instead
    return render(request, 'lightning_address_payment/index.html')

def success(request):
        # return render instead
    return render(request, 'lightning_address_payment/success.html')

Let’s run our server and check out the templates! Looking good so far.



Adding Support to Handle a POST request

In our index.html file, we have configured the form request to submit as a POST request. Therefore, we’ll need to handle the GET request to load the initial form and then the POST request after the user has filled out the form and submitted it.

For more on GET vs POST Requests, read this.

Getting POST Request Data

After you’ve filled out the form on http://localhost:8000 and click the submit button. You’ll need to get the data that the user entered and make an API request to ZBD to process the payment.

In the views.py file, update our index function’s code.

...

def index(request):
        # handles the post request
    if request.POST:
        return render(request, 'lightning_address_payment/index.html')
        # handles the get request
    else:
        return render(request, 'lightning_address_payment/index.html')

...

Now the GET request all appears fine. Let’s get the data from the user submitting the form.

Reviewing the HTML file, we named the Lightning Address field lnaddress and the amount field amount .



Django makes it really easy to parse form data, so let’s go ahead and do that.


All you need to do is use the request.POST object to see which data is incoming on the request. We can ignore the csrfmiddlewaretoken for the scope of this tutorial. We can access the lnaddress and amount properties to pass them to the ZBD API. Let’s save them as variables.

...

def index(request):
    if request.POST:
                amount = request.POST["amount"] + "000"
        # Form Data
                data = { 'lnAddress' : request.POST["lnaddress"], 'amount' : amount}

        return render(request, 'lightning_address_payment/index.html')
    else:
        return render(request, 'lightning_address_payment/index.html')

...

Great! Let’s now take a look at the ZBD API. Here’s the Send Payment to Lightning Address API documentation.

Plugging in the ZBD API

First, we need to install a new library. Install the python requests library. Press CMD (Ctrl) + C to turn off your webserver, if needed. Then execute the following command in your terminal.

pip3 install requests

Import the requests library now at the top of the file that you have it installed.

from django.shortcuts import render
from django.http import HttpResponse
import requests

# Create your views here.

def index(request):
    if request.POST:
                # Form Data
                # We add three zeroes as a string at the end to convert to msats.
        amount = request.POST["amount"] + "000"
                data = { 'lnAddress' : request.POST["lnaddress"], 'amount' : amount, 'comment': 'Sending to a Lightning Address'}
        return render(request, 'lightning_address_payment/index.html')
    else:
        return render(request, 'lightning_address_payment/index.html')

...

Grab your apikey from your project in the Dev Dashboard project. Make sure you have transferred sats to this project!

  • If you haven’t already, Top Up with MoonPay or create a charge to deposit funds into your Developer Wallet and then Transfer the funds to to your project!

Now let’s add the properties needed for the request.posts function that we imported. We’ll add the properties and form the request. Please review the code below and make adjustments, or just copy and paste.

...

def index(request):
    if request.POST:
        # Form Data
        amount = request.POST["amount"] + "000"

        # ZBD API
        URL = "https://api.zebedee.io/v0/ln-address/send-payment"
        data = { 'lnAddress' : request.POST["lnaddress"], 'amount' : amount, 'comment': 'Sending to a Lightning Address'}

                # ADD YOUR API KEY BELOW!
        APIKEY = 'YOUR_API_KEY_HERE'
                ############################
        headers = { "Content-Type":"application/json",  'apikey' : APIKEY }

        # sending get request and saving the response as response object
        r = requests.post(url = URL, json = data , headers = headers )
        # extracting data in json format
        data = r.json()

        print(data)

        if data["success"]:
            return redirect('success')
        else:
            return render(request, 'lightning_address_payment/index.html')

    else:
        return render(request, 'lightning_address_payment/index.html')

...

Whew. Now we’re all ready to go! Here’s the complete views.py file.

from django.shortcuts import render, redirect
import requests, os

# Create your views here.

def index(request):
    if request.POST:
        # Form Data
        amount = request.POST["amount"] + "000"

        # ZBD API
        URL = "https://api.zebedee.io/v0/ln-address/send-payment"
        data = { 'lnAddress' : request.POST["lnaddress"], 'amount' : amount, 'comment': 'Sending to a Lightning Address'}
                # ADD YOUR API KEY BELOW!
        APIKEY = 'YOUR_API_KEY_HERE'
                ############################
        headers = { "Content-Type":"application/json",  'apikey' : APIKEY }

        # sending get request and saving the response as response object
        r = requests.post(url = URL, json = data , headers = headers )
        # extracting data in json format
        data = r.json()

        print(data)

        if data["success"]:
            return redirect('success')
        else:
            return render(request, 'lightning_address_payment/index.html')

    else:
        return render(request, 'lightning_address_payment/index.html')

def success(request):
    return render(request, 'lightning_address_payment/success.html')

Making a Payment Using Your Web App

Now everything is hooked up. Let’s go ahead and fill out the form and give it a go! This assumes you already have a valid Lightning Address.


Click submit!


Yay! Everything went through just as expected.


Check out the data in the terminal! Everything went through as expected.

Congratulations. You just made your first Lightning Web App with Lightning Address 😄!

Please note, this does not use environment variables and is NOT suitable for production usage as it is NOT secure. This is purely for demonstration and learning purposes only. If you are going to publish your app to GitHub, please remove your apikey.