316 lines
9.3 KiB
Go
316 lines
9.3 KiB
Go
//go:build unit
|
|
|
|
package service
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/payment"
|
|
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
|
"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.Equal(t, "INVALID_RESUME_TOKEN", infraerrors.Reason(err))
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func TestVerifyOrderPublicRejectsBlankOutTradeNo(t *testing.T) {
|
|
svc := &PaymentService{
|
|
entClient: newPaymentConfigServiceTestClient(t),
|
|
}
|
|
|
|
_, err := svc.VerifyOrderPublic(context.Background(), " ")
|
|
require.Error(t, err)
|
|
require.Equal(t, "INVALID_OUT_TRADE_NO", infraerrors.Reason(err))
|
|
}
|