fix: add 2-minute timeout to browser payment poll so abandoned stripe checkout sessions stop polling
Some checks failed
Deploy / deploy (push) Has been cancelled
Some checks failed
Deploy / deploy (push) Has been cancelled
This commit is contained in:
@@ -171,6 +171,8 @@ type paymentState struct {
|
||||
collectURL *string
|
||||
collectCardCount int
|
||||
collectBaseline time.Time
|
||||
collectDeadline time.Time
|
||||
collectTimeOut bool
|
||||
viewport viewport.Model
|
||||
viewportReady bool
|
||||
}
|
||||
@@ -944,6 +946,7 @@ func (m Model) collectCardCmd() tea.Cmd {
|
||||
}
|
||||
|
||||
const ordersPollInterval = 10 * time.Second
|
||||
const cardPollTimeout = 2 * time.Minute
|
||||
|
||||
type OrdersPollTickMsg struct{}
|
||||
|
||||
@@ -953,14 +956,17 @@ func (m Model) pollOrderCmd() tea.Cmd {
|
||||
})
|
||||
}
|
||||
|
||||
func (m Model) pollCardsCmd(baseline time.Time, baselineCount int) tea.Cmd {
|
||||
return tea.Tick(time.Second, func(_ time.Time) tea.Msg {
|
||||
func (m Model) pollCardsCmd(baseline time.Time, baselineCount int, deadline time.Time) tea.Cmd {
|
||||
return tea.Tick(time.Second, func(now time.Time) tea.Msg {
|
||||
if now.After(deadline) {
|
||||
return PollPaymentTimeoutMsg{}
|
||||
}
|
||||
if m.APIClient == nil {
|
||||
return PollPaymentStatusMsg{Baseline: baseline, BaselineCount: baselineCount}
|
||||
return PollPaymentStatusMsg{Baseline: baseline, BaselineCount: baselineCount, Deadline: deadline}
|
||||
}
|
||||
cards, err := m.APIClient.GetCards()
|
||||
if err != nil {
|
||||
return PollPaymentStatusMsg{Baseline: baseline, BaselineCount: baselineCount}
|
||||
return PollPaymentStatusMsg{Baseline: baseline, BaselineCount: baselineCount, Deadline: deadline}
|
||||
}
|
||||
var newest time.Time
|
||||
for _, card := range cards {
|
||||
@@ -971,6 +977,6 @@ func (m Model) pollCardsCmd(baseline time.Time, baselineCount int) tea.Cmd {
|
||||
if newest.After(baseline) {
|
||||
return PollPaymentCompleteMsg{Cards: cards, Duplicate: len(cards) == baselineCount}
|
||||
}
|
||||
return PollPaymentStatusMsg{Baseline: baseline, BaselineCount: baselineCount}
|
||||
return PollPaymentStatusMsg{Baseline: baseline, BaselineCount: baselineCount, Deadline: deadline}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ type PollPaymentInitMsg struct {
|
||||
type PollPaymentStatusMsg struct {
|
||||
Baseline time.Time
|
||||
BaselineCount int
|
||||
Deadline time.Time
|
||||
}
|
||||
|
||||
// PollPaymentCompleteMsg is sent when the card count increases, meaning the
|
||||
@@ -71,6 +72,8 @@ type PollPaymentCompleteMsg struct {
|
||||
Duplicate bool
|
||||
}
|
||||
|
||||
type PollPaymentTimeoutMsg struct{}
|
||||
|
||||
// buildPaymentForm creates the huh form bound to state's fields.
|
||||
// No field-level validators so Tab/Enter move freely.
|
||||
func (m Model) buildPaymentForm(state *PaymentFormState) *huh.Form {
|
||||
@@ -370,6 +373,10 @@ func (m Model) renderPaymentHttpsView() string {
|
||||
baseStyle := m.theme.TextLabel()
|
||||
accentStyle := m.theme.TextHighlight()
|
||||
|
||||
if m.payment.collectTimeOut {
|
||||
return m.theme.TextError().Bold(true).Render("payment link timed out") + "\n\n" + baseStyle.Render("no card saved after 2 minutes.") + "\n" + baseStyle.Render("press ") + accentStyle.Render("esc") + baseStyle.Render(" to go back and try again")
|
||||
}
|
||||
|
||||
if m.payment.collectURL == nil {
|
||||
return baseStyle.Render(" generating payment link...")
|
||||
}
|
||||
@@ -439,13 +446,19 @@ func (m Model) PaymentUpdate(msg tea.Msg) (Model, tea.Cmd) {
|
||||
case PollPaymentInitMsg:
|
||||
url := msg.URL
|
||||
m.payment.collectURL = &url
|
||||
return m, m.pollCardsCmd(m.payment.collectBaseline, m.payment.collectCardCount)
|
||||
return m, m.pollCardsCmd(m.payment.collectBaseline, m.payment.collectCardCount, m.payment.collectDeadline)
|
||||
|
||||
case PollPaymentStatusMsg:
|
||||
if m.payment.view != 2 {
|
||||
return m, nil // user navigated away, stop polling
|
||||
}
|
||||
return m, m.pollCardsCmd(msg.Baseline, msg.BaselineCount)
|
||||
return m, m.pollCardsCmd(msg.Baseline, msg.BaselineCount, msg.Deadline)
|
||||
case PollPaymentTimeoutMsg:
|
||||
if m.payment.view != 2 {
|
||||
return m, nil
|
||||
}
|
||||
m.payment.collectTimeOut = true
|
||||
return m, nil
|
||||
|
||||
case PollPaymentCompleteMsg:
|
||||
if m.payment.view != 2 {
|
||||
@@ -508,6 +521,7 @@ func (m Model) PaymentUpdate(msg tea.Msg) (Model, tea.Cmd) {
|
||||
// Browser payment
|
||||
m.payment.view = 2
|
||||
m.payment.collectURL = nil
|
||||
m.payment.collectTimeOut = false
|
||||
m.payment.collectCardCount = len(m.SavedCards)
|
||||
var maxT time.Time
|
||||
for _, card := range m.SavedCards {
|
||||
@@ -516,6 +530,7 @@ func (m Model) PaymentUpdate(msg tea.Msg) (Model, tea.Cmd) {
|
||||
}
|
||||
}
|
||||
m.payment.collectBaseline = maxT
|
||||
m.payment.collectDeadline = time.Now().Add(cardPollTimeout)
|
||||
m.footer = []footerCommand{
|
||||
{key: "esc", value: "back"},
|
||||
}
|
||||
@@ -544,11 +559,12 @@ func (m Model) PaymentUpdate(msg tea.Msg) (Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// HTTPS/browser view avigation
|
||||
// HTTPS/browser view navigation
|
||||
if m.payment.view == 2 {
|
||||
if keyMsg, ok := msg.(tea.KeyMsg); ok && keyMsg.String() == "esc" {
|
||||
m.payment.view = 0
|
||||
m.payment.collectURL = nil
|
||||
m.payment.collectTimeOut = false
|
||||
m.footer = []footerCommand{
|
||||
{key: "j/k", value: "cards"},
|
||||
{key: "enter", value: "select"},
|
||||
|
||||
Reference in New Issue
Block a user