Files
sub2api/backend/internal/service/payment_resume_lookup_test.go

305 lines
8.9 KiB
Go

//go:build unit
package service
import (
"context"
"testing"
"time"
"github.com/Wei-Shaw/sub2api/internal/payment"
"github.com/stretchr/testify/require"
)
type paymentResumeLookupProvider struct {
queryCount int
}
func (p *paymentResumeLookupProvider) Name() string { return "resume-lookup-provider" }
func (p *paymentResumeLookupProvider) ProviderKey() string { return payment.TypeAlipay }
func (p *paymentResumeLookupProvider) SupportedTypes() []payment.PaymentType {
return []payment.PaymentType{payment.TypeAlipay}
}
func (p *paymentResumeLookupProvider) CreatePayment(context.Context, payment.CreatePaymentRequest) (*payment.CreatePaymentResponse, error) {
panic("unexpected call")
}
func (p *paymentResumeLookupProvider) QueryOrder(context.Context, string) (*payment.QueryOrderResponse, error) {
p.queryCount++
return &payment.QueryOrderResponse{Status: payment.ProviderStatusPending}, nil
}
func (p *paymentResumeLookupProvider) VerifyNotification(context.Context, string, map[string]string) (*payment.PaymentNotification, error) {
panic("unexpected call")
}
func (p *paymentResumeLookupProvider) Refund(context.Context, payment.RefundRequest) (*payment.RefundResponse, error) {
panic("unexpected call")
}
func TestGetPublicOrderByResumeTokenReturnsMatchingOrder(t *testing.T) {
ctx := context.Background()
client := newPaymentConfigServiceTestClient(t)
user, err := client.User.Create().
SetEmail("resume@example.com").
SetPasswordHash("hash").
SetUsername("resume-user").
Save(ctx)
require.NoError(t, err)
instanceID := "12"
providerKey := payment.TypeEasyPay
order, err := client.PaymentOrder.Create().
SetUserID(user.ID).
SetUserEmail(user.Email).
SetUserName(user.Username).
SetAmount(88).
SetPayAmount(88).
SetFeeRate(0).
SetRechargeCode("RESUME-ORDER").
SetOutTradeNo("sub2_resume_lookup").
SetPaymentType(payment.TypeAlipay).
SetPaymentTradeNo("trade-1").
SetOrderType(payment.OrderTypeBalance).
SetStatus(OrderStatusPending).
SetExpiresAt(time.Now().Add(time.Hour)).
SetClientIP("127.0.0.1").
SetSrcHost("api.example.com").
SetProviderInstanceID(instanceID).
SetProviderKey(providerKey).
Save(ctx)
require.NoError(t, err)
resumeSvc := NewPaymentResumeService([]byte("0123456789abcdef0123456789abcdef"))
token, err := resumeSvc.CreateToken(ResumeTokenClaims{
OrderID: order.ID,
UserID: user.ID,
ProviderInstanceID: instanceID,
ProviderKey: providerKey,
PaymentType: payment.TypeAlipay,
CanonicalReturnURL: "https://app.example.com/payment/result",
})
require.NoError(t, err)
svc := &PaymentService{
entClient: client,
resumeService: resumeSvc,
}
got, err := svc.GetPublicOrderByResumeToken(ctx, token)
require.NoError(t, err)
require.Equal(t, order.ID, got.ID)
}
func TestGetPublicOrderByResumeTokenRejectsSnapshotMismatch(t *testing.T) {
ctx := context.Background()
client := newPaymentConfigServiceTestClient(t)
user, err := client.User.Create().
SetEmail("resume-mismatch@example.com").
SetPasswordHash("hash").
SetUsername("resume-mismatch-user").
Save(ctx)
require.NoError(t, err)
order, err := client.PaymentOrder.Create().
SetUserID(user.ID).
SetUserEmail(user.Email).
SetUserName(user.Username).
SetAmount(88).
SetPayAmount(88).
SetFeeRate(0).
SetRechargeCode("RESUME-MISMATCH").
SetOutTradeNo("sub2_resume_lookup_mismatch").
SetPaymentType(payment.TypeAlipay).
SetPaymentTradeNo("trade-2").
SetOrderType(payment.OrderTypeBalance).
SetStatus(OrderStatusPending).
SetExpiresAt(time.Now().Add(time.Hour)).
SetClientIP("127.0.0.1").
SetSrcHost("api.example.com").
SetProviderInstanceID("12").
SetProviderKey(payment.TypeEasyPay).
Save(ctx)
require.NoError(t, err)
resumeSvc := NewPaymentResumeService([]byte("0123456789abcdef0123456789abcdef"))
token, err := resumeSvc.CreateToken(ResumeTokenClaims{
OrderID: order.ID,
UserID: user.ID,
ProviderInstanceID: "99",
ProviderKey: payment.TypeEasyPay,
PaymentType: payment.TypeAlipay,
CanonicalReturnURL: "https://app.example.com/payment/result",
})
require.NoError(t, err)
svc := &PaymentService{
entClient: client,
resumeService: resumeSvc,
}
_, err = svc.GetPublicOrderByResumeToken(ctx, token)
require.Error(t, err)
require.Contains(t, err.Error(), "resume token")
}
func TestGetPublicOrderByResumeTokenUsesSnapshotAuthorityWhenColumnsDiffer(t *testing.T) {
ctx := context.Background()
client := newPaymentConfigServiceTestClient(t)
user, err := client.User.Create().
SetEmail("resume-snapshot-authority@example.com").
SetPasswordHash("hash").
SetUsername("resume-snapshot-authority-user").
Save(ctx)
require.NoError(t, err)
order, err := client.PaymentOrder.Create().
SetUserID(user.ID).
SetUserEmail(user.Email).
SetUserName(user.Username).
SetAmount(88).
SetPayAmount(88).
SetFeeRate(0).
SetRechargeCode("RESUME-SNAPSHOT-AUTHORITY").
SetOutTradeNo("sub2_resume_snapshot_authority").
SetPaymentType(payment.TypeAlipay).
SetPaymentTradeNo("trade-snapshot-authority").
SetOrderType(payment.OrderTypeBalance).
SetStatus(OrderStatusPending).
SetExpiresAt(time.Now().Add(time.Hour)).
SetClientIP("127.0.0.1").
SetSrcHost("api.example.com").
SetProviderInstanceID("legacy-column-instance").
SetProviderKey(payment.TypeAlipay).
SetProviderSnapshot(map[string]any{
"schema_version": 2,
"provider_instance_id": "snapshot-instance",
"provider_key": payment.TypeEasyPay,
}).
Save(ctx)
require.NoError(t, err)
resumeSvc := NewPaymentResumeService([]byte("0123456789abcdef0123456789abcdef"))
token, err := resumeSvc.CreateToken(ResumeTokenClaims{
OrderID: order.ID,
UserID: user.ID,
ProviderInstanceID: "snapshot-instance",
ProviderKey: payment.TypeEasyPay,
PaymentType: payment.TypeAlipay,
CanonicalReturnURL: "https://app.example.com/payment/result",
})
require.NoError(t, err)
svc := &PaymentService{
entClient: client,
resumeService: resumeSvc,
}
got, err := svc.GetPublicOrderByResumeToken(ctx, token)
require.NoError(t, err)
require.Equal(t, order.ID, got.ID)
}
func TestGetPublicOrderByResumeTokenChecksUpstreamForPendingOrder(t *testing.T) {
ctx := context.Background()
client := newPaymentConfigServiceTestClient(t)
user, err := client.User.Create().
SetEmail("resume-refresh@example.com").
SetPasswordHash("hash").
SetUsername("resume-refresh-user").
Save(ctx)
require.NoError(t, err)
order, err := client.PaymentOrder.Create().
SetUserID(user.ID).
SetUserEmail(user.Email).
SetUserName(user.Username).
SetAmount(88).
SetPayAmount(88).
SetFeeRate(0).
SetRechargeCode("RESUME-PENDING").
SetOutTradeNo("sub2_resume_lookup_pending").
SetPaymentType(payment.TypeAlipay).
SetPaymentTradeNo("trade-pending").
SetOrderType(payment.OrderTypeBalance).
SetStatus(OrderStatusPending).
SetExpiresAt(time.Now().Add(time.Hour)).
SetClientIP("127.0.0.1").
SetSrcHost("api.example.com").
Save(ctx)
require.NoError(t, err)
resumeSvc := NewPaymentResumeService([]byte("0123456789abcdef0123456789abcdef"))
token, err := resumeSvc.CreateToken(ResumeTokenClaims{
OrderID: order.ID,
UserID: user.ID,
PaymentType: payment.TypeAlipay,
CanonicalReturnURL: "https://app.example.com/payment/result",
})
require.NoError(t, err)
registry := payment.NewRegistry()
provider := &paymentResumeLookupProvider{}
registry.Register(provider)
svc := &PaymentService{
entClient: client,
registry: registry,
resumeService: resumeSvc,
providersLoaded: true,
}
got, err := svc.GetPublicOrderByResumeToken(ctx, token)
require.NoError(t, err)
require.Equal(t, order.ID, got.ID)
require.Equal(t, 1, provider.queryCount)
}
func TestVerifyOrderPublicDoesNotCheckUpstreamForPendingOrder(t *testing.T) {
ctx := context.Background()
client := newPaymentConfigServiceTestClient(t)
user, err := client.User.Create().
SetEmail("public-verify@example.com").
SetPasswordHash("hash").
SetUsername("public-verify-user").
Save(ctx)
require.NoError(t, err)
order, err := client.PaymentOrder.Create().
SetUserID(user.ID).
SetUserEmail(user.Email).
SetUserName(user.Username).
SetAmount(88).
SetPayAmount(88).
SetFeeRate(0).
SetRechargeCode("PUBLIC-VERIFY").
SetOutTradeNo("sub2_public_verify_pending").
SetPaymentType(payment.TypeAlipay).
SetPaymentTradeNo("trade-public-verify").
SetOrderType(payment.OrderTypeBalance).
SetStatus(OrderStatusPending).
SetExpiresAt(time.Now().Add(time.Hour)).
SetClientIP("127.0.0.1").
SetSrcHost("api.example.com").
Save(ctx)
require.NoError(t, err)
registry := payment.NewRegistry()
provider := &paymentResumeLookupProvider{}
registry.Register(provider)
svc := &PaymentService{
entClient: client,
registry: registry,
providersLoaded: true,
}
got, err := svc.VerifyOrderPublic(ctx, order.OutTradeNo)
require.NoError(t, err)
require.Equal(t, order.ID, got.ID)
require.Equal(t, 0, provider.queryCount)
}