Some checks failed
continuous-integration/drone/push Build is failing
But use the hashed zap request event for the description hash.
148 lines
3.8 KiB
Ruby
148 lines
3.8 KiB
Ruby
class LnurlpayController < ApplicationController
|
|
before_action :check_service_available
|
|
before_action :find_user
|
|
|
|
MIN_SATS = 10
|
|
MAX_SATS = 1_000_000
|
|
MAX_COMMENT_CHARS = 100
|
|
|
|
# GET /.well-known/lnurlp/:username
|
|
def index
|
|
res = {
|
|
status: "OK",
|
|
callback: "https://#{Setting.accounts_domain}/lnurlpay/#{@user.cn}/invoice",
|
|
tag: "payRequest",
|
|
maxSendable: MAX_SATS * 1000, # msat
|
|
minSendable: MIN_SATS * 1000, # msat
|
|
metadata: metadata(@user.address),
|
|
commentAllowed: MAX_COMMENT_CHARS
|
|
}
|
|
|
|
if Setting.nostr_enabled? && Setting.nostr_private_key.present?
|
|
res[:allows_nostr] = true
|
|
res[:nostrPubkey] = Setting.nostr_public_key
|
|
end
|
|
|
|
render json: res
|
|
end
|
|
|
|
# GET /.well-known/keysend/:username
|
|
def keysend
|
|
http_status :not_found and return unless Setting.lndhub_keysend_enabled?
|
|
|
|
render json: {
|
|
status: "OK",
|
|
tag: "keysend",
|
|
pubkey: Setting.lndhub_public_key,
|
|
customData: [{
|
|
customKey: "696969",
|
|
customValue: @user.ln_account
|
|
}]
|
|
}
|
|
end
|
|
|
|
# GET /lnurlpay/:username/invoice
|
|
def invoice
|
|
amount = params[:amount].to_i / 1000 # msats to sats
|
|
comment = params[:comment] || ""
|
|
address = @user.address
|
|
|
|
if !valid_amount?(amount)
|
|
render json: { status: "ERROR", reason: "Invalid amount" }
|
|
return
|
|
end
|
|
|
|
if params[:nostr].present?
|
|
handle_zap_request amount, params[:nostr], params[:lnurl]
|
|
else
|
|
handle_pay_request address, amount, comment
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def check_service_available
|
|
http_status :not_found unless Setting.lndhub_enabled?
|
|
end
|
|
|
|
def find_user
|
|
@user = User.where(cn: params[:username], ou: Setting.primary_domain).first
|
|
http_status :not_found if @user.nil?
|
|
end
|
|
|
|
def metadata(address)
|
|
"[[\"text/identifier\",\"#{address}\"],[\"text/plain\",\"Sats for #{address}\"]]"
|
|
end
|
|
|
|
def valid_amount?(amount_in_sats)
|
|
amount_in_sats <= MAX_SATS && amount_in_sats >= MIN_SATS
|
|
end
|
|
|
|
def valid_comment?(comment)
|
|
comment.length <= MAX_COMMENT_CHARS
|
|
end
|
|
|
|
def handle_pay_request(address, amount, comment)
|
|
if !valid_comment?(comment)
|
|
render json: { status: "ERROR", reason: "Comment too long" }
|
|
return
|
|
end
|
|
|
|
desc = "To #{address}"
|
|
desc = "#{desc}: \"#{comment}\"" if comment.present?
|
|
|
|
invoice = LndhubManager::CreateUserInvoice.call(
|
|
user: @user, payload: {
|
|
amount: amount, # sats
|
|
description: desc,
|
|
description_hash: Digest::SHA256.hexdigest(metadata(address)),
|
|
}
|
|
)
|
|
|
|
render json: {
|
|
status: "OK",
|
|
successAction: {
|
|
tag: "message",
|
|
message: "Sats received. Thank you!"
|
|
},
|
|
routes: [],
|
|
pr: invoice["payment_request"]
|
|
}
|
|
end
|
|
|
|
def nostr_event_from_payload(nostr_param)
|
|
event_obj = JSON.parse(nostr_param).transform_keys(&:to_sym)
|
|
Nostr::Event.new(**event_obj)
|
|
rescue => e
|
|
return nil
|
|
end
|
|
|
|
def valid_zap_request?(amount, event, lnurl)
|
|
NostrManager::VerifyZapRequest.call(
|
|
amount: amount, event: event, lnurl: lnurl
|
|
)
|
|
end
|
|
|
|
def handle_zap_request(amount, nostr_param, lnurl_param)
|
|
event = nostr_event_from_payload(nostr_param)
|
|
|
|
unless event.present? && valid_zap_request?(amount*1000, event, lnurl_param)
|
|
render json: { status: "ERROR", reason: "Invalid zap request" }
|
|
return
|
|
end
|
|
|
|
desc = "Zap for #{@user.address}"
|
|
desc = "#{desc}: \"#{event.content}\"" if event.content.present?
|
|
|
|
invoice = LndhubManager::CreateUserInvoice.call(
|
|
user: @user, payload: {
|
|
amount: amount, # sats
|
|
description: desc,
|
|
description_hash: Digest::SHA256.hexdigest(event.to_json),
|
|
}
|
|
)
|
|
|
|
render json: { status: "OK", pr: invoice["payment_request"] }
|
|
end
|
|
end
|