so the ain was that older paid orders were getting buried in the #orders channel in slack and i would just forget about them. Pin/unpin from earlier helped, but if i missed pinning one or the channel got noisy, it was still easy to lose track. this adds a 24h cron that queries any paid order older than 24h with no label yet and dumps them all into one #orders message. Each line shows the order id, how old it is, total, where it is going, and a permalink back to the orders slack thread so i can hop in and hit Buy Label Ports the idea from terminal.shops cron/unshipped.ts but out stuch state is different( we auto-promote paid->shipped on label-buy so PRE_TRANSIT-stuck can not happen), so the query is "paid + no tracking number + >24h" instead. NO orders get a "Bring blocked" tag since i dont have the norwegian org set up yet and can not actually ship those today. Also fires once 40s after startup so a deploy does not black out the reminder for a full day.
102 lines
2.9 KiB
Go
102 lines
2.9 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"terminalShop/pkg/database"
|
|
"terminalShop/pkg/models"
|
|
"terminalShop/pkg/notify"
|
|
|
|
"github.com/stripe/stripe-go/v78"
|
|
"github.com/stripe/stripe-go/v78/paymentintent"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func reconcileLog() *slog.Logger { return slog.With("component", "reconcile") }
|
|
|
|
// ReconcileOrders finds pending orders with no Stripe ID older than 10 minutes
|
|
// and checks whether Stripe has a succeeded PaymentIntent for them. This fixes
|
|
// the case where the server crashed after charging but before the DB transaction
|
|
// completed.
|
|
func ReconcileOrders(stripeKey string) {
|
|
stripe.Key = stripeKey
|
|
db := database.GetDB()
|
|
|
|
var orders []models.Order
|
|
cutoff := time.Now().Add(-10 * time.Minute)
|
|
db.Where("status = ? AND stripe_payment_id = '' AND created_at < ?",
|
|
models.OrderStatusPending, cutoff).Find(&orders)
|
|
|
|
if len(orders) == 0 {
|
|
return
|
|
}
|
|
|
|
reconcileLog().Info("checking pending orders", "count", len(orders))
|
|
|
|
for _, order := range orders {
|
|
params := &stripe.PaymentIntentSearchParams{
|
|
SearchParams: stripe.SearchParams{
|
|
Query: fmt.Sprintf("metadata['order_id']:'%d' AND status:'succeeded'", order.ID),
|
|
},
|
|
}
|
|
|
|
iter := paymentintent.Search(params)
|
|
for iter.Next() {
|
|
pi := iter.PaymentIntent()
|
|
reconcileLog().Warn("found succeeded payment intent for unrecorded order — patching",
|
|
"order_id", order.ID, "pi", pi.ID)
|
|
|
|
txErr := db.Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Model(&order).Updates(map[string]interface{}{
|
|
"status": models.OrderStatusPaid,
|
|
"stripe_payment_id": pi.ID,
|
|
}).Error; err != nil {
|
|
return err
|
|
}
|
|
var cart models.Cart
|
|
if err := tx.Where("user_id = ?", order.UserID).First(&cart).Error; err != nil {
|
|
return nil // cart already gone, nothing to clear
|
|
}
|
|
if err := tx.Where("cart_id = ?", cart.ID).Delete(&models.CartItem{}).Error; err != nil {
|
|
return err
|
|
}
|
|
return tx.Model(&cart).Updates(map[string]interface{}{
|
|
"address_id": nil,
|
|
"card_id": nil,
|
|
}).Error
|
|
})
|
|
if txErr != nil {
|
|
reconcileLog().Error("failed to patch order", "order_id", order.ID, "error", txErr)
|
|
}
|
|
}
|
|
if err := iter.Err(); err != nil {
|
|
reconcileLog().Error("stripe search error", "order_id", order.ID, "error", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ReconcileUnshipped finds paid order orlder than 24h with no tracking
|
|
// number and posts single Slack reminder. Pure read + notif
|
|
func ReconcileUnshipped() {
|
|
db := database.GetDB()
|
|
|
|
var orders []models.Order
|
|
cutoff := time.Now().Add(-24 * time.Hour)
|
|
err := db.Where(
|
|
"status = ? AND (tracking_number = '' OR tracking_number IS NULL) AND created_at < ?",
|
|
models.OrderStatusPaid, cutoff,
|
|
).Order("created_at ASC").Find(&orders).Error
|
|
if err != nil {
|
|
reconcileLog().Error("unshipped query failed", "error", err)
|
|
return
|
|
}
|
|
if len(orders) == 0 {
|
|
return
|
|
}
|
|
|
|
reconcileLog().Info("unshipped orders found", "count", len(orders))
|
|
notify.SlackUnshippedReminder(orders)
|
|
}
|