Step 4 - Code

Code

  • Attach a new script to the ‘insertcoin’ Node (Root Node), name it ‘insertcoin.gd’
  • Click the ‘insertcoin’ node (Button) under the Node tab (this tab is next/behind the Inspector on the right hand side of Godot) and double click the ‘Pressed()’ signal, select the ‘insertcoin’ Node (root Node) and click 'Connect’ to automatically create the ‘_on_insertcoin_pressed method’. The connection wil be created in the Script ‘insertcoin.gd’ of the ‘insertcoin’ Node.
  • Open the ‘insertcoin.gd’ script, remove all contents (ctrl-a, ctrl-x) and copy-paste gdscript: insertcoin.gd to replace everything (copy here from the turorial and paste inside godot script editor).

gdscript: insertcoin.gd

extends Node

func _ready():
     pass # Replace with function body.

func _on_insertcoin_pressed():
     get_node("invoice").show()
  • Attach a new script to the ‘qrcode’ Node (Sprite), name it ‘qrcode.gd’.
  • Click the ‘Button’ Node (Button) under the Node tab (next/behind the Inspector) and double click the ‘Pressed()’ Signal, select the ‘qrcode’ Node (Sprite) and click ‘Connect’ to create the ‘_on_Button_Pressed’ Method.
  • Open the ‘qrcode.gd’ script by clicking on the scroll icon, next to the ‘qrcode’ Node (Sprite), delete all it’s contents (including the Method you just created) and copy-paste the following code:

gdscript: qrcode.gd

extends Sprite
const qrpng = "payreq_qrcode.png"
const path = "C:/Users/your/location/of/godot/insertcoins/"

func _ready():
    #unused
    pass

func generate_qrcode(payreq):
    var output = []
    #generate rcode
    OS.execute(path + 'zint.exe', ['-b', '58', '-o', path + qrpng, '--vers=15', '--scale', '2', '-d', payreq] ,true ,output)
    #to test the generation of the qrcode, uncomment the next line and check if zint is in this path
    #print(path)

func _on_Button_pressed():
    var httpr = load("res://HTTPRequest.gd").new()
    var jsonh = load("res://jsonhandler.gd").new()

    #generate new invoice
    var ni = httpr.new_invoice() 
    #test what happens by uncommenting the next line:
    #print(ni)

    #construct the new invoice data dictionary
    var invoice = jsonh.new_invoice(ni)

    #generate QR COde
    generate_qrcode(invoice['payreq'])
    #show qrcode
    var imageTexture = ImageTexture.new()
    var dynImage = Image.new()
    dynImage.load(path + qrpng)
    imageTexture.create_from_image(dynImage)
    self.texture = imageTexture

    #print(invoice['id'])
    #show invoice status
    get_node("../Button").disabled = true
    get_node("../Button").text = "Connecting..."
    get_node("../Label").text = "scan QR code with your LN wallet."

    #pause
    yield(get_tree().create_timer(1.1),"timeout")
    #var invoices = httpr.get_invoices()

    #start polling this invoice's status
    var invid = invoice['id']
    invoice_poller(httpr, jsonh, invid)

func invoice_poller(httpr, jsonh, id, count = 1023):
    #get status for this invoice (try 1023 times (max))
    count -= 1
    #request data and process json
    var invoices = httpr.get_invoices()
    var invoice = jsonh.poll_invoice(invoices, id)
    #print("status:" + invoice['status'] + " Expires: " + str(invoice['expires_at']) + " Conn timeout: " + str(count))
    if count == 0 or invoice['status'] == 'paid':
        #print(invoice)
        #print("status:" + invoice['status'] + " Expires: " + str(invoice['expires_at']) + " Conn timeout: " + str(count))
        if count == 0:
            print("LN server timeout")
        if invoice['status'] == 'paid':
            if int(invoice['msatoshi_received']) >= 10000:
                get_node("../Label").text = invoice['msatoshi_received'] + " sats received. Have fun!"
                get_tree().change_scene("res://game.tscn")
            else:
                get_node("../Label").text = "Payment received. Insufficient funds :-("
        #print("invoice expiry date: " + str(invoice['expires_at']))
        return
    else:
        yield(get_tree().create_timer(2.5),"timeout")
    get_node("../Label").text = "Scan QR code with your LN wallet. Conn timeout: " + str(count) + " (" + str(invoice['status']) + ")"
    #recurse: see if invoice is paid yet
    invoice_poller(httpr, jsonh, id, count)
  • Use zint.exe from this project or get a version for your system here: zint
  • In the ‘qrcode.gd’ Script set the Constant (const) on line 3 to the path of your godot project folder (you’ve remembered this in Step 1 - initial setup).

  • Add a HTTPRrequest Child Node under ‘insertcoin’ (root Node).
  • Attach a script to the ‘HTTPRequest’ Node (HTTPRequest) and name it ‘HTTPRequest.gd’.
  • Attach a new script to this ‘HTTPRequest’ Node, delete it’s contents, and copy past the following code:

gdscript: HTTPRequest.gd

extends HTTPRequest
# This simple class can do HTTP requests
const host = "10.0.0.1"
const port = 9112

func _ready():
    #output = get_invoices()
    pass

