Build and Publish Server / build (push) Successful in 4m30s
- 新增商业化模块:Stripe/Binance Pay 支付、邮箱登录、订单管理 - 新增数据模型:ReportProduct, ReportEntitlement, ReportSnapshot, PaymentEvent, EmailLoginCode - 新增 API:/api/commerce/checkout, /api/commerce/webhooks/*, /api/commerce/orders/* - 新增管理后台:订单列表、统计图表、统一 AdminHeader 组件 - 新增中间件测试:RateLimit, Auth, CORS 共 7 个测试用例 - 新增 AGENTS.md 项目代理指令文件 - 新增 REMINDER.md 商业化功能开发规范 - 修复 JWT 解析:添加 WithValidMethods 限制 HS256 - 修复 GetPayStatus:增加用户权限校验 - 更新 .gitignore 和 README.md
98 lines
3.9 KiB
Go
98 lines
3.9 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gouki/lunar-server/internal/config"
|
|
"github.com/gouki/lunar-server/internal/model"
|
|
)
|
|
|
|
func TestStripeCreateCheckout(t *testing.T) {
|
|
var received url.Values
|
|
server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
|
if request.Header.Get("Authorization") != "Bearer sk_test" {
|
|
t.Fatalf("missing Stripe authorization")
|
|
}
|
|
if request.Header.Get("Idempotency-Key") != "checkout-R123" {
|
|
t.Fatalf("missing idempotency key")
|
|
}
|
|
body, _ := io.ReadAll(request.Body)
|
|
received, _ = url.ParseQuery(string(body))
|
|
response.Header().Set("Content-Type", "application/json")
|
|
fmt.Fprint(response, `{"id":"cs_test_123","url":"https://checkout.stripe.test/session"}`)
|
|
}))
|
|
defer server.Close()
|
|
|
|
provider := NewStripeProvider(config.StripeConfig{SecretKey: "sk_test", APIBaseURL: server.URL})
|
|
order := &model.Order{OrderNo: "R123", UserID: 9, Amount: 499, Currency: "USD", CustomerEmail: "buyer@example.com", ProductName: "完整报告", ReportID: "ZM-ABCDEFG"}
|
|
result, err := provider.CreateCheckout(context.Background(), order, "https://example.test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.ProviderOrderID != "cs_test_123" || result.CheckoutURL == "" {
|
|
t.Fatalf("unexpected checkout result: %#v", result)
|
|
}
|
|
if received.Get("metadata[order_no]") != order.OrderNo || received.Get("line_items[0][price_data][unit_amount]") != "499" {
|
|
t.Fatalf("unexpected Stripe form: %#v", received)
|
|
}
|
|
}
|
|
|
|
func TestStripeWebhookSignature(t *testing.T) {
|
|
now := time.Unix(1760000000, 0)
|
|
raw := []byte(`{"id":"evt_123","type":"checkout.session.completed","data":{"object":{"id":"cs_test_123","payment_intent":"pi_123","payment_status":"paid","amount_total":499,"currency":"usd","metadata":{"order_no":"R123"}}}}`)
|
|
mac := hmac.New(sha256.New, []byte("whsec_test"))
|
|
mac.Write([]byte(fmt.Sprintf("%d.%s", now.Unix(), raw)))
|
|
signature := fmt.Sprintf("t=%d,v1=%s", now.Unix(), hex.EncodeToString(mac.Sum(nil)))
|
|
provider := NewStripeProvider(config.StripeConfig{WebhookSecret: "whsec_test"})
|
|
event, err := provider.ParseWebhook(raw, signature, now)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if event.Status != "paid" || event.OrderNo != "R123" || event.TransactionID != "pi_123" {
|
|
t.Fatalf("unexpected event: %#v", event)
|
|
}
|
|
if _, err := provider.ParseWebhook(raw, signature+"bad", now); err == nil {
|
|
t.Fatal("expected invalid signature to fail")
|
|
}
|
|
}
|
|
|
|
func TestBinancePayCreateCheckoutSignature(t *testing.T) {
|
|
secret := "binance-secret"
|
|
server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
|
body, _ := io.ReadAll(request.Body)
|
|
timestamp := request.Header.Get("BinancePay-Timestamp")
|
|
nonce := request.Header.Get("BinancePay-Nonce")
|
|
mac := hmac.New(sha512.New, []byte(secret))
|
|
mac.Write([]byte(timestamp + "\n" + nonce + "\n" + string(body) + "\n"))
|
|
expected := strings.ToUpper(hex.EncodeToString(mac.Sum(nil)))
|
|
if !hmac.Equal([]byte(expected), []byte(request.Header.Get("BinancePay-Signature"))) {
|
|
t.Fatal("invalid Binance Pay signature")
|
|
}
|
|
response.Header().Set("Content-Type", "application/json")
|
|
fmt.Fprint(response, `{"status":"SUCCESS","code":"000000","data":{"prepayId":"123456","universalUrl":"https://app.binance.test/pay"}}`)
|
|
}))
|
|
defer server.Close()
|
|
|
|
provider := NewBinancePayProvider(config.BinancePayConfig{APIKey: "api-key", SecretKey: secret, APIBaseURL: server.URL})
|
|
order := &model.Order{OrderNo: "R123", ProviderAmount: "5.00", Currency: "USDT", ProductName: "完整报告", ReportID: "ZM-ABCDEFG"}
|
|
result, err := provider.CreateCheckout(context.Background(), order, "https://example.test", "https://api.example.test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.ProviderOrderID != "123456" || result.CheckoutURL == "" {
|
|
t.Fatalf("unexpected checkout result: %#v", result)
|
|
}
|
|
}
|