fix: Waffo webhook 支持 GET 探测,避免浏览器打开 404

仪表盘/浏览器用 GET 校验 URL 时返回 ready;支付事件仍走 POST。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Logan.LiandCursor committed 2026-08-12 15:57:37 +08:00
1 parent ac31cc059a
commit f00ea7e873
5 files changed
+16 -5

No files matched your search

+1 -1
View File
@@ -49,7 +49,7 @@ WAFFO_STORE_SLUG=opsxshell-pwsi6m4n
WAFFO_PRODUCT_ID=PROD_0pcnzzT4nalEkkcdyIUoD5 WAFFO_PRODUCT_ID=PROD_0pcnzzT4nalEkkcdyIUoD5
WAFFO_PRODUCT_ID_YEARLY=PROD_2WmkUVoYWt0NN8OVEyC6n3 WAFFO_PRODUCT_ID_YEARLY=PROD_2WmkUVoYWt0NN8OVEyC6n3
WAFFO_PRODUCT_ID_PERPETUAL=PROD_0pcnzzT4nalEkkcdyIUoD5 WAFFO_PRODUCT_ID_PERPETUAL=PROD_0pcnzzT4nalEkkcdyIUoD5
WAFFO_CURRENCY=CNY WAFFO_CURRENCY=USD
# test | live(test 时自动附加 test=true) # test | live(test 时自动附加 test=true)
WAFFO_ENVIRONMENT=test WAFFO_ENVIRONMENT=test
# 支付成功回跳(桌面端 deep link;CreateOrder/Checkout 的 return_url 可覆盖) # 支付成功回跳(桌面端 deep link;CreateOrder/Checkout 的 return_url 可覆盖)
@@ -113,6 +113,16 @@ func (ctrl *OrderController) PaymentWebhook(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "Webhook processed"}) c.JSON(http.StatusOK, gin.H{"code": 0, "message": "Webhook processed"})
} }
// WaffoWebhookReady GET/HEAD probe for dashboard URL checks (browser opens as GET). // Logan.Li
func (ctrl *OrderController) WaffoWebhookReady(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "waffo webhook ready",
"methods": []string{"POST"},
"hint": "Payment events must POST JSON with order_no / orderNo",
})
}
// WaffoWebhook accepts loosely-shaped Waffo payment events and maps to PaymentWebhook. // Logan.Li // WaffoWebhook accepts loosely-shaped Waffo payment events and maps to PaymentWebhook. // Logan.Li
func (ctrl *OrderController) WaffoWebhook(c *gin.Context) { func (ctrl *OrderController) WaffoWebhook(c *gin.Context) {
body, err := io.ReadAll(c.Request.Body) body, err := io.ReadAll(c.Request.Body)
+1 -1
View File
@@ -31,7 +31,7 @@ func LoadWaffoConfig() WaffoConfig {
} }
currency := strings.TrimSpace(os.Getenv("WAFFO_CURRENCY")) currency := strings.TrimSpace(os.Getenv("WAFFO_CURRENCY"))
if currency == "" { if currency == "" {
currency = "CNY" currency = "USD"
} }
defaultProduct := strings.TrimSpace(os.Getenv("WAFFO_PRODUCT_ID")) defaultProduct := strings.TrimSpace(os.Getenv("WAFFO_PRODUCT_ID"))
yearly := strings.TrimSpace(os.Getenv("WAFFO_PRODUCT_ID_YEARLY")) yearly := strings.TrimSpace(os.Getenv("WAFFO_PRODUCT_ID_YEARLY"))
@@ -11,7 +11,7 @@ func TestBuildCheckoutURL_YearlySubscription(t *testing.T) {
StoreSlug: "opsxshell-pwsi6m4n", StoreSlug: "opsxshell-pwsi6m4n",
ProductIDYearly: "PROD_2WmkUVoYWt0NN8OVEyC6n3", ProductIDYearly: "PROD_2WmkUVoYWt0NN8OVEyC6n3",
ProductIDPerpetual: "PROD_0pcnzzT4nalEkkcdyIUoD5", ProductIDPerpetual: "PROD_0pcnzzT4nalEkkcdyIUoD5",
Currency: "CNY", Currency: "USD",
Environment: "test", Environment: "test",
SuccessURL: "opsxshell://pro/activated", SuccessURL: "opsxshell://pro/activated",
} }
@@ -30,7 +30,7 @@ func TestBuildCheckoutURL_YearlySubscription(t *testing.T) {
if q.Get("type") != "subscription" { if q.Get("type") != "subscription" {
t.Fatalf("type=%s want subscription", q.Get("type")) t.Fatalf("type=%s want subscription", q.Get("type"))
} }
if q.Get("currency") != "CNY" { if q.Get("currency") != "USD" {
t.Fatalf("currency=%s", q.Get("currency")) t.Fatalf("currency=%s", q.Get("currency"))
} }
if q.Get("test") != "true" { if q.Get("test") != "true" {
@@ -43,7 +43,7 @@ func TestBuildCheckoutURL_PerpetualOnetime(t *testing.T) {
StoreSlug: "opsxshell-pwsi6m4n", StoreSlug: "opsxshell-pwsi6m4n",
ProductIDYearly: "PROD_2WmkUVoYWt0NN8OVEyC6n3", ProductIDYearly: "PROD_2WmkUVoYWt0NN8OVEyC6n3",
ProductIDPerpetual: "PROD_0pcnzzT4nalEkkcdyIUoD5", ProductIDPerpetual: "PROD_0pcnzzT4nalEkkcdyIUoD5",
Currency: "CNY", Currency: "USD",
Environment: "test", Environment: "test",
SuccessURL: "opsxshell://pro/activated", SuccessURL: "opsxshell://pro/activated",
} }
+1
View File
@@ -109,6 +109,7 @@ func SetupRouter(db *gorm.DB, cfg *config.Config) *gin.Engine {
register(clientAPI, "POST", "/checkout", orderController.Checkout) register(clientAPI, "POST", "/checkout", orderController.Checkout)
register(clientAPI, "POST", "/purchase/claim", orderController.ClaimPurchase) register(clientAPI, "POST", "/purchase/claim", orderController.ClaimPurchase)
register(clientAPI, "POST", "/webhooks/payment", orderController.PaymentWebhook) register(clientAPI, "POST", "/webhooks/payment", orderController.PaymentWebhook)
register(clientAPI, "GET", "/webhooks/waffo", orderController.WaffoWebhookReady)
register(clientAPI, "POST", "/webhooks/waffo", orderController.WaffoWebhook) register(clientAPI, "POST", "/webhooks/waffo", orderController.WaffoWebhook)
register(clientAPI, "POST", "/purchase/check", orderController.CheckDuplicatePurchase) register(clientAPI, "POST", "/purchase/check", orderController.CheckDuplicatePurchase)
register(clientAPI, "POST", "/purchase/status", orderController.CheckPurchaseStatus) register(clientAPI, "POST", "/purchase/status", orderController.CheckPurchaseStatus)