Merge pull request #14 from boltcard/ln-addr
adds support for lightning address receipts & email notifications
This commit is contained in:
commit
5d3072fa6d
13 changed files with 977 additions and 120 deletions
|
|
@ -15,10 +15,13 @@ CREATE TABLE cards (
|
||||||
uid CHAR(14) NOT NULL,
|
uid CHAR(14) NOT NULL,
|
||||||
last_counter_value INTEGER NOT NULL,
|
last_counter_value INTEGER NOT NULL,
|
||||||
lnurlw_request_timeout_sec INT NOT NULL,
|
lnurlw_request_timeout_sec INT NOT NULL,
|
||||||
enable_flag CHAR(1) NOT NULL DEFAULT 'N',
|
lnurlw_enable CHAR(1) NOT NULL DEFAULT 'N',
|
||||||
tx_limit_sats INT NOT NULL,
|
tx_limit_sats INT NOT NULL,
|
||||||
day_limit_sats INT NOT NULL,
|
day_limit_sats INT NOT NULL,
|
||||||
card_name VARCHAR(100) NOT NULL DEFAULT '',
|
lnurlp_enable CHAR(1) NOT NULL DEFAULT 'N',
|
||||||
|
card_name VARCHAR(100) UNIQUE NOT NULL DEFAULT '',
|
||||||
|
email_address VARCHAR(100) DEFAULT '',
|
||||||
|
email_enable CHAR(1) NOT NULL DEFAULT 'N',
|
||||||
one_time_code CHAR(32) NOT NULL DEFAULT '',
|
one_time_code CHAR(32) NOT NULL DEFAULT '',
|
||||||
one_time_code_expiry TIMESTAMPTZ DEFAULT NOW() + INTERVAL '1 DAY',
|
one_time_code_expiry TIMESTAMPTZ DEFAULT NOW() + INTERVAL '1 DAY',
|
||||||
one_time_code_used CHAR(1) NOT NULL DEFAULT 'Y',
|
one_time_code_used CHAR(1) NOT NULL DEFAULT 'Y',
|
||||||
|
|
@ -41,5 +44,19 @@ CREATE TABLE card_payments (
|
||||||
CONSTRAINT fk_card FOREIGN KEY(card_id) REFERENCES cards(card_id)
|
CONSTRAINT fk_card FOREIGN KEY(card_id) REFERENCES cards(card_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE card_receipts (
|
||||||
|
card_receipt_id INT GENERATED ALWAYS AS IDENTITY,
|
||||||
|
card_id INT NOT NULL,
|
||||||
|
ln_invoice VARCHAR(1024) NOT NULL DEFAULT '',
|
||||||
|
r_hash_hex CHAR(64) UNIQUE NOT NULL DEFAULT '',
|
||||||
|
amount_msats BIGINT CHECK (amount_msats > 0),
|
||||||
|
receipt_status VARCHAR(100) NOT NULL DEFAULT '',
|
||||||
|
receipt_status_time TIMESTAMPTZ,
|
||||||
|
CONSTRAINT fk_card FOREIGN KEY(card_id) REFERENCES cards(card_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
GRANT ALL PRIVILEGES ON TABLE cards TO cardapp;
|
GRANT ALL PRIVILEGES ON TABLE cards TO cardapp;
|
||||||
GRANT ALL PRIVILEGES ON TABLE card_payments TO cardapp;
|
GRANT ALL PRIVILEGES ON TABLE card_payments TO cardapp;
|
||||||
|
GRANT ALL PRIVILEGES ON TABLE card_receipts TO cardapp;
|
||||||
|
|
||||||
|
|
|
||||||
162
database.go
162
database.go
|
|
@ -19,9 +19,12 @@ type Card struct {
|
||||||
db_uid string
|
db_uid string
|
||||||
last_counter_value uint32
|
last_counter_value uint32
|
||||||
lnurlw_request_timeout_sec int
|
lnurlw_request_timeout_sec int
|
||||||
enable_flag string
|
lnurlw_enable string
|
||||||
tx_limit_sats int
|
tx_limit_sats int
|
||||||
day_limit_sats int
|
day_limit_sats int
|
||||||
|
lnurlp_enable string
|
||||||
|
email_address string
|
||||||
|
email_enable string
|
||||||
one_time_code string
|
one_time_code string
|
||||||
card_name string
|
card_name string
|
||||||
}
|
}
|
||||||
|
|
@ -106,6 +109,88 @@ func db_get_card_count_for_uid(uid string) (int, error) {
|
||||||
return card_count, nil
|
return card_count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func db_get_card_count_for_name_lnurlp(name string) (int, error) {
|
||||||
|
|
||||||
|
card_count := 0
|
||||||
|
|
||||||
|
db, err := db_open()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
sqlStatement := `select count(card_id) from cards where card_name=$1 and lnurlp_enable='Y';`
|
||||||
|
|
||||||
|
row := db.QueryRow(sqlStatement, name)
|
||||||
|
err = row.Scan(&card_count)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return card_count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func db_get_card_id_for_name(name string) (int, error) {
|
||||||
|
|
||||||
|
card_id := 0
|
||||||
|
|
||||||
|
db, err := db_open()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
sqlStatement := `select card_id from cards where card_name=$1;`
|
||||||
|
|
||||||
|
row := db.QueryRow(sqlStatement, name)
|
||||||
|
err = row.Scan(&card_id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return card_id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func db_get_card_id_for_card_payment_id(card_payment_id int) (int, error) {
|
||||||
|
card_id := 0
|
||||||
|
|
||||||
|
db, err := db_open()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
sqlStatement := `SELECT card_id FROM card_payments WHERE card_payment_id=$1;`
|
||||||
|
|
||||||
|
row := db.QueryRow(sqlStatement, card_payment_id)
|
||||||
|
err = row.Scan(&card_id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return card_id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func db_get_card_id_for_r_hash(r_hash string) (int, error) {
|
||||||
|
card_id := 0
|
||||||
|
|
||||||
|
db, err := db_open()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
sqlStatement := `SELECT card_id FROM card_receipts WHERE r_hash_hex=$1;`
|
||||||
|
|
||||||
|
row := db.QueryRow(sqlStatement, r_hash)
|
||||||
|
err = row.Scan(&card_id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return card_id, nil
|
||||||
|
}
|
||||||
|
|
||||||
func db_get_cards_blank_uid() ([]Card, error) {
|
func db_get_cards_blank_uid() ([]Card, error) {
|
||||||
|
|
||||||
// open the database
|
// open the database
|
||||||
|
|
@ -189,7 +274,7 @@ func db_get_card_from_uid(card_uid string) (*Card, error) {
|
||||||
|
|
||||||
sqlStatement := `SELECT card_id, k2_cmac_key, uid,` +
|
sqlStatement := `SELECT card_id, k2_cmac_key, uid,` +
|
||||||
` last_counter_value, lnurlw_request_timeout_sec,` +
|
` last_counter_value, lnurlw_request_timeout_sec,` +
|
||||||
` enable_flag, tx_limit_sats, day_limit_sats` +
|
` lnurlw_enable, tx_limit_sats, day_limit_sats` +
|
||||||
` FROM cards WHERE uid=$1;`
|
` FROM cards WHERE uid=$1;`
|
||||||
row := db.QueryRow(sqlStatement, card_uid)
|
row := db.QueryRow(sqlStatement, card_uid)
|
||||||
err = row.Scan(
|
err = row.Scan(
|
||||||
|
|
@ -198,7 +283,7 @@ func db_get_card_from_uid(card_uid string) (*Card, error) {
|
||||||
&c.db_uid,
|
&c.db_uid,
|
||||||
&c.last_counter_value,
|
&c.last_counter_value,
|
||||||
&c.lnurlw_request_timeout_sec,
|
&c.lnurlw_request_timeout_sec,
|
||||||
&c.enable_flag,
|
&c.lnurlw_enable,
|
||||||
&c.tx_limit_sats,
|
&c.tx_limit_sats,
|
||||||
&c.day_limit_sats)
|
&c.day_limit_sats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -220,7 +305,7 @@ func db_get_card_from_card_id(card_id int) (*Card, error) {
|
||||||
|
|
||||||
sqlStatement := `SELECT card_id, k2_cmac_key, uid,` +
|
sqlStatement := `SELECT card_id, k2_cmac_key, uid,` +
|
||||||
` last_counter_value, lnurlw_request_timeout_sec,` +
|
` last_counter_value, lnurlw_request_timeout_sec,` +
|
||||||
` enable_flag, tx_limit_sats, day_limit_sats` +
|
` lnurlw_enable, tx_limit_sats, day_limit_sats, email_enable, email_address` +
|
||||||
` FROM cards WHERE card_id=$1;`
|
` FROM cards WHERE card_id=$1;`
|
||||||
row := db.QueryRow(sqlStatement, card_id)
|
row := db.QueryRow(sqlStatement, card_id)
|
||||||
err = row.Scan(
|
err = row.Scan(
|
||||||
|
|
@ -229,9 +314,11 @@ func db_get_card_from_card_id(card_id int) (*Card, error) {
|
||||||
&c.db_uid,
|
&c.db_uid,
|
||||||
&c.last_counter_value,
|
&c.last_counter_value,
|
||||||
&c.lnurlw_request_timeout_sec,
|
&c.lnurlw_request_timeout_sec,
|
||||||
&c.enable_flag,
|
&c.lnurlw_enable,
|
||||||
&c.tx_limit_sats,
|
&c.tx_limit_sats,
|
||||||
&c.day_limit_sats)
|
&c.day_limit_sats,
|
||||||
|
&c.email_enable,
|
||||||
|
&c.email_address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &c, err
|
return &c, err
|
||||||
}
|
}
|
||||||
|
|
@ -297,8 +384,8 @@ func db_insert_payment(card_id int, lnurlw_k1 string) error {
|
||||||
// insert a new record into card_payments with card_id & lnurlw_k1 set
|
// insert a new record into card_payments with card_id & lnurlw_k1 set
|
||||||
|
|
||||||
sqlStatement := `INSERT INTO card_payments` +
|
sqlStatement := `INSERT INTO card_payments` +
|
||||||
` (card_id, lnurlw_k1, paid_flag, lnurlw_request_time)` +
|
` (card_id, lnurlw_k1, paid_flag, lnurlw_request_time, payment_status_time)` +
|
||||||
` VALUES ($1, $2, 'N', NOW());`
|
` VALUES ($1, $2, 'N', NOW(), NOW());`
|
||||||
res, err := db.Exec(sqlStatement, card_id, lnurlw_k1)
|
res, err := db.Exec(sqlStatement, card_id, lnurlw_k1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -314,6 +401,63 @@ func db_insert_payment(card_id int, lnurlw_k1 string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func db_insert_receipt(
|
||||||
|
card_id int,
|
||||||
|
ln_invoice string,
|
||||||
|
r_hash_hex string,
|
||||||
|
amount_msat int64) error {
|
||||||
|
|
||||||
|
db, err := db_open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// insert a new record into card_receipts
|
||||||
|
|
||||||
|
sqlStatement := `INSERT INTO card_receipts` +
|
||||||
|
` (card_id, ln_invoice, r_hash_hex, amount_msats, receipt_status_time)` +
|
||||||
|
` VALUES ($1, $2, $3, $4, NOW());`
|
||||||
|
res, err := db.Exec(sqlStatement, card_id, ln_invoice, r_hash_hex, amount_msat)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
count, err := res.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
return errors.New("not one card_receipts record inserted")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func db_update_receipt_state(r_hash_hex string, invoice_state string) error {
|
||||||
|
db, err := db_open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
sqlStatement := `UPDATE card_receipts ` +
|
||||||
|
`SET receipt_status = $2, receipt_status_time = NOW() ` +
|
||||||
|
`WHERE r_hash_hex = $1;`
|
||||||
|
res, err := db.Exec(sqlStatement, r_hash_hex, invoice_state)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
count, err := res.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
return errors.New("not one card_receipts record updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func db_get_payment_k1(lnurlw_k1 string) (*payment, error) {
|
func db_get_payment_k1(lnurlw_k1 string) (*payment, error) {
|
||||||
p := payment{}
|
p := payment{}
|
||||||
|
|
||||||
|
|
@ -355,7 +499,7 @@ func db_update_payment_invoice(card_payment_id int, ln_invoice string, amount_ms
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if count != 1 {
|
if count != 1 {
|
||||||
return errors.New("not one card_payment record updated")
|
return errors.New("not one card_payments record updated")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ edit `create_db.sql` to set the cardapp password
|
||||||
`$ ./s_create_db`
|
`$ ./s_create_db`
|
||||||
|
|
||||||
### boltcard service install
|
### boltcard service install
|
||||||
`$ sudo cp boltcard.service /etc/systemd/system/boltcard.service`
|
|
||||||
`$ ./s_build`
|
`$ ./s_build`
|
||||||
`$ sudo systemctl enable boltcard`
|
`$ sudo systemctl enable boltcard`
|
||||||
`$ sudo systemctl status boltcard`
|
`$ sudo systemctl status boltcard`
|
||||||
|
|
|
||||||
79
email.go
Normal file
79
email.go
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
|
"github.com/aws/aws-sdk-go/service/ses"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/ses-example-send-email.html
|
||||||
|
|
||||||
|
func send_email(recipient string, subject string, htmlBody string, textBody string) {
|
||||||
|
|
||||||
|
aws_ses_id := os.Getenv("AWS_SES_ID")
|
||||||
|
aws_ses_secret := os.Getenv("AWS_SES_SECRET")
|
||||||
|
sender := os.Getenv("AWS_SES_EMAIL_FROM")
|
||||||
|
|
||||||
|
sess, err := session.NewSession(&aws.Config{
|
||||||
|
Region: aws.String("us-east-1"),
|
||||||
|
Credentials: credentials.NewStaticCredentials(aws_ses_id, aws_ses_secret, ""),
|
||||||
|
})
|
||||||
|
|
||||||
|
svc := ses.New(sess)
|
||||||
|
|
||||||
|
charSet := "UTF-8"
|
||||||
|
|
||||||
|
input := &ses.SendEmailInput{
|
||||||
|
Destination: &ses.Destination{
|
||||||
|
CcAddresses: []*string{},
|
||||||
|
ToAddresses: []*string{
|
||||||
|
aws.String(recipient),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Message: &ses.Message{
|
||||||
|
Body: &ses.Body{
|
||||||
|
Html: &ses.Content{
|
||||||
|
Charset: aws.String(charSet),
|
||||||
|
Data: aws.String(htmlBody),
|
||||||
|
},
|
||||||
|
Text: &ses.Content{
|
||||||
|
Charset: aws.String(charSet),
|
||||||
|
Data: aws.String(textBody),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Subject: &ses.Content{
|
||||||
|
Charset: aws.String(charSet),
|
||||||
|
Data: aws.String(subject),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Source: aws.String(sender),
|
||||||
|
//ConfigurationSetName: aws.String(ConfigurationSet),
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := svc.SendEmail(input)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if aerr, ok := err.(awserr.Error); ok {
|
||||||
|
switch aerr.Code() {
|
||||||
|
case ses.ErrCodeMessageRejected:
|
||||||
|
log.Warn(ses.ErrCodeMessageRejected, aerr.Error())
|
||||||
|
case ses.ErrCodeMailFromDomainNotVerifiedException:
|
||||||
|
log.Warn(ses.ErrCodeMailFromDomainNotVerifiedException, aerr.Error())
|
||||||
|
case ses.ErrCodeConfigurationSetDoesNotExistException:
|
||||||
|
log.Warn(ses.ErrCodeConfigurationSetDoesNotExistException, aerr.Error())
|
||||||
|
default:
|
||||||
|
log.Warn(aerr.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Warn(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{"result": result}).Info("email sent")
|
||||||
|
}
|
||||||
22
go.mod
22
go.mod
|
|
@ -2,11 +2,23 @@ module github.com/boltcard/boltcard
|
||||||
|
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1
|
||||||
|
github.com/fiatjaf/ln-decodepay v1.4.0
|
||||||
|
github.com/gorilla/mux v1.8.0
|
||||||
|
github.com/lib/pq v1.10.6
|
||||||
|
github.com/lightningnetwork/lnd v0.15.1-beta.rc2
|
||||||
|
github.com/sirupsen/logrus v1.9.0
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
|
google.golang.org/grpc v1.49.0
|
||||||
|
gopkg.in/macaroon.v2 v2.1.0
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||||
github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 // indirect
|
|
||||||
github.com/aead/siphash v1.0.1 // indirect
|
github.com/aead/siphash v1.0.1 // indirect
|
||||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||||
|
github.com/aws/aws-sdk-go v1.44.101 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/btcsuite/btcd v0.23.1 // indirect
|
github.com/btcsuite/btcd v0.23.1 // indirect
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.2.1 // indirect
|
github.com/btcsuite/btcd/btcec/v2 v2.2.1 // indirect
|
||||||
|
|
@ -35,7 +47,6 @@ require (
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||||
github.com/dvyukov/go-fuzz v0.0.0-20220726122315-1d375ef9f9f6 // indirect
|
github.com/dvyukov/go-fuzz v0.0.0-20220726122315-1d375ef9f9f6 // indirect
|
||||||
github.com/fergusstrange/embedded-postgres v1.17.0 // indirect
|
github.com/fergusstrange/embedded-postgres v1.17.0 // indirect
|
||||||
github.com/fiatjaf/ln-decodepay v1.4.0 // indirect
|
|
||||||
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
|
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
|
||||||
github.com/go-errors/errors v1.4.2 // indirect
|
github.com/go-errors/errors v1.4.2 // indirect
|
||||||
github.com/go-logr/logr v1.2.3 // indirect
|
github.com/go-logr/logr v1.2.3 // indirect
|
||||||
|
|
@ -59,6 +70,7 @@ require (
|
||||||
github.com/jackc/pgtype v1.12.0 // indirect
|
github.com/jackc/pgtype v1.12.0 // indirect
|
||||||
github.com/jackc/pgx/v4 v4.17.0 // indirect
|
github.com/jackc/pgx/v4 v4.17.0 // indirect
|
||||||
github.com/jessevdk/go-flags v1.5.0 // indirect
|
github.com/jessevdk/go-flags v1.5.0 // indirect
|
||||||
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
github.com/jonboulle/clockwork v0.3.0 // indirect
|
github.com/jonboulle/clockwork v0.3.0 // indirect
|
||||||
github.com/jrick/logrotate v1.0.0 // indirect
|
github.com/jrick/logrotate v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
|
@ -66,11 +78,9 @@ require (
|
||||||
github.com/kkdai/bstream v1.0.0 // indirect
|
github.com/kkdai/bstream v1.0.0 // indirect
|
||||||
github.com/klauspost/compress v1.15.9 // indirect
|
github.com/klauspost/compress v1.15.9 // indirect
|
||||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||||
github.com/lib/pq v1.10.6 // indirect
|
|
||||||
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect
|
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect
|
||||||
github.com/lightninglabs/neutrino v0.14.2 // indirect
|
github.com/lightninglabs/neutrino v0.14.2 // indirect
|
||||||
github.com/lightningnetwork/lightning-onion v1.2.0 // indirect
|
github.com/lightningnetwork/lightning-onion v1.2.0 // indirect
|
||||||
github.com/lightningnetwork/lnd v0.15.1-beta.rc2 // indirect
|
|
||||||
github.com/lightningnetwork/lnd/clock v1.1.0 // indirect
|
github.com/lightningnetwork/lnd/clock v1.1.0 // indirect
|
||||||
github.com/lightningnetwork/lnd/healthcheck v1.2.2 // indirect
|
github.com/lightningnetwork/lnd/healthcheck v1.2.2 // indirect
|
||||||
github.com/lightningnetwork/lnd/kvdb v1.3.1 // indirect
|
github.com/lightningnetwork/lnd/kvdb v1.3.1 // indirect
|
||||||
|
|
@ -93,8 +103,6 @@ require (
|
||||||
github.com/prometheus/common v0.37.0 // indirect
|
github.com/prometheus/common v0.37.0 // indirect
|
||||||
github.com/prometheus/procfs v0.8.0 // indirect
|
github.com/prometheus/procfs v0.8.0 // indirect
|
||||||
github.com/rogpeppe/fastuuid v1.2.0 // indirect
|
github.com/rogpeppe/fastuuid v1.2.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
|
||||||
github.com/soheilhy/cmux v0.1.5 // indirect
|
github.com/soheilhy/cmux v0.1.5 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/stretchr/objx v0.4.0 // indirect
|
github.com/stretchr/objx v0.4.0 // indirect
|
||||||
|
|
@ -136,11 +144,9 @@ require (
|
||||||
golang.org/x/tools v0.1.12 // indirect
|
golang.org/x/tools v0.1.12 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
|
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
|
||||||
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc // indirect
|
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc // indirect
|
||||||
google.golang.org/grpc v1.49.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
gopkg.in/errgo.v1 v1.0.1 // indirect
|
gopkg.in/errgo.v1 v1.0.1 // indirect
|
||||||
gopkg.in/macaroon-bakery.v2 v2.3.0 // indirect
|
gopkg.in/macaroon-bakery.v2 v2.3.0 // indirect
|
||||||
gopkg.in/macaroon.v2 v2.1.0 // indirect
|
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|
|
||||||
211
lightning.go
211
lightning.go
|
|
@ -6,12 +6,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
log "github.com/sirupsen/logrus"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
"crypto/sha256"
|
||||||
|
|
||||||
lnrpc "github.com/lightningnetwork/lnd/lnrpc"
|
lnrpc "github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
invoicesrpc "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
||||||
routerrpc "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
routerrpc "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
@ -32,7 +34,7 @@ func newCreds(bytes []byte) rpcCreds {
|
||||||
return creds
|
return creds
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRouterClient(hostname string, port int, tlsFile, macaroonFile string) routerrpc.RouterClient {
|
func getGrpcConn(hostname string, port int, tlsFile, macaroonFile string) *grpc.ClientConn {
|
||||||
macaroonBytes, err := ioutil.ReadFile(macaroonFile)
|
macaroonBytes, err := ioutil.ReadFile(macaroonFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Cannot read macaroon file .. ", err)
|
log.Println("Cannot read macaroon file .. ", err)
|
||||||
|
|
@ -65,49 +67,72 @@ func getRouterClient(hostname string, port int, tlsFile, macaroonFile string) ro
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return routerrpc.NewRouterClient(connection)
|
return connection
|
||||||
}
|
}
|
||||||
|
|
||||||
func pay_invoice(invoice string) (payment_status string, failure_reason string, return_err error) {
|
// https://api.lightning.community/?shell#addinvoice
|
||||||
|
|
||||||
payment_status = ""
|
func add_invoice(amount_sat int64, metadata string) (payment_request string, r_hash []byte, return_err error) {
|
||||||
failure_reason = ""
|
|
||||||
return_err = nil
|
|
||||||
|
|
||||||
// SendPaymentV2
|
|
||||||
|
|
||||||
ctx2, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// get node parameters from environment variables
|
|
||||||
|
|
||||||
ln_port, err := strconv.Atoi(os.Getenv("LN_PORT"))
|
ln_port, err := strconv.Atoi(os.Getenv("LN_PORT"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return_err = err
|
return "", nil, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r_client := getRouterClient(
|
dh := sha256.Sum256([]byte(metadata))
|
||||||
|
|
||||||
|
connection := getGrpcConn(
|
||||||
os.Getenv("LN_HOST"),
|
os.Getenv("LN_HOST"),
|
||||||
ln_port,
|
ln_port,
|
||||||
os.Getenv("LN_TLS_FILE"),
|
os.Getenv("LN_TLS_FILE"),
|
||||||
os.Getenv("LN_MACAROON_FILE"))
|
os.Getenv("LN_MACAROON_FILE"))
|
||||||
|
|
||||||
fee_limit_sat_str := os.Getenv("FEE_LIMIT_SAT")
|
l_client := lnrpc.NewLightningClient(connection)
|
||||||
fee_limit_sat, err := strconv.ParseInt(fee_limit_sat_str, 10, 64)
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
result, err := l_client.AddInvoice(ctx, &lnrpc.Invoice {
|
||||||
|
Value: amount_sat,
|
||||||
|
DescriptionHash: dh[:],
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return_err = err
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.PaymentRequest, result.RHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://api.lightning.community/?shell#subscribesingleinvoice
|
||||||
|
|
||||||
|
func monitor_invoice_state(r_hash []byte) () {
|
||||||
|
|
||||||
|
// SubscribeSingleInvoice
|
||||||
|
|
||||||
|
// get node parameters from environment variables
|
||||||
|
|
||||||
|
ln_port, err := strconv.Atoi(os.Getenv("LN_PORT"))
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stream, err := r_client.SendPaymentV2(ctx2, &routerrpc.SendPaymentRequest{
|
connection := getGrpcConn(
|
||||||
PaymentRequest: invoice,
|
os.Getenv("LN_HOST"),
|
||||||
NoInflightUpdates: true,
|
ln_port,
|
||||||
TimeoutSeconds: 30,
|
os.Getenv("LN_TLS_FILE"),
|
||||||
FeeLimitSat: fee_limit_sat})
|
os.Getenv("LN_MACAROON_FILE"))
|
||||||
|
|
||||||
|
i_client := invoicesrpc.NewInvoicesClient(connection)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
stream, err := i_client.SubscribeSingleInvoice(ctx, &invoicesrpc.SubscribeSingleInvoiceRequest{
|
||||||
|
RHash: r_hash})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return_err = err
|
log.WithFields(log.Fields{"r_hash": hex.EncodeToString(r_hash)}).Warn(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,13 +144,141 @@ func pay_invoice(invoice string) (payment_status string, failure_reason string,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return_err = err
|
log.WithFields(log.Fields{"r_hash": hex.EncodeToString(r_hash)}).Warn(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
payment_status = lnrpc.Payment_PaymentStatus_name[int32(update.Status)]
|
invoice_state := lnrpc.Invoice_InvoiceState_name[int32(update.State)]
|
||||||
failure_reason = lnrpc.PaymentFailureReason_name[int32(update.FailureReason)]
|
|
||||||
|
log.WithFields(
|
||||||
|
log.Fields{
|
||||||
|
"r_hash": hex.EncodeToString(r_hash),
|
||||||
|
"invoice_state": invoice_state,
|
||||||
|
},).Info("invoice state updated")
|
||||||
|
|
||||||
|
db_update_receipt_state(hex.EncodeToString(r_hash), invoice_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connection.Close()
|
||||||
|
|
||||||
|
// send email
|
||||||
|
|
||||||
|
card_id, err := db_get_card_id_for_r_hash(hex.EncodeToString(r_hash))
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"r_hash": hex.EncodeToString(r_hash)}).Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{"r_hash": hex.EncodeToString(r_hash), "card_id": card_id}).Debug("card found")
|
||||||
|
|
||||||
|
c, err := db_get_card_from_card_id(card_id)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"r_hash": hex.EncodeToString(r_hash)}).Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.email_enable != "Y" {
|
||||||
|
log.Debug("email is not enabled for the card")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go send_email(c.email_address, "bolt card receipt", "html body", "text body")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://api.lightning.community/?shell#sendpaymentv2
|
||||||
|
|
||||||
|
func pay_invoice(card_payment_id int, invoice string) {
|
||||||
|
|
||||||
|
// SendPaymentV2
|
||||||
|
|
||||||
|
// get node parameters from environment variables
|
||||||
|
|
||||||
|
ln_port, err := strconv.Atoi(os.Getenv("LN_PORT"))
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": card_payment_id}).Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
connection := getGrpcConn(
|
||||||
|
os.Getenv("LN_HOST"),
|
||||||
|
ln_port,
|
||||||
|
os.Getenv("LN_TLS_FILE"),
|
||||||
|
os.Getenv("LN_MACAROON_FILE"))
|
||||||
|
|
||||||
|
r_client := routerrpc.NewRouterClient(connection)
|
||||||
|
|
||||||
|
fee_limit_sat_str := os.Getenv("FEE_LIMIT_SAT")
|
||||||
|
fee_limit_sat, err := strconv.ParseInt(fee_limit_sat_str, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": card_payment_id}).Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
stream, err := r_client.SendPaymentV2(ctx, &routerrpc.SendPaymentRequest{
|
||||||
|
PaymentRequest: invoice,
|
||||||
|
NoInflightUpdates: true,
|
||||||
|
TimeoutSeconds: 30,
|
||||||
|
FeeLimitSat: fee_limit_sat})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": card_payment_id}).Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
update, err := stream.Recv()
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": card_payment_id}).Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payment_status := lnrpc.Payment_PaymentStatus_name[int32(update.Status)]
|
||||||
|
failure_reason := lnrpc.PaymentFailureReason_name[int32(update.FailureReason)]
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": card_payment_id}).Info("payment failure reason : ", failure_reason)
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": card_payment_id}).Info("payment status : ", payment_status)
|
||||||
|
|
||||||
|
err = db_update_payment_status(card_payment_id, payment_status, failure_reason)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": card_payment_id}).Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.Close()
|
||||||
|
|
||||||
|
// send email
|
||||||
|
|
||||||
|
card_id, err := db_get_card_id_for_card_payment_id(card_payment_id)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": card_payment_id}).Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": card_payment_id, "card_id": card_id}).Debug("card found")
|
||||||
|
|
||||||
|
c, err := db_get_card_from_card_id(card_id)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": card_payment_id}).Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.email_enable != "Y" {
|
||||||
|
log.Debug("email is not enabled for the card")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go send_email(c.email_address, "bolt card payment", "html body", "text body")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
81
lnurlp_callback.go
Normal file
81
lnurlp_callback.go
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
func lnurlp_callback(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if os.Getenv("FUNCTION_LNURLP") != "ENABLE" {
|
||||||
|
log.Debug("LNURLp function is not enabled")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := mux.Vars(r)["name"]
|
||||||
|
amount := r.URL.Query().Get("amount")
|
||||||
|
|
||||||
|
card_id, err := db_get_card_id_for_name(name)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("card name not found")
|
||||||
|
write_error(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(
|
||||||
|
log.Fields{
|
||||||
|
"url_path": r.URL.Path,
|
||||||
|
"name": name,
|
||||||
|
"card_id": card_id,
|
||||||
|
"amount": amount,
|
||||||
|
"req.Host": r.Host,
|
||||||
|
},).Info("lnurlp_callback")
|
||||||
|
|
||||||
|
domain := os.Getenv("HOST_DOMAIN")
|
||||||
|
if r.Host != domain {
|
||||||
|
log.Warn("wrong host domain")
|
||||||
|
write_error(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
amount_msat, err := strconv.ParseInt(amount, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("amount is not a valid integer")
|
||||||
|
write_error(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
amount_sat := amount_msat / 1000;
|
||||||
|
|
||||||
|
metadata := "[[\"text/identifier\",\"" + name + "@" + domain + "\"],[\"text/plain\",\"bolt card deposit\"]]"
|
||||||
|
pr, r_hash, err := add_invoice(amount_sat, metadata)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("could not add_invoice")
|
||||||
|
write_error(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db_insert_receipt(card_id, pr, hex.EncodeToString(r_hash), amount_msat)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
write_error(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go monitor_invoice_state(r_hash)
|
||||||
|
|
||||||
|
log.Debug("sending 'status OK' response");
|
||||||
|
|
||||||
|
jsonData := []byte(`{` +
|
||||||
|
`"status":"OK",` +
|
||||||
|
`"routes":[],` +
|
||||||
|
`"pr":"` + pr + `"` +
|
||||||
|
`}`)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write(jsonData)
|
||||||
|
}
|
||||||
63
lnurlp_request.go
Normal file
63
lnurlp_request.go
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func lnurlp_response(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if os.Getenv("FUNCTION_LNURLP") != "ENABLE" {
|
||||||
|
log.Debug("LNURLp function is not enabled")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := mux.Vars(r)["name"]
|
||||||
|
|
||||||
|
log.WithFields(
|
||||||
|
log.Fields{
|
||||||
|
"url_path": r.URL.Path,
|
||||||
|
"name": name,
|
||||||
|
"r.Host": r.Host,
|
||||||
|
},).Info("lnurlp_response")
|
||||||
|
|
||||||
|
// look up domain in env vars (HOST_DOMAIN)
|
||||||
|
|
||||||
|
domain := os.Getenv("HOST_DOMAIN")
|
||||||
|
if r.Host != domain {
|
||||||
|
log.Warn("wrong host domain")
|
||||||
|
write_error(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// look up name in database (table cards, field card_name)
|
||||||
|
|
||||||
|
card_count, err := db_get_card_count_for_name_lnurlp(name)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("could not get card count for name")
|
||||||
|
write_error(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if card_count != 1 {
|
||||||
|
log.Info("not one enabled card with that name")
|
||||||
|
write_error(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata := "[[\\\"text/identifier\\\",\\\"" + name + "@" + domain + "\\\"],[\\\"text/plain\\\",\\\"bolt card deposit\\\"]]"
|
||||||
|
|
||||||
|
jsonData := []byte(`{"status":"OK",` +
|
||||||
|
`"callback":"https://` + domain + `/lnurlp/` + name + `",` +
|
||||||
|
`"tag":"payRequest",` +
|
||||||
|
`"maxSendable":1000000000,` +
|
||||||
|
`"minSendable":1000,` +
|
||||||
|
`"metadata":"` + metadata + `",` +
|
||||||
|
`"commentAllowed":0` +
|
||||||
|
`}`)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write(jsonData)
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,13 @@ import (
|
||||||
|
|
||||||
func lnurlw_callback(w http.ResponseWriter, req *http.Request) {
|
func lnurlw_callback(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
|
env_host_domain := os.Getenv("HOST_DOMAIN")
|
||||||
|
if req.Host != env_host_domain {
|
||||||
|
log.Warn("wrong host domain")
|
||||||
|
write_error(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
url := req.URL.RequestURI()
|
url := req.URL.RequestURI()
|
||||||
log.WithFields(log.Fields{"url": url}).Debug("cb request")
|
log.WithFields(log.Fields{"url": url}).Debug("cb request")
|
||||||
|
|
||||||
|
|
@ -123,34 +130,16 @@ func lnurlw_callback(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
payment_status, failure_reason, err := pay_invoice(param_pr)
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Warn(err)
|
|
||||||
write_error(w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if failure_reason != "FAILURE_REASON_NONE" {
|
|
||||||
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Info("payment failure reason : ", failure_reason)
|
|
||||||
write_error(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Info("payment status : ", payment_status)
|
|
||||||
|
|
||||||
// store result in database
|
|
||||||
err = db_update_payment_status(p.card_payment_id, payment_status, failure_reason)
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Warn(err)
|
|
||||||
write_error(w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/fiatjaf/lnurl-rfc/blob/luds/03.md
|
// https://github.com/fiatjaf/lnurl-rfc/blob/luds/03.md
|
||||||
//
|
//
|
||||||
// LN SERVICE sends a {"status": "OK"} or
|
// LN SERVICE sends a {"status": "OK"} or
|
||||||
// {"status": "ERROR", "reason": "error details..."}
|
// {"status": "ERROR", "reason": "error details..."}
|
||||||
// JSON response and then attempts to pay the invoices asynchronously.
|
// JSON response and then attempts to pay the invoices asynchronously.
|
||||||
|
|
||||||
|
go pay_invoice(p.card_payment_id, param_pr)
|
||||||
|
|
||||||
|
log.Debug("sending 'status OK' response");
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
jsonData := []byte(`{"status":"OK"}`)
|
jsonData := []byte(`{"status":"OK"}`)
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ func parse_request(req *http.Request) (int, error) {
|
||||||
card_count, err := db_get_card_count_for_uid(uid_str)
|
card_count, err := db_get_card_count_for_uid(uid_str)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, errors.New("could not get card records count")
|
return 0, errors.New("could not get card count for uid")
|
||||||
}
|
}
|
||||||
|
|
||||||
if card_count == 0 {
|
if card_count == 0 {
|
||||||
|
|
@ -205,8 +205,8 @@ func parse_request(req *http.Request) (int, error) {
|
||||||
|
|
||||||
// check if card is enabled
|
// check if card is enabled
|
||||||
|
|
||||||
if c.enable_flag != "Y" {
|
if c.lnurlw_enable != "Y" {
|
||||||
return 0, errors.New("card enable is not set to Y")
|
return 0, errors.New("card lnurlw enable is not set to Y")
|
||||||
}
|
}
|
||||||
|
|
||||||
// check cmac
|
// check cmac
|
||||||
|
|
@ -246,6 +246,13 @@ func parse_request(req *http.Request) (int, error) {
|
||||||
|
|
||||||
func lnurlw_response(w http.ResponseWriter, req *http.Request) {
|
func lnurlw_response(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
|
env_host_domain := os.Getenv("HOST_DOMAIN")
|
||||||
|
if req.Host != env_host_domain {
|
||||||
|
log.Warn("wrong host domain")
|
||||||
|
write_error(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
card_id, err := parse_request(req)
|
card_id, err := parse_request(req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
29
main.go
29
main.go
|
|
@ -2,10 +2,14 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var router = mux.NewRouter()
|
||||||
|
|
||||||
func write_error(w http.ResponseWriter) {
|
func write_error(w http.ResponseWriter) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
@ -34,17 +38,26 @@ func main() {
|
||||||
DisableHTMLEscape: true,
|
DisableHTMLEscape: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
// createboltcard
|
||||||
|
router.Path("/new").Methods("GET").HandlerFunc(new_card_request)
|
||||||
mux.HandleFunc("/new", new_card_request)
|
// lnurlw for pos
|
||||||
mux.HandleFunc("/ln", lnurlw_response)
|
router.Path("/ln").Methods("GET").HandlerFunc(lnurlw_response)
|
||||||
mux.HandleFunc("/cb", lnurlw_callback)
|
router.Path("/cb").Methods("GET").HandlerFunc(lnurlw_callback)
|
||||||
|
// lnurlp for lightning address lnurlp
|
||||||
|
router.Path("/.well-known/lnurlp/{name}").Methods("GET").HandlerFunc(lnurlp_response)
|
||||||
|
router.Path("/lnurlp/{name}").Methods("GET").HandlerFunc(lnurlp_callback)
|
||||||
|
|
||||||
port := os.Getenv("HOST_PORT")
|
port := os.Getenv("HOST_PORT")
|
||||||
if len(port) == 0 {
|
if len(port) == 0 {
|
||||||
port = "9000"
|
port = "9000"
|
||||||
}
|
}
|
||||||
|
|
||||||
err := http.ListenAndServe(":" + port, mux)
|
srv := &http.Server {
|
||||||
log.Fatal(err)
|
Handler: router,
|
||||||
|
Addr: ":" + port, // consider adding host
|
||||||
|
WriteTimeout: 30 * time.Second,
|
||||||
|
ReadTimeout: 30 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.ListenAndServe()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
s_build
1
s_build
|
|
@ -1,4 +1,5 @@
|
||||||
go build
|
go build
|
||||||
|
sudo cp boltcard.service /etc/systemd/system/boltcard.service
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
sudo systemctl stop boltcard
|
sudo systemctl stop boltcard
|
||||||
sudo systemctl start boltcard
|
sudo systemctl start boltcard
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue