Compare commits
1 Commits
sca-abando
...
feat/sca-r
| Author | SHA1 | Date | |
|---|---|---|---|
| 8401e3d163 |
@@ -7,7 +7,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/stripe/stripe-go/v78"
|
||||
@@ -24,35 +24,43 @@ import (
|
||||
stripeSession "github.com/stripe/stripe-go/v78/checkout/session"
|
||||
)
|
||||
|
||||
// payRedirects maps short tokens to full Stripe checkout URLs.
|
||||
var (
|
||||
payRedirects = map[string]string{}
|
||||
payRedirectsMu sync.Mutex
|
||||
)
|
||||
const RedirectTTL = 10 * time.Minute
|
||||
|
||||
func storeRedirect(stripeURL string) (string, error) {
|
||||
func storeRedirect(stripeURL, purpose string) (string, error) {
|
||||
b := make([]byte, 16)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
token := hex.EncodeToString(b)
|
||||
payRedirectsMu.Lock()
|
||||
payRedirects[token] = stripeURL
|
||||
payRedirectsMu.Unlock()
|
||||
now := time.Now()
|
||||
rec := models.PayRedirect{
|
||||
Token: token,
|
||||
URL: stripeURL,
|
||||
Purpose: purpose,
|
||||
CreatedAt: now,
|
||||
ExpiresAt: now.Add(RedirectTTL),
|
||||
}
|
||||
if err := database.GetDB().Create(&rec).Error; err != nil {
|
||||
return "", err
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// PayRedirect resolves a short token and redirects to the Stripe checkout URL.
|
||||
func PayRedirect(w http.ResponseWriter, r *http.Request) {
|
||||
token := chi.URLParam(r, "token")
|
||||
payRedirectsMu.Lock()
|
||||
url, ok := payRedirects[token]
|
||||
payRedirectsMu.Unlock()
|
||||
if !ok {
|
||||
db := database.GetDB().WithContext(r.Context())
|
||||
var rec models.PayRedirect
|
||||
if err := db.Where("token = ?", token).First(&rec).Error; err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, url, http.StatusFound)
|
||||
if time.Now().After(rec.ExpiresAt) {
|
||||
db.Where("token = ?", rec.Token).Delete(&models.PayRedirect{})
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, rec.URL, http.StatusFound)
|
||||
}
|
||||
|
||||
// CardHandler handles saved payment method CRUD.
|
||||
@@ -168,7 +176,7 @@ func (h *CardHandler) CollectCard(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
token, err := storeRedirect(sess.URL)
|
||||
token, err := storeRedirect(sess.URL, models.RedirectPurposeAddCard)
|
||||
if err != nil {
|
||||
utils.RespondError(w, http.StatusInternalServerError, "INTERNAL_ERROR", "failed to generate redirect token", nil)
|
||||
return
|
||||
|
||||
@@ -541,7 +541,7 @@ func (h *CartHandler) respondRequiresAction(w http.ResponseWriter, order *models
|
||||
return
|
||||
}
|
||||
|
||||
token, err := storeRedirect(pi.NextAction.RedirectToURL.URL)
|
||||
token, err := storeRedirect(pi.NextAction.RedirectToURL.URL, models.RedirectPurposeCheckout3DS)
|
||||
if err != nil {
|
||||
utils.RespondError(w, http.StatusInternalServerError, "INTERNAL_ERROR", "failed to allocate redirect token", nil)
|
||||
return
|
||||
|
||||
@@ -3,6 +3,7 @@ package database
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"terminalShop/pkg/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@@ -24,6 +25,7 @@ func Migrate(db *gorm.DB) error {
|
||||
&models.OrderItem{}, // Line items within orders
|
||||
&models.Address{}, // Saved addresses
|
||||
&models.OrderEvent{},
|
||||
&models.PayRedirect{}, // short-token -> stripe url store (10 min TTL)
|
||||
); err != nil {
|
||||
return fmt.Errorf("migration failed: %w", err)
|
||||
}
|
||||
|
||||
23
pkg/models/pay_redirect.go
Normal file
23
pkg/models/pay_redirect.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// PayRedirect maps a short token to a stripe hosted url with a TTL.
|
||||
// survives api restarts so a customer mid-3Ds authentication still
|
||||
// lands on the bank-hosted challenge page after a deploy or crash
|
||||
type PayRedirect struct {
|
||||
Token string `gorm:"primaryKey;size:64" json:"token"`
|
||||
URL string `gorm:"type:text;not null" json:"url"`
|
||||
Purpose string `gorm:"size:32;not null" json:"purpose"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ExpiresAt time.Time `gorm:"index" json:"expires_at"`
|
||||
}
|
||||
|
||||
const (
|
||||
RedirectPurposeCheckout3DS = "checkout_3ds"
|
||||
RedirectPurposeAddCard = "add_card"
|
||||
)
|
||||
|
||||
func (PayRedirect) TableName() string {
|
||||
return "pay_redirects"
|
||||
}
|
||||
Reference in New Issue
Block a user