Add LndHub service, lnurl-pay endpoints
Enables the lnurl-pay payment workflow
This commit is contained in:
		
							parent
							
								
									884070a3cb
								
							
						
					
					
						commit
						dbc811b840
					
				
							
								
								
									
										58
									
								
								app/controllers/lnurlpay_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								app/controllers/lnurlpay_controller.rb
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,58 @@
 | 
			
		||||
class LnurlpayController < ApplicationController
 | 
			
		||||
  before_action :find_user_by_address
 | 
			
		||||
 | 
			
		||||
  def index
 | 
			
		||||
    render json: {
 | 
			
		||||
      status: "OK",
 | 
			
		||||
      callback: "https://accounts.kosmos.org/lnurlpay/#{@user.address}/invoice",
 | 
			
		||||
      tag: "payRequest",
 | 
			
		||||
      maxSendable: 1000000,
 | 
			
		||||
      minSendable: 1000,
 | 
			
		||||
      metadata: metadata(@user.address),
 | 
			
		||||
      commentAllowed: 255
 | 
			
		||||
    }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def invoice
 | 
			
		||||
    amount = params[:amount].to_i # msats
 | 
			
		||||
    address = params[:address]
 | 
			
		||||
 | 
			
		||||
    validate_amount(amount)
 | 
			
		||||
 | 
			
		||||
    # amount = amount / 1000 # we need sats
 | 
			
		||||
    payment_request = @user.ln_create_invoice({
 | 
			
		||||
      amount: amount,
 | 
			
		||||
      description_hash: Digest::SHA2.hexdigest(metadata(address))
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    render json: {
 | 
			
		||||
      status: "OK",
 | 
			
		||||
      successAction: {
 | 
			
		||||
        tag: "message",
 | 
			
		||||
        message: "Payment received. Thanks!"
 | 
			
		||||
      },
 | 
			
		||||
      routes: [],
 | 
			
		||||
      pr: payment_request
 | 
			
		||||
    }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def find_user_by_address
 | 
			
		||||
    address = params[:address].split("@")
 | 
			
		||||
    @user = User.where(cn: address.first, ou: address.last).first
 | 
			
		||||
    http_status :not_found if @user.nil?
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def metadata(address)
 | 
			
		||||
    "[[\"text/identifier\", \"#{address}\"], [\"text/plain\", \"Sats for #{address}\"]]"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def validate_amount(amount)
 | 
			
		||||
    if amount > 1000000 || amount < 1000
 | 
			
		||||
      render json: { status: "ERROR", reason: "Invalid amount" }
 | 
			
		||||
      return
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
end
 | 
			
		||||
@ -27,27 +27,18 @@ class WalletController < ApplicationController
 | 
			
		||||
    if session["ln_auth_token"].present?
 | 
			
		||||
      @ln_auth_token = session["ln_auth_token"]
 | 
			
		||||
    else
 | 
			
		||||
      res = Faraday.post("#{ENV["LNDHUB_API_URL"]}/auth?type=auth",
 | 
			
		||||
                         { login: current_user.ln_login, password: current_user.ln_password }.to_json,
 | 
			
		||||
                         "Content-Type" => "application/json",
 | 
			
		||||
                         "Accept" => "application/json")
 | 
			
		||||
 | 
			
		||||
      credentials = JSON.parse(res.body)
 | 
			
		||||
      session["ln_auth_token"] = credentials["access_token"]
 | 
			
		||||
      @ln_auth_token = credentials["access_token"]
 | 
			
		||||
      lndhub = Lndhub.new
 | 
			
		||||
      auth_token = lndhub.authenticate(current_user)
 | 
			
		||||
      session["ln_auth_token"] = auth_token
 | 
			
		||||
      @ln_auth_token = auth_token
 | 
			
		||||
    end
 | 
			
		||||
  rescue
 | 
			
		||||
    # TODO add exception tracking
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def fetch_balance
 | 
			
		||||
      res = Faraday.get("#{ENV["LNDHUB_API_URL"]}/balance", {}, {
 | 
			
		||||
        "Content-Type" => "application/json",
 | 
			
		||||
        "Accept" => "application/json",
 | 
			
		||||
        "Authorization" => "Bearer #{@ln_auth_token}"
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      data = JSON.parse(res.body)
 | 
			
		||||
      data["BTC"]["AvailableBalance"]
 | 
			
		||||
    lndhub = Lndhub.new
 | 
			
		||||
    data = lndhub.balance @ln_auth_token
 | 
			
		||||
    data["BTC"]["AvailableBalance"]
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								app/helpers/lnurlpay_helper.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								app/helpers/lnurlpay_helper.rb
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
module LnurlpayHelper
 | 
			
		||||
end
 | 
			
		||||
@ -2,14 +2,10 @@ class CreateLndhubWalletJob < ApplicationJob
 | 
			
		||||
  queue_as :default
 | 
			
		||||
 | 
			
		||||
  def perform(user)
 | 
			
		||||
    res = Faraday.post("#{ENV["LNDHUB_API_URL"]}/create",
 | 
			
		||||
                       { partnerid: "bluewallet", accounttype: "common" }.to_json,
 | 
			
		||||
                       "Content-Type" => "application/json")
 | 
			
		||||
 | 
			
		||||
    credentials = JSON.parse(res.body)
 | 
			
		||||
    lndhub = Lndhub.new
 | 
			
		||||
    credentials = lndhub.create({ partnerid: user.ou, accounttype: "user" })
 | 
			
		||||
 | 
			
		||||
    user.update! ln_login: credentials["login"],
 | 
			
		||||
                 ln_password: credentials["password"]
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -56,4 +56,10 @@ class User < ApplicationRecord
 | 
			
		||||
    self.valid?
 | 
			
		||||
    self.errors[attribute_name].blank?
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def ln_create_invoice(payload)
 | 
			
		||||
    lndhub = Lndhub.new
 | 
			
		||||
    lndhub.authenticate self
 | 
			
		||||
    lndhub.addinvoice payload
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										57
									
								
								app/services/lndhub.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								app/services/lndhub.rb
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,57 @@
 | 
			
		||||
class Lndhub
 | 
			
		||||
  attr_accessor :auth_token
 | 
			
		||||
 | 
			
		||||
  def initialize
 | 
			
		||||
    @base_url = ENV["LNDHUB_API_URL"]
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def post(endpoint, payload)
 | 
			
		||||
    headers = { "Content-Type" => "application/json" }
 | 
			
		||||
    if auth_token
 | 
			
		||||
      headers.merge!({ "Authorization" => "Bearer #{auth_token}" })
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    res = Faraday.post "#{@base_url}/#{endpoint}", payload.to_json, headers
 | 
			
		||||
 | 
			
		||||
    if res.status != 200
 | 
			
		||||
      Rails.logger.error "[lndhub] API request failed:"
 | 
			
		||||
      Rails.logger.error res.body
 | 
			
		||||
      #TODO add some kind of exception tracking/notifications
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    JSON.parse(res.body)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def get(endpoint, auth_token)
 | 
			
		||||
    res = Faraday.get("#{@base_url}/#{endpoint}", {}, {
 | 
			
		||||
      "Content-Type" => "application/json",
 | 
			
		||||
      "Accept" => "application/json",
 | 
			
		||||
      "Authorization" => "Bearer #{auth_token}"
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    JSON.parse(res.body)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def create(payload)
 | 
			
		||||
    post "create", payload
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def authenticate(user)
 | 
			
		||||
    credentials = post "auth?type=auth", { login: user.ln_login, password: user.ln_password }
 | 
			
		||||
    self.auth_token = credentials["access_token"]
 | 
			
		||||
    self.auth_token
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def balance(user_token)
 | 
			
		||||
    get "balance", user_token || auth_token
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def addinvoice(payload)
 | 
			
		||||
    invoice = post "addinvoice", {
 | 
			
		||||
      amt: payload[:amount],
 | 
			
		||||
      description_hash: payload[:description_hash]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    invoice["payment_request"]
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -21,6 +21,9 @@ Rails.application.routes.draw do
 | 
			
		||||
 | 
			
		||||
  get 'wallet', to: 'wallet#index'
 | 
			
		||||
 | 
			
		||||
  get 'lnurlpay/:address', to: 'lnurlpay#index', constraints: { address: /[^\/]+/}
 | 
			
		||||
  get 'lnurlpay/:address/invoice', to: 'lnurlpay#invoice', constraints: { address: /[^\/]+/}
 | 
			
		||||
 | 
			
		||||
  namespace :admin do
 | 
			
		||||
    root to: 'dashboard#index'
 | 
			
		||||
    get 'invitations', to: 'invitations#index'
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										15
									
								
								spec/helpers/lnurlpay_helper_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								spec/helpers/lnurlpay_helper_spec.rb
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
require 'rails_helper'
 | 
			
		||||
 | 
			
		||||
# Specs in this file have access to a helper object that includes
 | 
			
		||||
# the LnurlpayHelper. For example:
 | 
			
		||||
#
 | 
			
		||||
# describe LnurlpayHelper do
 | 
			
		||||
#   describe "string concat" do
 | 
			
		||||
#     it "concats two strings with spaces" do
 | 
			
		||||
#       expect(helper.concat_strings("this","that")).to eq("this that")
 | 
			
		||||
#     end
 | 
			
		||||
#   end
 | 
			
		||||
# end
 | 
			
		||||
RSpec.describe LnurlpayHelper, type: :helper do
 | 
			
		||||
  pending "add some examples to (or delete) #{__FILE__}"
 | 
			
		||||
end
 | 
			
		||||
@ -16,7 +16,7 @@ RSpec.describe CreateLndhubWalletJob, type: :job do
 | 
			
		||||
    perform_enqueued_jobs { job }
 | 
			
		||||
 | 
			
		||||
    expect(WebMock).to have_requested(:post, "http://10.1.1.163:3023/create")
 | 
			
		||||
      .with { |req| req.body == '{"partnerid":"bluewallet","accounttype":"common"}' }
 | 
			
		||||
      .with { |req| req.body == '{"partnerid":"kosmos.org","accounttype":"user"}' }
 | 
			
		||||
 | 
			
		||||
    user.reload
 | 
			
		||||
    expect(user.ln_login).to eq("abc123")
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										7
									
								
								spec/requests/lnurlpay_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								spec/requests/lnurlpay_spec.rb
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
require 'rails_helper'
 | 
			
		||||
 | 
			
		||||
RSpec.describe "Lnurlpays", type: :request do
 | 
			
		||||
  describe "GET /index" do
 | 
			
		||||
    pending "add some examples (or delete) #{__FILE__}"
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user