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