func get_info():
    return request_data("info")

func get_invoices():
    return request_data("invoices")

func new_invoice():
    #return json
    return request_data("invoice")

func request_data(type):
    var err = 0
    var http = HTTPClient.new() # Create the Client.

    err = http.connect_to_host(host, port) # Connect to host/port.
    assert(err == OK) # Make sure connection was OK.

    # Wait until resolved and connected.
    while http.get_status() == HTTPClient.STATUS_CONNECTING or http.get_status() == HTTPClient.STATUS_RESOLVING:
        http.poll()
        #print("Connecting...")
        OS.delay_msec(500)

    assert(http.get_status() == HTTPClient.STATUS_CONNECTED) # Could not connect

    # Some headers
    var headers = [
        "User-Agent: Pirulo/1.0 (Godot)",
        "Content-Type: application/json",
        "Authorization: Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", #base64 encoded string "Token:Secret" ! DO NOT USE in shared compiled program (add hashing)
        "Accept: */*"
    ]
    var query = '{"msatoshi":10000,"metadata":{"customer_id":9999,"products":[1,1]},"description":"yourgame invoice"}'

    if type == "invoice":
        err = http.request(HTTPClient.METHOD_POST, "/" + type, headers, query) # Request a new invoice using POST
    else:
        err = http.request(HTTPClient.METHOD_GET, "/" + type, headers) # Request info or list of invoices using GET
    assert(err == OK) # Make sure all is OK.

    while http.get_status() == HTTPClient.STATUS_REQUESTING:
        # Keep polling for as long as the request is being processed.
        http.poll()
        #print("Requesting...")
        if not OS.has_feature("web"):
            OS.delay_msec(500)
        else:
            # Synchronous HTTP requests are not supported on the web,
            # so wait for the next main loop iteration.
            yield(Engine.get_main_loop(), "idle_frame")

    assert(http.get_status() == HTTPClient.STATUS_BODY or http.get_status() == HTTPClient.STATUS_CONNECTED) # Make sure request finished well.

    #print("response? ", http.has_response()) # Site might not have a response.
    if http.has_response():
        # If there is a response...
        headers = http.get_response_headers_as_dictionary() # Get response headers.
        #print("code: ", http.get_response_code()) # Show response code.
        #print("**headers:\\n", headers) # Show headers.

        # Getting the HTTP Body

        if http.is_response_chunked():
            # Does it use chunks?
            print("Response is Chunked")
        #else:
            # Or just plain Content-Length
            var bl = http.get_response_body_length()
            print("Response Length: ",bl)
        # This method works for both anyway

        var rb = PoolByteArray() # Array that will hold the data.

        while http.get_status() == HTTPClient.STATUS_BODY:
            # While there is body left to be read
            http.poll()
            var chunk = http.read_response_body_chunk() # Get a chunk.
            if chunk.size() == 0:
                # Got nothing, wait for buffers to fill a bit.
                OS.delay_usec(1000)
            else:
                rb = rb + chunk # Append to read buffer.

        var text = rb.get_string_from_ascii()
        #print("bytes got: ", rb.size())
        return text
  • On line 3 and 4 of ‘HTTPRequest.gd’ set your host (server-ip) and it’s port to what you have remembered earlier.
  • Generate a base64 encoded string from your ‘api-token’ and your ‘secretpassword’, concatenated using a semicolon like this: login:password. You remembered this during the installation of Lighting Charge in the requirements section. Use a handy online tool like this base64encode (choose ‘encode’) or a command-line if you know what you’re doing.
  • On line 39 replace the X-es with your base64 encoded access credentials. Be careful, do not use this in a production version of your game without understanding the security implications, people could decompile your executable, find these credentials, gain access to your Lightning Charge server and see your invoices.

  • Add a Child Node (Node) under ‘insertcoin’ (Root Node) and rename it “jsonhandler”. (You’ll find it in the node creation window when you remove your previous search string, it’s the top one)
  • Attach a new script, name it ‘jsonhandler.gd’, remove all it’s contents and copy-paste this code:

gdscript: jsonhandler.gd

extends Node
#class handles json

# Called when the node enters the scene tree for the first time.
func _ready():
    pass # Replace with function body.

func get_invoice(json, id):
    #get a specific invoice as array
    var invoices = JSON.parse(json).result

    #print("parsed json: ")
    #print(invoices)

    if typeof(invoices) == TYPE_ARRAY:
        #loop through list of invoices and find the one requested
        for i in range(invoices.size()):
            if invoices[i]['id'] == id:
                return invoices[i] #return dictionary
    else:
        print("unexpected result")

func new_invoice(json):
    #process json from new invoice
    var invoice = JSON.parse(json).result
    if typeof(invoice) == TYPE_DICTIONARY:
        return invoice #return dictionary
    else:
        print("not a dictionary")

func poll_invoice(json, id):
    var invoice = get_invoice(json, id)
    return invoice
  • From the Godot menu click ‘Scene > New Scene’, name it “game” and Save as ‘game.tscn’.
  • From the menu select ‘Project > Project settings’. In the project window under the ‘application’ category click the ‘Run’ item. Choose ‘insertcoins.tscn’ as your main Scene using the folder icon on the right.
  • Close the window.

Test