//go:build unit package service import ( "context" "strconv" "testing" "time" "github.com/Wei-Shaw/sub2api/internal/payment" infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors" "github.com/stretchr/testify/require" ) func TestValidateRefundRequestRejectsLegacyGuessedProviderInstance(t *testing.T) { ctx := context.Background() client := newPaymentConfigServiceTestClient(t) user, err := client.User.Create(). SetEmail("refund-legacy@example.com"). SetPasswordHash("hash"). SetUsername("refund-legacy-user"). Save(ctx) require.NoError(t, err) _, err = client.PaymentProviderInstance.Create(). SetProviderKey(payment.TypeAlipay). SetName("alipay-refund-instance"). SetConfig("{}"). SetSupportedTypes("alipay"). SetEnabled(true). SetAllowUserRefund(true). SetRefundEnabled(true). 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("REFUND-LEGACY-ORDER"). SetOutTradeNo("sub2_refund_legacy_order"). SetPaymentType(payment.TypeAlipay). SetPaymentTradeNo("trade-legacy-refund"). SetOrderType(payment.OrderTypeBalance). SetStatus(OrderStatusCompleted). SetExpiresAt(time.Now().Add(time.Hour)). SetPaidAt(time.Now()). SetClientIP("127.0.0.1"). SetSrcHost("api.example.com"). Save(ctx) require.NoError(t, err) svc := &PaymentService{ entClient: client, } _, err = svc.validateRefundRequest(ctx, order.ID, user.ID) require.Error(t, err) require.Equal(t, "USER_REFUND_DISABLED", infraerrors.Reason(err)) } func TestPrepareRefundRejectsLegacyGuessedProviderInstance(t *testing.T) { ctx := context.Background() client := newPaymentConfigServiceTestClient(t) user, err := client.User.Create(). SetEmail("refund-legacy-admin@example.com"). SetPasswordHash("hash"). SetUsername("refund-legacy-admin-user"). Save(ctx) require.NoError(t, err) _, err = client.PaymentProviderInstance.Create(). SetProviderKey(payment.TypeAlipay). SetName("alipay-refund-admin-instance"). SetConfig("{}"). SetSupportedTypes("alipay"). SetEnabled(true). SetAllowUserRefund(true). SetRefundEnabled(true). Save(ctx) require.NoError(t, err) order, err := client.PaymentOrder.Create(). SetUserID(user.ID). SetUserEmail(user.Email). SetUserName(user.Username). SetAmount(188). SetPayAmount(188). SetFeeRate(0). SetRechargeCode("REFUND-LEGACY-ADMIN-ORDER"). SetOutTradeNo("sub2_refund_legacy_admin_order"). SetPaymentType(payment.TypeAlipay). SetPaymentTradeNo("trade-legacy-admin-refund"). SetOrderType(payment.OrderTypeBalance). SetStatus(OrderStatusCompleted). SetExpiresAt(time.Now().Add(time.Hour)). SetPaidAt(time.Now()). SetClientIP("127.0.0.1"). SetSrcHost("api.example.com"). Save(ctx) require.NoError(t, err) svc := &PaymentService{ entClient: client, } plan, result, err := svc.PrepareRefund(ctx, order.ID, 0, "", false, false) require.Nil(t, plan) require.Nil(t, result) require.Error(t, err) require.Equal(t, "REFUND_DISABLED", infraerrors.Reason(err)) } func TestGwRefundRejectsAlipayMerchantIdentitySnapshotMismatch(t *testing.T) { ctx := context.Background() client := newPaymentConfigServiceTestClient(t) user, err := client.User.Create(). SetEmail("refund-snapshot-mismatch@example.com"). SetPasswordHash("hash"). SetUsername("refund-snapshot-mismatch-user"). Save(ctx) require.NoError(t, err) inst, err := client.PaymentProviderInstance.Create(). SetProviderKey(payment.TypeAlipay). SetName("alipay-refund-mismatch-instance"). SetConfig(encryptWebhookProviderConfig(t, map[string]string{ "appId": "runtime-alipay-app", "privateKey": "runtime-private-key", })). SetSupportedTypes("alipay"). SetEnabled(true). SetRefundEnabled(true). Save(ctx) require.NoError(t, err) instID := strconv.FormatInt(inst.ID, 10) order, err := client.PaymentOrder.Create(). SetUserID(user.ID). SetUserEmail(user.Email). SetUserName(user.Username). SetAmount(88). SetPayAmount(88). SetFeeRate(0). SetRechargeCode("REFUND-SNAPSHOT-MISMATCH-ORDER"). SetOutTradeNo("sub2_refund_snapshot_mismatch_order"). SetPaymentType(payment.TypeAlipay). SetPaymentTradeNo("trade-refund-snapshot-mismatch"). SetOrderType(payment.OrderTypeBalance). SetStatus(OrderStatusCompleted). SetExpiresAt(time.Now().Add(time.Hour)). SetPaidAt(time.Now()). SetClientIP("127.0.0.1"). SetSrcHost("api.example.com"). SetProviderInstanceID(instID). SetProviderKey(payment.TypeAlipay). SetProviderSnapshot(map[string]any{ "schema_version": 2, "provider_instance_id": instID, "provider_key": payment.TypeAlipay, "merchant_app_id": "expected-alipay-app", }). Save(ctx) require.NoError(t, err) svc := &PaymentService{ entClient: client, loadBalancer: newWebhookProviderTestLoadBalancer(client), } err = svc.gwRefund(ctx, &RefundPlan{ OrderID: order.ID, Order: order, RefundAmount: order.Amount, GatewayAmount: order.Amount, Reason: "snapshot mismatch", }) require.ErrorContains(t, err, "alipay app_id mismatch") }