Redirect after payment

This commit is contained in:
ekzyis 2023-09-09 22:52:51 +02:00
parent 921623ee4d
commit e5f5759871
7 changed files with 62 additions and 9 deletions

1
go.mod
View File

@ -92,6 +92,7 @@ require (
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c // indirect
google.golang.org/grpc v1.24.0 // indirect
gopkg.in/errgo.v1 v1.0.1 // indirect
gopkg.in/guregu/null.v4 v4.0.0 // indirect
gopkg.in/macaroon-bakery.v2 v2.0.1 // indirect
gopkg.in/macaroon.v2 v2.1.0 // indirect
gopkg.in/yaml.v2 v2.2.3 // indirect

2
go.sum
View File

@ -358,6 +358,8 @@ gopkg.in/errgo.v1 v1.0.1 h1:oQFRXzZ7CkBGdm1XZm/EbQYaYNNEElNBOd09M6cqNso=
gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/guregu/null.v4 v4.0.0 h1:1Wm3S1WEA2I26Kq+6vcW+w0gcDo44YKYD7YIEJNHDjg=
gopkg.in/guregu/null.v4 v4.0.0/go.mod h1:YoQhUrADuG3i9WqesrCmpNRwm1ypAgSHYqoOcTu/JrI=
gopkg.in/macaroon-bakery.v2 v2.0.1 h1:0N1TlEdfLP4HXNCg7MQUMp5XwvOoxk+oe9Owr2cpvsc=
gopkg.in/macaroon-bakery.v2 v2.0.1/go.mod h1:B4/T17l+ZWGwxFSZQmlBwp25x+og7OkhETfr3S9MbIA=
gopkg.in/macaroon.v2 v2.0.0/go.mod h1:+I6LnTMkm/uV5ew/0nsulNjL16SK4+C8yDmRUzHR17I=

View File

@ -9,6 +9,7 @@
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="stylesheet" href="/index.css" />
<link rel="stylesheet" href="/market.css" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#091833" />
{{ if eq .ENV "development" }}
@ -38,7 +39,13 @@
|_| \___/_____|</pre>
</strong>
</code>
<div class="font-mono mb-1">Payment Required</div>
<div class="font-mono mb-1">
<div class="mb-1">Payment Required</div>
<div id="paid" class="yes" hidden>
<div>Paid</div>
<div id="countdown">Redirecting in 3 ...</div>
</div>
</div>
<div id="qr">
<a href="lightning:{{.lnurl}}">
<img class="m-auto mb-1" src="data:image/png;base64,{{.qr}}" width="100%" />
@ -55,7 +62,28 @@
</div>
</div>
</body>
{{ if .RedirectAfterPayment }}
<script>
const invoiceId = "{{.Invoice.Id}}"
const paid = document.querySelector("#paid")
const countdown = document.querySelector("#countdown")
const interval = setInterval(async () => {
const body = await fetch(`/api/invoice/${invoiceId}`)
.then((r) => r.json())
.catch(console.error)
if (body.ConfirmedAt) {
paid.removeAttribute("hidden")
clearInterval(interval)
let timer = 2
const redirect = setInterval(() => {
countdown.textContent = `Redirecting in ${timer--} ...`
if (timer === -1) {
window.location.href = "https://{{.PUBLIC_URL}}/market/{{.MarketId}}";
}
}, 1000)
}
}, 1000)
</script>
{{ end }}
</html>

View File

@ -112,7 +112,7 @@ func (db *DB) CreateInvoice(invoice *Invoice) error {
func (db *DB) FetchInvoice(invoiceId string, invoice *Invoice) error {
if err := db.QueryRow(""+
"SELECT id, pubkey, msats, preimage, hash, bolt11, created_at, expires_at FROM invoices WHERE id = $1", invoiceId).Scan(&invoice.Id, &invoice.Pubkey, &invoice.Msats, &invoice.Preimage, &invoice.PaymentHash, &invoice.PaymentRequest, &invoice.CreatedAt, &invoice.ExpiresAt); err != nil {
"SELECT id, pubkey, msats, preimage, hash, bolt11, created_at, expires_at, confirmed_at, held_since FROM invoices WHERE id = $1", invoiceId).Scan(&invoice.Id, &invoice.Pubkey, &invoice.Msats, &invoice.Preimage, &invoice.PaymentHash, &invoice.PaymentRequest, &invoice.CreatedAt, &invoice.ExpiresAt, &invoice.ConfirmedAt, &invoice.HeldSince); err != nil {
return err
}
return nil

View File

@ -122,3 +122,19 @@ func invoice(c echo.Context) error {
}
return c.Render(http.StatusOK, "invoice.html", data)
}
func invoiceStatus(c echo.Context) error {
invoiceId := c.Param("id")
var invoice Invoice
if err := db.FetchInvoice(invoiceId, &invoice); err == sql.ErrNoRows {
return echo.NewHTTPError(http.StatusNotFound, "Not Found")
} else if err != nil {
return err
}
session := c.Get("session").(Session)
if invoice.Pubkey != session.Pubkey {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
invoice.Preimage = ""
return c.JSON(http.StatusOK, invoice)
}

View File

@ -8,6 +8,7 @@ import (
"time"
"github.com/labstack/echo/v4"
"gopkg.in/guregu/null.v4"
)
type Market struct {
@ -43,8 +44,8 @@ type Invoice struct {
PaymentHash string
CreatedAt time.Time
ExpiresAt time.Time
ConfirmedAt time.Time
HeldSince time.Time
ConfirmedAt null.Time
HeldSince null.Time
}
func costFunction(b float64, q1 float64, q2 float64) float64 {
@ -63,6 +64,7 @@ func BinaryLMSR(invariant int, funding int, q1 int, q2 int, dq1 int) float64 {
}
func order(c echo.Context) error {
marketId := c.Param("id")
// (TBD) Step 0: If SELL order, check share balance of user
// (TBD) Step 1: respond with HODL invoice
o := new(Order)
@ -86,11 +88,14 @@ func order(c echo.Context) error {
}
go lnd.CheckInvoice(invoice.PaymentHash)
data := map[string]any{
"session": c.Get("session"),
"ENV": ENV,
"lnurl": invoice.PaymentRequest,
"qr": qr,
"Invoice": invoice,
"session": c.Get("session"),
"ENV": ENV,
"lnurl": invoice.PaymentRequest,
"qr": qr,
"Invoice": invoice,
"RedirectAfterPayment": true,
"PUBLIC_URL": PUBLIC_URL,
"MarketId": marketId,
}
return c.Render(http.StatusPaymentRequired, "invoice.html", data)
// Step 2: After payment, confirm order if no matching order was found

View File

@ -66,6 +66,7 @@ func main() {
e.GET("/market/:id", sessionGuard(market))
e.POST("/market/:id/order", sessionGuard(order))
e.GET("/invoice/:id", sessionGuard(invoice))
e.GET("/api/invoice/:id", sessionGuard(invoiceStatus))
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: "${time_custom} ${method} ${uri} ${status}\n",
CustomTimeFormat: "2006-01-02 15:04:05.00000-0700",