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) } }