Merge pull request #1572 from touwaeriol/feat/payment-system-v2

feat(payment): add complete payment system with multi-provider support
This commit is contained in:
Wesley Liddick 2026-04-11 19:07:31 +08:00 committed by GitHub
commit 97f14b7a08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
174 changed files with 43085 additions and 428 deletions

View File

@ -28,3 +28,10 @@ exceptions:
mitigation: "No user-controlled template strings; plan to migrate to native JS alternatives"
expires_on: "2026-07-02"
owner: "security@your-domain"
- package: axios
advisory: "GHSA-3p68-rc4w-qgx5"
severity: critical
reason: "NO_PROXY bypass not exploitable; all API calls go to known endpoints via server-side proxy"
mitigation: "Proxy configuration not user-controlled; upgrade when axios releases fix"
expires_on: "2026-07-10"
owner: "security@your-domain"

View File

@ -13,6 +13,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/handler"
"github.com/Wei-Shaw/sub2api/internal/payment"
"github.com/Wei-Shaw/sub2api/internal/repository"
"github.com/Wei-Shaw/sub2api/internal/server"
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
@ -41,6 +42,13 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
// Server layer ProviderSet
server.ProviderSet,
// Payment providers
payment.ProvideRegistry,
payment.ProvideEncryptionKey,
payment.ProvideDefaultLoadBalancer,
service.ProvidePaymentConfigService,
service.ProvidePaymentOrderExpiryService,
// Privacy client factory for OpenAI training opt-out
providePrivacyClientFactory,
@ -76,7 +84,6 @@ func provideCleanup(
opsCleanup *service.OpsCleanupService,
opsScheduledReport *service.OpsScheduledReportService,
opsSystemLogSink *service.OpsSystemLogSink,
soraMediaCleanup *service.SoraMediaCleanupService,
schedulerSnapshot *service.SchedulerSnapshotService,
tokenRefresh *service.TokenRefreshService,
accountExpiry *service.AccountExpiryService,
@ -95,6 +102,7 @@ func provideCleanup(
openAIGateway *service.OpenAIGatewayService,
scheduledTestRunner *service.ScheduledTestRunnerService,
backupSvc *service.BackupService,
paymentOrderExpiry *service.PaymentOrderExpiryService,
) func() {
return func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
@ -125,12 +133,6 @@ func provideCleanup(
}
return nil
}},
{"SoraMediaCleanupService", func() error {
if soraMediaCleanup != nil {
soraMediaCleanup.Stop()
}
return nil
}},
{"OpsAlertEvaluatorService", func() error {
if opsAlertEvaluator != nil {
opsAlertEvaluator.Stop()
@ -237,6 +239,12 @@ func provideCleanup(
}
return nil
}},
{"PaymentOrderExpiryService", func() error {
if paymentOrderExpiry != nil {
paymentOrderExpiry.Stop()
}
return nil
}},
}
infraSteps := []cleanupStep{

View File

@ -12,6 +12,7 @@ import (
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/handler"
"github.com/Wei-Shaw/sub2api/internal/handler/admin"
"github.com/Wei-Shaw/sub2api/internal/payment"
"github.com/Wei-Shaw/sub2api/internal/repository"
"github.com/Wei-Shaw/sub2api/internal/server"
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
@ -72,6 +73,15 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
userService := service.NewUserService(userRepository, apiKeyAuthCacheInvalidator, billingCache)
redeemCache := repository.NewRedeemCache(redisClient)
redeemService := service.NewRedeemService(redeemCodeRepository, userRepository, subscriptionService, redeemCache, billingCacheService, client, apiKeyAuthCacheInvalidator)
registry := payment.ProvideRegistry()
encryptionKey, err := payment.ProvideEncryptionKey(configConfig)
if err != nil {
return nil, err
}
defaultLoadBalancer := payment.ProvideDefaultLoadBalancer(client, encryptionKey)
paymentConfigService := service.ProvidePaymentConfigService(client, settingRepository, encryptionKey)
paymentService := service.NewPaymentService(client, registry, defaultLoadBalancer, redeemService, subscriptionService, paymentConfigService, userRepository, groupRepository)
paymentOrderExpiryService := service.ProvidePaymentOrderExpiryService(paymentService)
secretEncryptor, err := repository.NewAESEncryptor(configConfig)
if err != nil {
return nil, err
@ -183,7 +193,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
geminiMessagesCompatService := service.NewGeminiMessagesCompatService(accountRepository, groupRepository, gatewayCache, schedulerSnapshotService, geminiTokenProvider, rateLimitService, httpUpstream, antigravityGatewayService, configConfig)
opsSystemLogSink := service.ProvideOpsSystemLogSink(opsRepository)
opsService := service.NewOpsService(opsRepository, settingRepository, configConfig, accountRepository, userRepository, concurrencyService, gatewayService, openAIGatewayService, geminiMessagesCompatService, antigravityGatewayService, opsSystemLogSink)
settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService, opsService)
settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService, opsService, paymentConfigService, paymentService)
opsHandler := admin.NewOpsHandler(opsService)
updateCache := repository.NewUpdateCache(redisClient)
gitHubReleaseClient := repository.ProvideGitHubReleaseClient(configConfig)
@ -211,7 +221,8 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
scheduledTestService := service.ProvideScheduledTestService(scheduledTestPlanRepository, scheduledTestResultRepository)
scheduledTestHandler := admin.NewScheduledTestHandler(scheduledTestService)
channelHandler := admin.NewChannelHandler(channelService, billingService)
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, backupHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, tlsFingerprintProfileHandler, adminAPIKeyHandler, scheduledTestHandler, channelHandler)
adminPaymentHandler := admin.NewPaymentHandler(paymentService, paymentConfigService)
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, backupHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, tlsFingerprintProfileHandler, adminAPIKeyHandler, scheduledTestHandler, channelHandler, adminPaymentHandler)
usageRecordWorkerPool := service.NewUsageRecordWorkerPool(configConfig)
userMsgQueueCache := repository.NewUserMsgQueueCache(redisClient)
userMessageQueueService := service.ProvideUserMessageQueueService(userMsgQueueCache, rpmCache, configConfig)
@ -219,9 +230,11 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
openAIGatewayHandler := handler.NewOpenAIGatewayHandler(openAIGatewayService, concurrencyService, billingCacheService, apiKeyService, usageRecordWorkerPool, errorPassthroughService, configConfig)
handlerSettingHandler := handler.ProvideSettingHandler(settingService, buildInfo)
totpHandler := handler.NewTotpHandler(totpService)
handlerPaymentHandler := handler.NewPaymentHandler(paymentService, paymentConfigService, channelService)
paymentWebhookHandler := handler.NewPaymentWebhookHandler(paymentService, registry)
idempotencyCoordinator := service.ProvideIdempotencyCoordinator(idempotencyRepository, configConfig)
idempotencyCleanupService := service.ProvideIdempotencyCleanupService(idempotencyRepository, configConfig)
handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, announcementHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, handlerSettingHandler, totpHandler, idempotencyCoordinator, idempotencyCleanupService)
handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, announcementHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, handlerSettingHandler, totpHandler, handlerPaymentHandler, paymentWebhookHandler, idempotencyCoordinator, idempotencyCleanupService)
jwtAuthMiddleware := middleware.NewJWTAuthMiddleware(authService, userService)
adminAuthMiddleware := middleware.NewAdminAuthMiddleware(authService, userService, settingService)
apiKeyAuthMiddleware := middleware.NewAPIKeyAuthMiddleware(apiKeyService, subscriptionService, configConfig)
@ -236,7 +249,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
accountExpiryService := service.ProvideAccountExpiryService(accountRepository)
subscriptionExpiryService := service.ProvideSubscriptionExpiryService(userSubscriptionRepository)
scheduledTestRunnerService := service.ProvideScheduledTestRunnerService(scheduledTestPlanRepository, scheduledTestService, accountTestService, rateLimitService, configConfig)
v := provideCleanup(client, redisClient, opsMetricsCollector, opsAggregationService, opsAlertEvaluatorService, opsCleanupService, opsScheduledReportService, opsSystemLogSink, schedulerSnapshotService, tokenRefreshService, accountExpiryService, subscriptionExpiryService, usageCleanupService, idempotencyCleanupService, pricingService, emailQueueService, billingCacheService, usageRecordWorkerPool, subscriptionService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, openAIGatewayService, scheduledTestRunnerService, backupService)
v := provideCleanup(client, redisClient, opsMetricsCollector, opsAggregationService, opsAlertEvaluatorService, opsCleanupService, opsScheduledReportService, opsSystemLogSink, schedulerSnapshotService, tokenRefreshService, accountExpiryService, subscriptionExpiryService, usageCleanupService, idempotencyCleanupService, pricingService, emailQueueService, billingCacheService, usageRecordWorkerPool, subscriptionService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, openAIGatewayService, scheduledTestRunnerService, backupService, paymentOrderExpiryService)
application := &Application{
Server: httpServer,
Cleanup: v,
@ -289,6 +302,7 @@ func provideCleanup(
openAIGateway *service.OpenAIGatewayService,
scheduledTestRunner *service.ScheduledTestRunnerService,
backupSvc *service.BackupService,
paymentOrderExpiry *service.PaymentOrderExpiryService,
) func() {
return func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
@ -424,6 +438,12 @@ func provideCleanup(
}
return nil
}},
{"PaymentOrderExpiryService", func() error {
if paymentOrderExpiry != nil {
paymentOrderExpiry.Stop()
}
return nil
}},
}
infraSteps := []cleanupStep{

View File

@ -75,6 +75,7 @@ func TestProvideCleanup_WithMinimalDependencies_NoPanic(t *testing.T) {
nil, // openAIGateway
nil, // scheduledTestRunner
nil, // backupSvc
nil, // paymentOrderExpiry
)
require.NotPanics(t, func() {

View File

@ -23,12 +23,16 @@ import (
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
"github.com/Wei-Shaw/sub2api/ent/paymentauditlog"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
"github.com/Wei-Shaw/sub2api/ent/promocode"
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/ent/proxy"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
"github.com/Wei-Shaw/sub2api/ent/setting"
"github.com/Wei-Shaw/sub2api/ent/subscriptionplan"
"github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile"
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
"github.com/Wei-Shaw/sub2api/ent/usagelog"
@ -62,6 +66,12 @@ type Client struct {
Group *GroupClient
// IdempotencyRecord is the client for interacting with the IdempotencyRecord builders.
IdempotencyRecord *IdempotencyRecordClient
// PaymentAuditLog is the client for interacting with the PaymentAuditLog builders.
PaymentAuditLog *PaymentAuditLogClient
// PaymentOrder is the client for interacting with the PaymentOrder builders.
PaymentOrder *PaymentOrderClient
// PaymentProviderInstance is the client for interacting with the PaymentProviderInstance builders.
PaymentProviderInstance *PaymentProviderInstanceClient
// PromoCode is the client for interacting with the PromoCode builders.
PromoCode *PromoCodeClient
// PromoCodeUsage is the client for interacting with the PromoCodeUsage builders.
@ -74,6 +84,8 @@ type Client struct {
SecuritySecret *SecuritySecretClient
// Setting is the client for interacting with the Setting builders.
Setting *SettingClient
// SubscriptionPlan is the client for interacting with the SubscriptionPlan builders.
SubscriptionPlan *SubscriptionPlanClient
// TLSFingerprintProfile is the client for interacting with the TLSFingerprintProfile builders.
TLSFingerprintProfile *TLSFingerprintProfileClient
// UsageCleanupTask is the client for interacting with the UsageCleanupTask builders.
@ -109,12 +121,16 @@ func (c *Client) init() {
c.ErrorPassthroughRule = NewErrorPassthroughRuleClient(c.config)
c.Group = NewGroupClient(c.config)
c.IdempotencyRecord = NewIdempotencyRecordClient(c.config)
c.PaymentAuditLog = NewPaymentAuditLogClient(c.config)
c.PaymentOrder = NewPaymentOrderClient(c.config)
c.PaymentProviderInstance = NewPaymentProviderInstanceClient(c.config)
c.PromoCode = NewPromoCodeClient(c.config)
c.PromoCodeUsage = NewPromoCodeUsageClient(c.config)
c.Proxy = NewProxyClient(c.config)
c.RedeemCode = NewRedeemCodeClient(c.config)
c.SecuritySecret = NewSecuritySecretClient(c.config)
c.Setting = NewSettingClient(c.config)
c.SubscriptionPlan = NewSubscriptionPlanClient(c.config)
c.TLSFingerprintProfile = NewTLSFingerprintProfileClient(c.config)
c.UsageCleanupTask = NewUsageCleanupTaskClient(c.config)
c.UsageLog = NewUsageLogClient(c.config)
@ -223,12 +239,16 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
ErrorPassthroughRule: NewErrorPassthroughRuleClient(cfg),
Group: NewGroupClient(cfg),
IdempotencyRecord: NewIdempotencyRecordClient(cfg),
PaymentAuditLog: NewPaymentAuditLogClient(cfg),
PaymentOrder: NewPaymentOrderClient(cfg),
PaymentProviderInstance: NewPaymentProviderInstanceClient(cfg),
PromoCode: NewPromoCodeClient(cfg),
PromoCodeUsage: NewPromoCodeUsageClient(cfg),
Proxy: NewProxyClient(cfg),
RedeemCode: NewRedeemCodeClient(cfg),
SecuritySecret: NewSecuritySecretClient(cfg),
Setting: NewSettingClient(cfg),
SubscriptionPlan: NewSubscriptionPlanClient(cfg),
TLSFingerprintProfile: NewTLSFingerprintProfileClient(cfg),
UsageCleanupTask: NewUsageCleanupTaskClient(cfg),
UsageLog: NewUsageLogClient(cfg),
@ -264,12 +284,16 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
ErrorPassthroughRule: NewErrorPassthroughRuleClient(cfg),
Group: NewGroupClient(cfg),
IdempotencyRecord: NewIdempotencyRecordClient(cfg),
PaymentAuditLog: NewPaymentAuditLogClient(cfg),
PaymentOrder: NewPaymentOrderClient(cfg),
PaymentProviderInstance: NewPaymentProviderInstanceClient(cfg),
PromoCode: NewPromoCodeClient(cfg),
PromoCodeUsage: NewPromoCodeUsageClient(cfg),
Proxy: NewProxyClient(cfg),
RedeemCode: NewRedeemCodeClient(cfg),
SecuritySecret: NewSecuritySecretClient(cfg),
Setting: NewSettingClient(cfg),
SubscriptionPlan: NewSubscriptionPlanClient(cfg),
TLSFingerprintProfile: NewTLSFingerprintProfileClient(cfg),
UsageCleanupTask: NewUsageCleanupTaskClient(cfg),
UsageLog: NewUsageLogClient(cfg),
@ -308,8 +332,9 @@ func (c *Client) Close() error {
func (c *Client) Use(hooks ...Hook) {
for _, n := range []interface{ Use(...Hook) }{
c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead,
c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord, c.PromoCode,
c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting,
c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord, c.PaymentAuditLog,
c.PaymentOrder, c.PaymentProviderInstance, c.PromoCode, c.PromoCodeUsage,
c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting, c.SubscriptionPlan,
c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog, c.User,
c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
c.UserSubscription,
@ -323,8 +348,9 @@ func (c *Client) Use(hooks ...Hook) {
func (c *Client) Intercept(interceptors ...Interceptor) {
for _, n := range []interface{ Intercept(...Interceptor) }{
c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead,
c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord, c.PromoCode,
c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting,
c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord, c.PaymentAuditLog,
c.PaymentOrder, c.PaymentProviderInstance, c.PromoCode, c.PromoCodeUsage,
c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting, c.SubscriptionPlan,
c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog, c.User,
c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
c.UserSubscription,
@ -352,6 +378,12 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
return c.Group.mutate(ctx, m)
case *IdempotencyRecordMutation:
return c.IdempotencyRecord.mutate(ctx, m)
case *PaymentAuditLogMutation:
return c.PaymentAuditLog.mutate(ctx, m)
case *PaymentOrderMutation:
return c.PaymentOrder.mutate(ctx, m)
case *PaymentProviderInstanceMutation:
return c.PaymentProviderInstance.mutate(ctx, m)
case *PromoCodeMutation:
return c.PromoCode.mutate(ctx, m)
case *PromoCodeUsageMutation:
@ -364,6 +396,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
return c.SecuritySecret.mutate(ctx, m)
case *SettingMutation:
return c.Setting.mutate(ctx, m)
case *SubscriptionPlanMutation:
return c.SubscriptionPlan.mutate(ctx, m)
case *TLSFingerprintProfileMutation:
return c.TLSFingerprintProfile.mutate(ctx, m)
case *UsageCleanupTaskMutation:
@ -1726,6 +1760,421 @@ func (c *IdempotencyRecordClient) mutate(ctx context.Context, m *IdempotencyReco
}
}
// PaymentAuditLogClient is a client for the PaymentAuditLog schema.
type PaymentAuditLogClient struct {
config
}
// NewPaymentAuditLogClient returns a client for the PaymentAuditLog from the given config.
func NewPaymentAuditLogClient(c config) *PaymentAuditLogClient {
return &PaymentAuditLogClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `paymentauditlog.Hooks(f(g(h())))`.
func (c *PaymentAuditLogClient) Use(hooks ...Hook) {
c.hooks.PaymentAuditLog = append(c.hooks.PaymentAuditLog, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `paymentauditlog.Intercept(f(g(h())))`.
func (c *PaymentAuditLogClient) Intercept(interceptors ...Interceptor) {
c.inters.PaymentAuditLog = append(c.inters.PaymentAuditLog, interceptors...)
}
// Create returns a builder for creating a PaymentAuditLog entity.
func (c *PaymentAuditLogClient) Create() *PaymentAuditLogCreate {
mutation := newPaymentAuditLogMutation(c.config, OpCreate)
return &PaymentAuditLogCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of PaymentAuditLog entities.
func (c *PaymentAuditLogClient) CreateBulk(builders ...*PaymentAuditLogCreate) *PaymentAuditLogCreateBulk {
return &PaymentAuditLogCreateBulk{config: c.config, builders: builders}
}
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *PaymentAuditLogClient) MapCreateBulk(slice any, setFunc func(*PaymentAuditLogCreate, int)) *PaymentAuditLogCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &PaymentAuditLogCreateBulk{err: fmt.Errorf("calling to PaymentAuditLogClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*PaymentAuditLogCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &PaymentAuditLogCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for PaymentAuditLog.
func (c *PaymentAuditLogClient) Update() *PaymentAuditLogUpdate {
mutation := newPaymentAuditLogMutation(c.config, OpUpdate)
return &PaymentAuditLogUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *PaymentAuditLogClient) UpdateOne(_m *PaymentAuditLog) *PaymentAuditLogUpdateOne {
mutation := newPaymentAuditLogMutation(c.config, OpUpdateOne, withPaymentAuditLog(_m))
return &PaymentAuditLogUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *PaymentAuditLogClient) UpdateOneID(id int64) *PaymentAuditLogUpdateOne {
mutation := newPaymentAuditLogMutation(c.config, OpUpdateOne, withPaymentAuditLogID(id))
return &PaymentAuditLogUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for PaymentAuditLog.
func (c *PaymentAuditLogClient) Delete() *PaymentAuditLogDelete {
mutation := newPaymentAuditLogMutation(c.config, OpDelete)
return &PaymentAuditLogDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *PaymentAuditLogClient) DeleteOne(_m *PaymentAuditLog) *PaymentAuditLogDeleteOne {
return c.DeleteOneID(_m.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *PaymentAuditLogClient) DeleteOneID(id int64) *PaymentAuditLogDeleteOne {
builder := c.Delete().Where(paymentauditlog.ID(id))
builder.mutation.id = &id
builder.mutation.op = OpDeleteOne
return &PaymentAuditLogDeleteOne{builder}
}
// Query returns a query builder for PaymentAuditLog.
func (c *PaymentAuditLogClient) Query() *PaymentAuditLogQuery {
return &PaymentAuditLogQuery{
config: c.config,
ctx: &QueryContext{Type: TypePaymentAuditLog},
inters: c.Interceptors(),
}
}
// Get returns a PaymentAuditLog entity by its id.
func (c *PaymentAuditLogClient) Get(ctx context.Context, id int64) (*PaymentAuditLog, error) {
return c.Query().Where(paymentauditlog.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *PaymentAuditLogClient) GetX(ctx context.Context, id int64) *PaymentAuditLog {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// Hooks returns the client hooks.
func (c *PaymentAuditLogClient) Hooks() []Hook {
return c.hooks.PaymentAuditLog
}
// Interceptors returns the client interceptors.
func (c *PaymentAuditLogClient) Interceptors() []Interceptor {
return c.inters.PaymentAuditLog
}
func (c *PaymentAuditLogClient) mutate(ctx context.Context, m *PaymentAuditLogMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&PaymentAuditLogCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&PaymentAuditLogUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&PaymentAuditLogUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&PaymentAuditLogDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown PaymentAuditLog mutation op: %q", m.Op())
}
}
// PaymentOrderClient is a client for the PaymentOrder schema.
type PaymentOrderClient struct {
config
}
// NewPaymentOrderClient returns a client for the PaymentOrder from the given config.
func NewPaymentOrderClient(c config) *PaymentOrderClient {
return &PaymentOrderClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `paymentorder.Hooks(f(g(h())))`.
func (c *PaymentOrderClient) Use(hooks ...Hook) {
c.hooks.PaymentOrder = append(c.hooks.PaymentOrder, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `paymentorder.Intercept(f(g(h())))`.
func (c *PaymentOrderClient) Intercept(interceptors ...Interceptor) {
c.inters.PaymentOrder = append(c.inters.PaymentOrder, interceptors...)
}
// Create returns a builder for creating a PaymentOrder entity.
func (c *PaymentOrderClient) Create() *PaymentOrderCreate {
mutation := newPaymentOrderMutation(c.config, OpCreate)
return &PaymentOrderCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of PaymentOrder entities.
func (c *PaymentOrderClient) CreateBulk(builders ...*PaymentOrderCreate) *PaymentOrderCreateBulk {
return &PaymentOrderCreateBulk{config: c.config, builders: builders}
}
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *PaymentOrderClient) MapCreateBulk(slice any, setFunc func(*PaymentOrderCreate, int)) *PaymentOrderCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &PaymentOrderCreateBulk{err: fmt.Errorf("calling to PaymentOrderClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*PaymentOrderCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &PaymentOrderCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for PaymentOrder.
func (c *PaymentOrderClient) Update() *PaymentOrderUpdate {
mutation := newPaymentOrderMutation(c.config, OpUpdate)
return &PaymentOrderUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *PaymentOrderClient) UpdateOne(_m *PaymentOrder) *PaymentOrderUpdateOne {
mutation := newPaymentOrderMutation(c.config, OpUpdateOne, withPaymentOrder(_m))
return &PaymentOrderUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *PaymentOrderClient) UpdateOneID(id int64) *PaymentOrderUpdateOne {
mutation := newPaymentOrderMutation(c.config, OpUpdateOne, withPaymentOrderID(id))
return &PaymentOrderUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for PaymentOrder.
func (c *PaymentOrderClient) Delete() *PaymentOrderDelete {
mutation := newPaymentOrderMutation(c.config, OpDelete)
return &PaymentOrderDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *PaymentOrderClient) DeleteOne(_m *PaymentOrder) *PaymentOrderDeleteOne {
return c.DeleteOneID(_m.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *PaymentOrderClient) DeleteOneID(id int64) *PaymentOrderDeleteOne {
builder := c.Delete().Where(paymentorder.ID(id))
builder.mutation.id = &id
builder.mutation.op = OpDeleteOne
return &PaymentOrderDeleteOne{builder}
}
// Query returns a query builder for PaymentOrder.
func (c *PaymentOrderClient) Query() *PaymentOrderQuery {
return &PaymentOrderQuery{
config: c.config,
ctx: &QueryContext{Type: TypePaymentOrder},
inters: c.Interceptors(),
}
}
// Get returns a PaymentOrder entity by its id.
func (c *PaymentOrderClient) Get(ctx context.Context, id int64) (*PaymentOrder, error) {
return c.Query().Where(paymentorder.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *PaymentOrderClient) GetX(ctx context.Context, id int64) *PaymentOrder {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// QueryUser queries the user edge of a PaymentOrder.
func (c *PaymentOrderClient) QueryUser(_m *PaymentOrder) *UserQuery {
query := (&UserClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := _m.ID
step := sqlgraph.NewStep(
sqlgraph.From(paymentorder.Table, paymentorder.FieldID, id),
sqlgraph.To(user.Table, user.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, paymentorder.UserTable, paymentorder.UserColumn),
)
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks.
func (c *PaymentOrderClient) Hooks() []Hook {
return c.hooks.PaymentOrder
}
// Interceptors returns the client interceptors.
func (c *PaymentOrderClient) Interceptors() []Interceptor {
return c.inters.PaymentOrder
}
func (c *PaymentOrderClient) mutate(ctx context.Context, m *PaymentOrderMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&PaymentOrderCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&PaymentOrderUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&PaymentOrderUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&PaymentOrderDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown PaymentOrder mutation op: %q", m.Op())
}
}
// PaymentProviderInstanceClient is a client for the PaymentProviderInstance schema.
type PaymentProviderInstanceClient struct {
config
}
// NewPaymentProviderInstanceClient returns a client for the PaymentProviderInstance from the given config.
func NewPaymentProviderInstanceClient(c config) *PaymentProviderInstanceClient {
return &PaymentProviderInstanceClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `paymentproviderinstance.Hooks(f(g(h())))`.
func (c *PaymentProviderInstanceClient) Use(hooks ...Hook) {
c.hooks.PaymentProviderInstance = append(c.hooks.PaymentProviderInstance, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `paymentproviderinstance.Intercept(f(g(h())))`.
func (c *PaymentProviderInstanceClient) Intercept(interceptors ...Interceptor) {
c.inters.PaymentProviderInstance = append(c.inters.PaymentProviderInstance, interceptors...)
}
// Create returns a builder for creating a PaymentProviderInstance entity.
func (c *PaymentProviderInstanceClient) Create() *PaymentProviderInstanceCreate {
mutation := newPaymentProviderInstanceMutation(c.config, OpCreate)
return &PaymentProviderInstanceCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of PaymentProviderInstance entities.
func (c *PaymentProviderInstanceClient) CreateBulk(builders ...*PaymentProviderInstanceCreate) *PaymentProviderInstanceCreateBulk {
return &PaymentProviderInstanceCreateBulk{config: c.config, builders: builders}
}
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *PaymentProviderInstanceClient) MapCreateBulk(slice any, setFunc func(*PaymentProviderInstanceCreate, int)) *PaymentProviderInstanceCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &PaymentProviderInstanceCreateBulk{err: fmt.Errorf("calling to PaymentProviderInstanceClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*PaymentProviderInstanceCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &PaymentProviderInstanceCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for PaymentProviderInstance.
func (c *PaymentProviderInstanceClient) Update() *PaymentProviderInstanceUpdate {
mutation := newPaymentProviderInstanceMutation(c.config, OpUpdate)
return &PaymentProviderInstanceUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *PaymentProviderInstanceClient) UpdateOne(_m *PaymentProviderInstance) *PaymentProviderInstanceUpdateOne {
mutation := newPaymentProviderInstanceMutation(c.config, OpUpdateOne, withPaymentProviderInstance(_m))
return &PaymentProviderInstanceUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *PaymentProviderInstanceClient) UpdateOneID(id int64) *PaymentProviderInstanceUpdateOne {
mutation := newPaymentProviderInstanceMutation(c.config, OpUpdateOne, withPaymentProviderInstanceID(id))
return &PaymentProviderInstanceUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for PaymentProviderInstance.
func (c *PaymentProviderInstanceClient) Delete() *PaymentProviderInstanceDelete {
mutation := newPaymentProviderInstanceMutation(c.config, OpDelete)
return &PaymentProviderInstanceDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *PaymentProviderInstanceClient) DeleteOne(_m *PaymentProviderInstance) *PaymentProviderInstanceDeleteOne {
return c.DeleteOneID(_m.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *PaymentProviderInstanceClient) DeleteOneID(id int64) *PaymentProviderInstanceDeleteOne {
builder := c.Delete().Where(paymentproviderinstance.ID(id))
builder.mutation.id = &id
builder.mutation.op = OpDeleteOne
return &PaymentProviderInstanceDeleteOne{builder}
}
// Query returns a query builder for PaymentProviderInstance.
func (c *PaymentProviderInstanceClient) Query() *PaymentProviderInstanceQuery {
return &PaymentProviderInstanceQuery{
config: c.config,
ctx: &QueryContext{Type: TypePaymentProviderInstance},
inters: c.Interceptors(),
}
}
// Get returns a PaymentProviderInstance entity by its id.
func (c *PaymentProviderInstanceClient) Get(ctx context.Context, id int64) (*PaymentProviderInstance, error) {
return c.Query().Where(paymentproviderinstance.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *PaymentProviderInstanceClient) GetX(ctx context.Context, id int64) *PaymentProviderInstance {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// Hooks returns the client hooks.
func (c *PaymentProviderInstanceClient) Hooks() []Hook {
return c.hooks.PaymentProviderInstance
}
// Interceptors returns the client interceptors.
func (c *PaymentProviderInstanceClient) Interceptors() []Interceptor {
return c.inters.PaymentProviderInstance
}
func (c *PaymentProviderInstanceClient) mutate(ctx context.Context, m *PaymentProviderInstanceMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&PaymentProviderInstanceCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&PaymentProviderInstanceUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&PaymentProviderInstanceUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&PaymentProviderInstanceDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown PaymentProviderInstance mutation op: %q", m.Op())
}
}
// PromoCodeClient is a client for the PromoCode schema.
type PromoCodeClient struct {
config
@ -2622,6 +3071,139 @@ func (c *SettingClient) mutate(ctx context.Context, m *SettingMutation) (Value,
}
}
// SubscriptionPlanClient is a client for the SubscriptionPlan schema.
type SubscriptionPlanClient struct {
config
}
// NewSubscriptionPlanClient returns a client for the SubscriptionPlan from the given config.
func NewSubscriptionPlanClient(c config) *SubscriptionPlanClient {
return &SubscriptionPlanClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `subscriptionplan.Hooks(f(g(h())))`.
func (c *SubscriptionPlanClient) Use(hooks ...Hook) {
c.hooks.SubscriptionPlan = append(c.hooks.SubscriptionPlan, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `subscriptionplan.Intercept(f(g(h())))`.
func (c *SubscriptionPlanClient) Intercept(interceptors ...Interceptor) {
c.inters.SubscriptionPlan = append(c.inters.SubscriptionPlan, interceptors...)
}
// Create returns a builder for creating a SubscriptionPlan entity.
func (c *SubscriptionPlanClient) Create() *SubscriptionPlanCreate {
mutation := newSubscriptionPlanMutation(c.config, OpCreate)
return &SubscriptionPlanCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of SubscriptionPlan entities.
func (c *SubscriptionPlanClient) CreateBulk(builders ...*SubscriptionPlanCreate) *SubscriptionPlanCreateBulk {
return &SubscriptionPlanCreateBulk{config: c.config, builders: builders}
}
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
// a builder and applies setFunc on it.
func (c *SubscriptionPlanClient) MapCreateBulk(slice any, setFunc func(*SubscriptionPlanCreate, int)) *SubscriptionPlanCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &SubscriptionPlanCreateBulk{err: fmt.Errorf("calling to SubscriptionPlanClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*SubscriptionPlanCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &SubscriptionPlanCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for SubscriptionPlan.
func (c *SubscriptionPlanClient) Update() *SubscriptionPlanUpdate {
mutation := newSubscriptionPlanMutation(c.config, OpUpdate)
return &SubscriptionPlanUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *SubscriptionPlanClient) UpdateOne(_m *SubscriptionPlan) *SubscriptionPlanUpdateOne {
mutation := newSubscriptionPlanMutation(c.config, OpUpdateOne, withSubscriptionPlan(_m))
return &SubscriptionPlanUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *SubscriptionPlanClient) UpdateOneID(id int64) *SubscriptionPlanUpdateOne {
mutation := newSubscriptionPlanMutation(c.config, OpUpdateOne, withSubscriptionPlanID(id))
return &SubscriptionPlanUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for SubscriptionPlan.
func (c *SubscriptionPlanClient) Delete() *SubscriptionPlanDelete {
mutation := newSubscriptionPlanMutation(c.config, OpDelete)
return &SubscriptionPlanDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *SubscriptionPlanClient) DeleteOne(_m *SubscriptionPlan) *SubscriptionPlanDeleteOne {
return c.DeleteOneID(_m.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *SubscriptionPlanClient) DeleteOneID(id int64) *SubscriptionPlanDeleteOne {
builder := c.Delete().Where(subscriptionplan.ID(id))
builder.mutation.id = &id
builder.mutation.op = OpDeleteOne
return &SubscriptionPlanDeleteOne{builder}
}
// Query returns a query builder for SubscriptionPlan.
func (c *SubscriptionPlanClient) Query() *SubscriptionPlanQuery {
return &SubscriptionPlanQuery{
config: c.config,
ctx: &QueryContext{Type: TypeSubscriptionPlan},
inters: c.Interceptors(),
}
}
// Get returns a SubscriptionPlan entity by its id.
func (c *SubscriptionPlanClient) Get(ctx context.Context, id int64) (*SubscriptionPlan, error) {
return c.Query().Where(subscriptionplan.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *SubscriptionPlanClient) GetX(ctx context.Context, id int64) *SubscriptionPlan {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// Hooks returns the client hooks.
func (c *SubscriptionPlanClient) Hooks() []Hook {
return c.hooks.SubscriptionPlan
}
// Interceptors returns the client interceptors.
func (c *SubscriptionPlanClient) Interceptors() []Interceptor {
return c.inters.SubscriptionPlan
}
func (c *SubscriptionPlanClient) mutate(ctx context.Context, m *SubscriptionPlanMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&SubscriptionPlanCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&SubscriptionPlanUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&SubscriptionPlanUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&SubscriptionPlanDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown SubscriptionPlan mutation op: %q", m.Op())
}
}
// TLSFingerprintProfileClient is a client for the TLSFingerprintProfile schema.
type TLSFingerprintProfileClient struct {
config
@ -3353,6 +3935,22 @@ func (c *UserClient) QueryPromoCodeUsages(_m *User) *PromoCodeUsageQuery {
return query
}
// QueryPaymentOrders queries the payment_orders edge of a User.
func (c *UserClient) QueryPaymentOrders(_m *User) *PaymentOrderQuery {
query := (&PaymentOrderClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := _m.ID
step := sqlgraph.NewStep(
sqlgraph.From(user.Table, user.FieldID, id),
sqlgraph.To(paymentorder.Table, paymentorder.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, user.PaymentOrdersTable, user.PaymentOrdersColumn),
)
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
return fromV, nil
}
return query
}
// QueryUserAllowedGroups queries the user_allowed_groups edge of a User.
func (c *UserClient) QueryUserAllowedGroups(_m *User) *UserAllowedGroupQuery {
query := (&UserAllowedGroupClient{config: c.config}).Query()
@ -4031,15 +4629,17 @@ func (c *UserSubscriptionClient) mutate(ctx context.Context, m *UserSubscription
type (
hooks struct {
APIKey, Account, AccountGroup, Announcement, AnnouncementRead,
ErrorPassthroughRule, Group, IdempotencyRecord, PromoCode, PromoCodeUsage,
Proxy, RedeemCode, SecuritySecret, Setting, TLSFingerprintProfile,
ErrorPassthroughRule, Group, IdempotencyRecord, PaymentAuditLog, PaymentOrder,
PaymentProviderInstance, PromoCode, PromoCodeUsage, Proxy, RedeemCode,
SecuritySecret, Setting, SubscriptionPlan, TLSFingerprintProfile,
UsageCleanupTask, UsageLog, User, UserAllowedGroup, UserAttributeDefinition,
UserAttributeValue, UserSubscription []ent.Hook
}
inters struct {
APIKey, Account, AccountGroup, Announcement, AnnouncementRead,
ErrorPassthroughRule, Group, IdempotencyRecord, PromoCode, PromoCodeUsage,
Proxy, RedeemCode, SecuritySecret, Setting, TLSFingerprintProfile,
ErrorPassthroughRule, Group, IdempotencyRecord, PaymentAuditLog, PaymentOrder,
PaymentProviderInstance, PromoCode, PromoCodeUsage, Proxy, RedeemCode,
SecuritySecret, Setting, SubscriptionPlan, TLSFingerprintProfile,
UsageCleanupTask, UsageLog, User, UserAllowedGroup, UserAttributeDefinition,
UserAttributeValue, UserSubscription []ent.Interceptor
}

View File

@ -20,12 +20,16 @@ import (
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
"github.com/Wei-Shaw/sub2api/ent/paymentauditlog"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
"github.com/Wei-Shaw/sub2api/ent/promocode"
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/ent/proxy"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
"github.com/Wei-Shaw/sub2api/ent/setting"
"github.com/Wei-Shaw/sub2api/ent/subscriptionplan"
"github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile"
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
"github.com/Wei-Shaw/sub2api/ent/usagelog"
@ -102,12 +106,16 @@ func checkColumn(t, c string) error {
errorpassthroughrule.Table: errorpassthroughrule.ValidColumn,
group.Table: group.ValidColumn,
idempotencyrecord.Table: idempotencyrecord.ValidColumn,
paymentauditlog.Table: paymentauditlog.ValidColumn,
paymentorder.Table: paymentorder.ValidColumn,
paymentproviderinstance.Table: paymentproviderinstance.ValidColumn,
promocode.Table: promocode.ValidColumn,
promocodeusage.Table: promocodeusage.ValidColumn,
proxy.Table: proxy.ValidColumn,
redeemcode.Table: redeemcode.ValidColumn,
securitysecret.Table: securitysecret.ValidColumn,
setting.Table: setting.ValidColumn,
subscriptionplan.Table: subscriptionplan.ValidColumn,
tlsfingerprintprofile.Table: tlsfingerprintprofile.ValidColumn,
usagecleanuptask.Table: usagecleanuptask.ValidColumn,
usagelog.Table: usagelog.ValidColumn,

View File

@ -105,6 +105,42 @@ func (f IdempotencyRecordFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.IdempotencyRecordMutation", m)
}
// The PaymentAuditLogFunc type is an adapter to allow the use of ordinary
// function as PaymentAuditLog mutator.
type PaymentAuditLogFunc func(context.Context, *ent.PaymentAuditLogMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f PaymentAuditLogFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.PaymentAuditLogMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.PaymentAuditLogMutation", m)
}
// The PaymentOrderFunc type is an adapter to allow the use of ordinary
// function as PaymentOrder mutator.
type PaymentOrderFunc func(context.Context, *ent.PaymentOrderMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f PaymentOrderFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.PaymentOrderMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.PaymentOrderMutation", m)
}
// The PaymentProviderInstanceFunc type is an adapter to allow the use of ordinary
// function as PaymentProviderInstance mutator.
type PaymentProviderInstanceFunc func(context.Context, *ent.PaymentProviderInstanceMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f PaymentProviderInstanceFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.PaymentProviderInstanceMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.PaymentProviderInstanceMutation", m)
}
// The PromoCodeFunc type is an adapter to allow the use of ordinary
// function as PromoCode mutator.
type PromoCodeFunc func(context.Context, *ent.PromoCodeMutation) (ent.Value, error)
@ -177,6 +213,18 @@ func (f SettingFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, err
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SettingMutation", m)
}
// The SubscriptionPlanFunc type is an adapter to allow the use of ordinary
// function as SubscriptionPlan mutator.
type SubscriptionPlanFunc func(context.Context, *ent.SubscriptionPlanMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f SubscriptionPlanFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.SubscriptionPlanMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SubscriptionPlanMutation", m)
}
// The TLSFingerprintProfileFunc type is an adapter to allow the use of ordinary
// function as TLSFingerprintProfile mutator.
type TLSFingerprintProfileFunc func(context.Context, *ent.TLSFingerprintProfileMutation) (ent.Value, error)

View File

@ -16,6 +16,9 @@ import (
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
"github.com/Wei-Shaw/sub2api/ent/paymentauditlog"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/promocode"
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
@ -23,6 +26,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
"github.com/Wei-Shaw/sub2api/ent/setting"
"github.com/Wei-Shaw/sub2api/ent/subscriptionplan"
"github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile"
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
"github.com/Wei-Shaw/sub2api/ent/usagelog"
@ -305,6 +309,87 @@ func (f TraverseIdempotencyRecord) Traverse(ctx context.Context, q ent.Query) er
return fmt.Errorf("unexpected query type %T. expect *ent.IdempotencyRecordQuery", q)
}
// The PaymentAuditLogFunc type is an adapter to allow the use of ordinary function as a Querier.
type PaymentAuditLogFunc func(context.Context, *ent.PaymentAuditLogQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f PaymentAuditLogFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.PaymentAuditLogQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.PaymentAuditLogQuery", q)
}
// The TraversePaymentAuditLog type is an adapter to allow the use of ordinary function as Traverser.
type TraversePaymentAuditLog func(context.Context, *ent.PaymentAuditLogQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraversePaymentAuditLog) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraversePaymentAuditLog) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.PaymentAuditLogQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.PaymentAuditLogQuery", q)
}
// The PaymentOrderFunc type is an adapter to allow the use of ordinary function as a Querier.
type PaymentOrderFunc func(context.Context, *ent.PaymentOrderQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f PaymentOrderFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.PaymentOrderQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.PaymentOrderQuery", q)
}
// The TraversePaymentOrder type is an adapter to allow the use of ordinary function as Traverser.
type TraversePaymentOrder func(context.Context, *ent.PaymentOrderQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraversePaymentOrder) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraversePaymentOrder) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.PaymentOrderQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.PaymentOrderQuery", q)
}
// The PaymentProviderInstanceFunc type is an adapter to allow the use of ordinary function as a Querier.
type PaymentProviderInstanceFunc func(context.Context, *ent.PaymentProviderInstanceQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f PaymentProviderInstanceFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.PaymentProviderInstanceQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.PaymentProviderInstanceQuery", q)
}
// The TraversePaymentProviderInstance type is an adapter to allow the use of ordinary function as Traverser.
type TraversePaymentProviderInstance func(context.Context, *ent.PaymentProviderInstanceQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraversePaymentProviderInstance) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraversePaymentProviderInstance) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.PaymentProviderInstanceQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.PaymentProviderInstanceQuery", q)
}
// The PromoCodeFunc type is an adapter to allow the use of ordinary function as a Querier.
type PromoCodeFunc func(context.Context, *ent.PromoCodeQuery) (ent.Value, error)
@ -467,6 +552,33 @@ func (f TraverseSetting) Traverse(ctx context.Context, q ent.Query) error {
return fmt.Errorf("unexpected query type %T. expect *ent.SettingQuery", q)
}
// The SubscriptionPlanFunc type is an adapter to allow the use of ordinary function as a Querier.
type SubscriptionPlanFunc func(context.Context, *ent.SubscriptionPlanQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f SubscriptionPlanFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.SubscriptionPlanQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.SubscriptionPlanQuery", q)
}
// The TraverseSubscriptionPlan type is an adapter to allow the use of ordinary function as Traverser.
type TraverseSubscriptionPlan func(context.Context, *ent.SubscriptionPlanQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraverseSubscriptionPlan) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraverseSubscriptionPlan) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.SubscriptionPlanQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.SubscriptionPlanQuery", q)
}
// The TLSFingerprintProfileFunc type is an adapter to allow the use of ordinary function as a Querier.
type TLSFingerprintProfileFunc func(context.Context, *ent.TLSFingerprintProfileQuery) (ent.Value, error)
@ -702,6 +814,12 @@ func NewQuery(q ent.Query) (Query, error) {
return &query[*ent.GroupQuery, predicate.Group, group.OrderOption]{typ: ent.TypeGroup, tq: q}, nil
case *ent.IdempotencyRecordQuery:
return &query[*ent.IdempotencyRecordQuery, predicate.IdempotencyRecord, idempotencyrecord.OrderOption]{typ: ent.TypeIdempotencyRecord, tq: q}, nil
case *ent.PaymentAuditLogQuery:
return &query[*ent.PaymentAuditLogQuery, predicate.PaymentAuditLog, paymentauditlog.OrderOption]{typ: ent.TypePaymentAuditLog, tq: q}, nil
case *ent.PaymentOrderQuery:
return &query[*ent.PaymentOrderQuery, predicate.PaymentOrder, paymentorder.OrderOption]{typ: ent.TypePaymentOrder, tq: q}, nil
case *ent.PaymentProviderInstanceQuery:
return &query[*ent.PaymentProviderInstanceQuery, predicate.PaymentProviderInstance, paymentproviderinstance.OrderOption]{typ: ent.TypePaymentProviderInstance, tq: q}, nil
case *ent.PromoCodeQuery:
return &query[*ent.PromoCodeQuery, predicate.PromoCode, promocode.OrderOption]{typ: ent.TypePromoCode, tq: q}, nil
case *ent.PromoCodeUsageQuery:
@ -714,6 +832,8 @@ func NewQuery(q ent.Query) (Query, error) {
return &query[*ent.SecuritySecretQuery, predicate.SecuritySecret, securitysecret.OrderOption]{typ: ent.TypeSecuritySecret, tq: q}, nil
case *ent.SettingQuery:
return &query[*ent.SettingQuery, predicate.Setting, setting.OrderOption]{typ: ent.TypeSetting, tq: q}, nil
case *ent.SubscriptionPlanQuery:
return &query[*ent.SubscriptionPlanQuery, predicate.SubscriptionPlan, subscriptionplan.OrderOption]{typ: ent.TypeSubscriptionPlan, tq: q}, nil
case *ent.TLSFingerprintProfileQuery:
return &query[*ent.TLSFingerprintProfileQuery, predicate.TLSFingerprintProfile, tlsfingerprintprofile.OrderOption]{typ: ent.TypeTLSFingerprintProfile, tq: q}, nil
case *ent.UsageCleanupTaskQuery:

View File

@ -485,6 +485,158 @@ var (
},
},
}
// PaymentAuditLogsColumns holds the columns for the "payment_audit_logs" table.
PaymentAuditLogsColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "order_id", Type: field.TypeString, Size: 64},
{Name: "action", Type: field.TypeString, Size: 50},
{Name: "detail", Type: field.TypeString, Default: "", SchemaType: map[string]string{"postgres": "text"}},
{Name: "operator", Type: field.TypeString, Size: 100, Default: "system"},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
}
// PaymentAuditLogsTable holds the schema information for the "payment_audit_logs" table.
PaymentAuditLogsTable = &schema.Table{
Name: "payment_audit_logs",
Columns: PaymentAuditLogsColumns,
PrimaryKey: []*schema.Column{PaymentAuditLogsColumns[0]},
Indexes: []*schema.Index{
{
Name: "paymentauditlog_order_id",
Unique: false,
Columns: []*schema.Column{PaymentAuditLogsColumns[1]},
},
},
}
// PaymentOrdersColumns holds the columns for the "payment_orders" table.
PaymentOrdersColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "user_email", Type: field.TypeString, Size: 255},
{Name: "user_name", Type: field.TypeString, Size: 100},
{Name: "user_notes", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "amount", Type: field.TypeFloat64, SchemaType: map[string]string{"postgres": "decimal(20,2)"}},
{Name: "pay_amount", Type: field.TypeFloat64, SchemaType: map[string]string{"postgres": "decimal(20,2)"}},
{Name: "fee_rate", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(10,4)"}},
{Name: "recharge_code", Type: field.TypeString, Size: 64},
{Name: "out_trade_no", Type: field.TypeString, Size: 64, Default: ""},
{Name: "payment_type", Type: field.TypeString, Size: 30},
{Name: "payment_trade_no", Type: field.TypeString, Size: 128},
{Name: "pay_url", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "qr_code", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "qr_code_img", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "order_type", Type: field.TypeString, Size: 20, Default: "balance"},
{Name: "plan_id", Type: field.TypeInt64, Nullable: true},
{Name: "subscription_group_id", Type: field.TypeInt64, Nullable: true},
{Name: "subscription_days", Type: field.TypeInt, Nullable: true},
{Name: "provider_instance_id", Type: field.TypeString, Nullable: true, Size: 64},
{Name: "status", Type: field.TypeString, Size: 30, Default: "PENDING"},
{Name: "refund_amount", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,2)"}},
{Name: "refund_reason", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "refund_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "force_refund", Type: field.TypeBool, Default: false},
{Name: "refund_requested_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "refund_request_reason", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "refund_requested_by", Type: field.TypeString, Nullable: true, Size: 20},
{Name: "expires_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "paid_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "completed_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "failed_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "failed_reason", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "client_ip", Type: field.TypeString, Size: 50},
{Name: "src_host", Type: field.TypeString, Size: 255},
{Name: "src_url", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "user_id", Type: field.TypeInt64},
}
// PaymentOrdersTable holds the schema information for the "payment_orders" table.
PaymentOrdersTable = &schema.Table{
Name: "payment_orders",
Columns: PaymentOrdersColumns,
PrimaryKey: []*schema.Column{PaymentOrdersColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "payment_orders_users_payment_orders",
Columns: []*schema.Column{PaymentOrdersColumns[37]},
RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.NoAction,
},
},
Indexes: []*schema.Index{
{
Name: "paymentorder_out_trade_no",
Unique: false,
Columns: []*schema.Column{PaymentOrdersColumns[8]},
},
{
Name: "paymentorder_user_id",
Unique: false,
Columns: []*schema.Column{PaymentOrdersColumns[37]},
},
{
Name: "paymentorder_status",
Unique: false,
Columns: []*schema.Column{PaymentOrdersColumns[19]},
},
{
Name: "paymentorder_expires_at",
Unique: false,
Columns: []*schema.Column{PaymentOrdersColumns[27]},
},
{
Name: "paymentorder_created_at",
Unique: false,
Columns: []*schema.Column{PaymentOrdersColumns[35]},
},
{
Name: "paymentorder_paid_at",
Unique: false,
Columns: []*schema.Column{PaymentOrdersColumns[28]},
},
{
Name: "paymentorder_payment_type_paid_at",
Unique: false,
Columns: []*schema.Column{PaymentOrdersColumns[9], PaymentOrdersColumns[28]},
},
{
Name: "paymentorder_order_type",
Unique: false,
Columns: []*schema.Column{PaymentOrdersColumns[14]},
},
},
}
// PaymentProviderInstancesColumns holds the columns for the "payment_provider_instances" table.
PaymentProviderInstancesColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "provider_key", Type: field.TypeString, Size: 30},
{Name: "name", Type: field.TypeString, Size: 100, Default: ""},
{Name: "config", Type: field.TypeString, SchemaType: map[string]string{"postgres": "text"}},
{Name: "supported_types", Type: field.TypeString, Size: 200, Default: ""},
{Name: "enabled", Type: field.TypeBool, Default: true},
{Name: "payment_mode", Type: field.TypeString, Size: 20, Default: ""},
{Name: "sort_order", Type: field.TypeInt, Default: 0},
{Name: "limits", Type: field.TypeString, Default: "", SchemaType: map[string]string{"postgres": "text"}},
{Name: "refund_enabled", Type: field.TypeBool, Default: false},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
}
// PaymentProviderInstancesTable holds the schema information for the "payment_provider_instances" table.
PaymentProviderInstancesTable = &schema.Table{
Name: "payment_provider_instances",
Columns: PaymentProviderInstancesColumns,
PrimaryKey: []*schema.Column{PaymentProviderInstancesColumns[0]},
Indexes: []*schema.Index{
{
Name: "paymentproviderinstance_provider_key",
Unique: false,
Columns: []*schema.Column{PaymentProviderInstancesColumns[1]},
},
{
Name: "paymentproviderinstance_enabled",
Unique: false,
Columns: []*schema.Column{PaymentProviderInstancesColumns[5]},
},
},
}
// PromoCodesColumns holds the columns for the "promo_codes" table.
PromoCodesColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
@ -671,6 +823,41 @@ var (
Columns: SettingsColumns,
PrimaryKey: []*schema.Column{SettingsColumns[0]},
}
// SubscriptionPlansColumns holds the columns for the "subscription_plans" table.
SubscriptionPlansColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
{Name: "group_id", Type: field.TypeInt64},
{Name: "name", Type: field.TypeString, Size: 100},
{Name: "description", Type: field.TypeString, Default: "", SchemaType: map[string]string{"postgres": "text"}},
{Name: "price", Type: field.TypeFloat64, SchemaType: map[string]string{"postgres": "decimal(20,2)"}},
{Name: "original_price", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,2)"}},
{Name: "validity_days", Type: field.TypeInt, Default: 30},
{Name: "validity_unit", Type: field.TypeString, Size: 10, Default: "day"},
{Name: "features", Type: field.TypeString, Default: "", SchemaType: map[string]string{"postgres": "text"}},
{Name: "product_name", Type: field.TypeString, Size: 100, Default: ""},
{Name: "for_sale", Type: field.TypeBool, Default: true},
{Name: "sort_order", Type: field.TypeInt, Default: 0},
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
}
// SubscriptionPlansTable holds the schema information for the "subscription_plans" table.
SubscriptionPlansTable = &schema.Table{
Name: "subscription_plans",
Columns: SubscriptionPlansColumns,
PrimaryKey: []*schema.Column{SubscriptionPlansColumns[0]},
Indexes: []*schema.Index{
{
Name: "subscriptionplan_group_id",
Unique: false,
Columns: []*schema.Column{SubscriptionPlansColumns[1]},
},
{
Name: "subscriptionplan_for_sale",
Unique: false,
Columns: []*schema.Column{SubscriptionPlansColumns[10]},
},
},
}
// TLSFingerprintProfilesColumns holds the columns for the "tls_fingerprint_profiles" table.
TLSFingerprintProfilesColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
@ -1128,12 +1315,16 @@ var (
ErrorPassthroughRulesTable,
GroupsTable,
IdempotencyRecordsTable,
PaymentAuditLogsTable,
PaymentOrdersTable,
PaymentProviderInstancesTable,
PromoCodesTable,
PromoCodeUsagesTable,
ProxiesTable,
RedeemCodesTable,
SecuritySecretsTable,
SettingsTable,
SubscriptionPlansTable,
TLSFingerprintProfilesTable,
UsageCleanupTasksTable,
UsageLogsTable,
@ -1177,6 +1368,16 @@ func init() {
IdempotencyRecordsTable.Annotation = &entsql.Annotation{
Table: "idempotency_records",
}
PaymentAuditLogsTable.Annotation = &entsql.Annotation{
Table: "payment_audit_logs",
}
PaymentOrdersTable.ForeignKeys[0].RefTable = UsersTable
PaymentOrdersTable.Annotation = &entsql.Annotation{
Table: "payment_orders",
}
PaymentProviderInstancesTable.Annotation = &entsql.Annotation{
Table: "payment_provider_instances",
}
PromoCodesTable.Annotation = &entsql.Annotation{
Table: "promo_codes",
}
@ -1199,6 +1400,9 @@ func init() {
SettingsTable.Annotation = &entsql.Annotation{
Table: "settings",
}
SubscriptionPlansTable.Annotation = &entsql.Annotation{
Table: "subscription_plans",
}
TLSFingerprintProfilesTable.Annotation = &entsql.Annotation{
Table: "tls_fingerprint_profiles",
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,150 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/paymentauditlog"
)
// PaymentAuditLog is the model entity for the PaymentAuditLog schema.
type PaymentAuditLog struct {
config `json:"-"`
// ID of the ent.
ID int64 `json:"id,omitempty"`
// OrderID holds the value of the "order_id" field.
OrderID string `json:"order_id,omitempty"`
// Action holds the value of the "action" field.
Action string `json:"action,omitempty"`
// Detail holds the value of the "detail" field.
Detail string `json:"detail,omitempty"`
// Operator holds the value of the "operator" field.
Operator string `json:"operator,omitempty"`
// CreatedAt holds the value of the "created_at" field.
CreatedAt time.Time `json:"created_at,omitempty"`
selectValues sql.SelectValues
}
// scanValues returns the types for scanning values from sql.Rows.
func (*PaymentAuditLog) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case paymentauditlog.FieldID:
values[i] = new(sql.NullInt64)
case paymentauditlog.FieldOrderID, paymentauditlog.FieldAction, paymentauditlog.FieldDetail, paymentauditlog.FieldOperator:
values[i] = new(sql.NullString)
case paymentauditlog.FieldCreatedAt:
values[i] = new(sql.NullTime)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the PaymentAuditLog fields.
func (_m *PaymentAuditLog) assignValues(columns []string, values []any) error {
if m, n := len(values), len(columns); m < n {
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
}
for i := range columns {
switch columns[i] {
case paymentauditlog.FieldID:
value, ok := values[i].(*sql.NullInt64)
if !ok {
return fmt.Errorf("unexpected type %T for field id", value)
}
_m.ID = int64(value.Int64)
case paymentauditlog.FieldOrderID:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field order_id", values[i])
} else if value.Valid {
_m.OrderID = value.String
}
case paymentauditlog.FieldAction:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field action", values[i])
} else if value.Valid {
_m.Action = value.String
}
case paymentauditlog.FieldDetail:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field detail", values[i])
} else if value.Valid {
_m.Detail = value.String
}
case paymentauditlog.FieldOperator:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field operator", values[i])
} else if value.Valid {
_m.Operator = value.String
}
case paymentauditlog.FieldCreatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field created_at", values[i])
} else if value.Valid {
_m.CreatedAt = value.Time
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the PaymentAuditLog.
// This includes values selected through modifiers, order, etc.
func (_m *PaymentAuditLog) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// Update returns a builder for updating this PaymentAuditLog.
// Note that you need to call PaymentAuditLog.Unwrap() before calling this method if this PaymentAuditLog
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *PaymentAuditLog) Update() *PaymentAuditLogUpdateOne {
return NewPaymentAuditLogClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the PaymentAuditLog entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (_m *PaymentAuditLog) Unwrap() *PaymentAuditLog {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: PaymentAuditLog is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *PaymentAuditLog) String() string {
var builder strings.Builder
builder.WriteString("PaymentAuditLog(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("order_id=")
builder.WriteString(_m.OrderID)
builder.WriteString(", ")
builder.WriteString("action=")
builder.WriteString(_m.Action)
builder.WriteString(", ")
builder.WriteString("detail=")
builder.WriteString(_m.Detail)
builder.WriteString(", ")
builder.WriteString("operator=")
builder.WriteString(_m.Operator)
builder.WriteString(", ")
builder.WriteString("created_at=")
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
builder.WriteByte(')')
return builder.String()
}
// PaymentAuditLogs is a parsable slice of PaymentAuditLog.
type PaymentAuditLogs []*PaymentAuditLog

View File

@ -0,0 +1,96 @@
// Code generated by ent, DO NOT EDIT.
package paymentauditlog
import (
"time"
"entgo.io/ent/dialect/sql"
)
const (
// Label holds the string label denoting the paymentauditlog type in the database.
Label = "payment_audit_log"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldOrderID holds the string denoting the order_id field in the database.
FieldOrderID = "order_id"
// FieldAction holds the string denoting the action field in the database.
FieldAction = "action"
// FieldDetail holds the string denoting the detail field in the database.
FieldDetail = "detail"
// FieldOperator holds the string denoting the operator field in the database.
FieldOperator = "operator"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt = "created_at"
// Table holds the table name of the paymentauditlog in the database.
Table = "payment_audit_logs"
)
// Columns holds all SQL columns for paymentauditlog fields.
var Columns = []string{
FieldID,
FieldOrderID,
FieldAction,
FieldDetail,
FieldOperator,
FieldCreatedAt,
}
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
return false
}
var (
// OrderIDValidator is a validator for the "order_id" field. It is called by the builders before save.
OrderIDValidator func(string) error
// ActionValidator is a validator for the "action" field. It is called by the builders before save.
ActionValidator func(string) error
// DefaultDetail holds the default value on creation for the "detail" field.
DefaultDetail string
// DefaultOperator holds the default value on creation for the "operator" field.
DefaultOperator string
// OperatorValidator is a validator for the "operator" field. It is called by the builders before save.
OperatorValidator func(string) error
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
DefaultCreatedAt func() time.Time
)
// OrderOption defines the ordering options for the PaymentAuditLog queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByOrderID orders the results by the order_id field.
func ByOrderID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldOrderID, opts...).ToFunc()
}
// ByAction orders the results by the action field.
func ByAction(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAction, opts...).ToFunc()
}
// ByDetail orders the results by the detail field.
func ByDetail(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldDetail, opts...).ToFunc()
}
// ByOperator orders the results by the operator field.
func ByOperator(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldOperator, opts...).ToFunc()
}
// ByCreatedAt orders the results by the created_at field.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
}

View File

@ -0,0 +1,395 @@
// Code generated by ent, DO NOT EDIT.
package paymentauditlog
import (
"time"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id int64) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int64) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int64) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int64) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int64) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int64) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int64) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int64) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int64) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLTE(FieldID, id))
}
// OrderID applies equality check predicate on the "order_id" field. It's identical to OrderIDEQ.
func OrderID(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldOrderID, v))
}
// Action applies equality check predicate on the "action" field. It's identical to ActionEQ.
func Action(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldAction, v))
}
// Detail applies equality check predicate on the "detail" field. It's identical to DetailEQ.
func Detail(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldDetail, v))
}
// Operator applies equality check predicate on the "operator" field. It's identical to OperatorEQ.
func Operator(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldOperator, v))
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldCreatedAt, v))
}
// OrderIDEQ applies the EQ predicate on the "order_id" field.
func OrderIDEQ(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldOrderID, v))
}
// OrderIDNEQ applies the NEQ predicate on the "order_id" field.
func OrderIDNEQ(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNEQ(FieldOrderID, v))
}
// OrderIDIn applies the In predicate on the "order_id" field.
func OrderIDIn(vs ...string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldIn(FieldOrderID, vs...))
}
// OrderIDNotIn applies the NotIn predicate on the "order_id" field.
func OrderIDNotIn(vs ...string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNotIn(FieldOrderID, vs...))
}
// OrderIDGT applies the GT predicate on the "order_id" field.
func OrderIDGT(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGT(FieldOrderID, v))
}
// OrderIDGTE applies the GTE predicate on the "order_id" field.
func OrderIDGTE(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGTE(FieldOrderID, v))
}
// OrderIDLT applies the LT predicate on the "order_id" field.
func OrderIDLT(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLT(FieldOrderID, v))
}
// OrderIDLTE applies the LTE predicate on the "order_id" field.
func OrderIDLTE(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLTE(FieldOrderID, v))
}
// OrderIDContains applies the Contains predicate on the "order_id" field.
func OrderIDContains(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldContains(FieldOrderID, v))
}
// OrderIDHasPrefix applies the HasPrefix predicate on the "order_id" field.
func OrderIDHasPrefix(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldHasPrefix(FieldOrderID, v))
}
// OrderIDHasSuffix applies the HasSuffix predicate on the "order_id" field.
func OrderIDHasSuffix(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldHasSuffix(FieldOrderID, v))
}
// OrderIDEqualFold applies the EqualFold predicate on the "order_id" field.
func OrderIDEqualFold(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEqualFold(FieldOrderID, v))
}
// OrderIDContainsFold applies the ContainsFold predicate on the "order_id" field.
func OrderIDContainsFold(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldContainsFold(FieldOrderID, v))
}
// ActionEQ applies the EQ predicate on the "action" field.
func ActionEQ(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldAction, v))
}
// ActionNEQ applies the NEQ predicate on the "action" field.
func ActionNEQ(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNEQ(FieldAction, v))
}
// ActionIn applies the In predicate on the "action" field.
func ActionIn(vs ...string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldIn(FieldAction, vs...))
}
// ActionNotIn applies the NotIn predicate on the "action" field.
func ActionNotIn(vs ...string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNotIn(FieldAction, vs...))
}
// ActionGT applies the GT predicate on the "action" field.
func ActionGT(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGT(FieldAction, v))
}
// ActionGTE applies the GTE predicate on the "action" field.
func ActionGTE(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGTE(FieldAction, v))
}
// ActionLT applies the LT predicate on the "action" field.
func ActionLT(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLT(FieldAction, v))
}
// ActionLTE applies the LTE predicate on the "action" field.
func ActionLTE(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLTE(FieldAction, v))
}
// ActionContains applies the Contains predicate on the "action" field.
func ActionContains(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldContains(FieldAction, v))
}
// ActionHasPrefix applies the HasPrefix predicate on the "action" field.
func ActionHasPrefix(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldHasPrefix(FieldAction, v))
}
// ActionHasSuffix applies the HasSuffix predicate on the "action" field.
func ActionHasSuffix(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldHasSuffix(FieldAction, v))
}
// ActionEqualFold applies the EqualFold predicate on the "action" field.
func ActionEqualFold(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEqualFold(FieldAction, v))
}
// ActionContainsFold applies the ContainsFold predicate on the "action" field.
func ActionContainsFold(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldContainsFold(FieldAction, v))
}
// DetailEQ applies the EQ predicate on the "detail" field.
func DetailEQ(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldDetail, v))
}
// DetailNEQ applies the NEQ predicate on the "detail" field.
func DetailNEQ(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNEQ(FieldDetail, v))
}
// DetailIn applies the In predicate on the "detail" field.
func DetailIn(vs ...string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldIn(FieldDetail, vs...))
}
// DetailNotIn applies the NotIn predicate on the "detail" field.
func DetailNotIn(vs ...string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNotIn(FieldDetail, vs...))
}
// DetailGT applies the GT predicate on the "detail" field.
func DetailGT(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGT(FieldDetail, v))
}
// DetailGTE applies the GTE predicate on the "detail" field.
func DetailGTE(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGTE(FieldDetail, v))
}
// DetailLT applies the LT predicate on the "detail" field.
func DetailLT(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLT(FieldDetail, v))
}
// DetailLTE applies the LTE predicate on the "detail" field.
func DetailLTE(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLTE(FieldDetail, v))
}
// DetailContains applies the Contains predicate on the "detail" field.
func DetailContains(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldContains(FieldDetail, v))
}
// DetailHasPrefix applies the HasPrefix predicate on the "detail" field.
func DetailHasPrefix(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldHasPrefix(FieldDetail, v))
}
// DetailHasSuffix applies the HasSuffix predicate on the "detail" field.
func DetailHasSuffix(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldHasSuffix(FieldDetail, v))
}
// DetailEqualFold applies the EqualFold predicate on the "detail" field.
func DetailEqualFold(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEqualFold(FieldDetail, v))
}
// DetailContainsFold applies the ContainsFold predicate on the "detail" field.
func DetailContainsFold(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldContainsFold(FieldDetail, v))
}
// OperatorEQ applies the EQ predicate on the "operator" field.
func OperatorEQ(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldOperator, v))
}
// OperatorNEQ applies the NEQ predicate on the "operator" field.
func OperatorNEQ(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNEQ(FieldOperator, v))
}
// OperatorIn applies the In predicate on the "operator" field.
func OperatorIn(vs ...string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldIn(FieldOperator, vs...))
}
// OperatorNotIn applies the NotIn predicate on the "operator" field.
func OperatorNotIn(vs ...string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNotIn(FieldOperator, vs...))
}
// OperatorGT applies the GT predicate on the "operator" field.
func OperatorGT(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGT(FieldOperator, v))
}
// OperatorGTE applies the GTE predicate on the "operator" field.
func OperatorGTE(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGTE(FieldOperator, v))
}
// OperatorLT applies the LT predicate on the "operator" field.
func OperatorLT(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLT(FieldOperator, v))
}
// OperatorLTE applies the LTE predicate on the "operator" field.
func OperatorLTE(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLTE(FieldOperator, v))
}
// OperatorContains applies the Contains predicate on the "operator" field.
func OperatorContains(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldContains(FieldOperator, v))
}
// OperatorHasPrefix applies the HasPrefix predicate on the "operator" field.
func OperatorHasPrefix(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldHasPrefix(FieldOperator, v))
}
// OperatorHasSuffix applies the HasSuffix predicate on the "operator" field.
func OperatorHasSuffix(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldHasSuffix(FieldOperator, v))
}
// OperatorEqualFold applies the EqualFold predicate on the "operator" field.
func OperatorEqualFold(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEqualFold(FieldOperator, v))
}
// OperatorContainsFold applies the ContainsFold predicate on the "operator" field.
func OperatorContainsFold(v string) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldContainsFold(FieldOperator, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.FieldLTE(FieldCreatedAt, v))
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.PaymentAuditLog) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.PaymentAuditLog) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.PaymentAuditLog) predicate.PaymentAuditLog {
return predicate.PaymentAuditLog(sql.NotPredicates(p))
}

View File

@ -0,0 +1,696 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/paymentauditlog"
)
// PaymentAuditLogCreate is the builder for creating a PaymentAuditLog entity.
type PaymentAuditLogCreate struct {
config
mutation *PaymentAuditLogMutation
hooks []Hook
conflict []sql.ConflictOption
}
// SetOrderID sets the "order_id" field.
func (_c *PaymentAuditLogCreate) SetOrderID(v string) *PaymentAuditLogCreate {
_c.mutation.SetOrderID(v)
return _c
}
// SetAction sets the "action" field.
func (_c *PaymentAuditLogCreate) SetAction(v string) *PaymentAuditLogCreate {
_c.mutation.SetAction(v)
return _c
}
// SetDetail sets the "detail" field.
func (_c *PaymentAuditLogCreate) SetDetail(v string) *PaymentAuditLogCreate {
_c.mutation.SetDetail(v)
return _c
}
// SetNillableDetail sets the "detail" field if the given value is not nil.
func (_c *PaymentAuditLogCreate) SetNillableDetail(v *string) *PaymentAuditLogCreate {
if v != nil {
_c.SetDetail(*v)
}
return _c
}
// SetOperator sets the "operator" field.
func (_c *PaymentAuditLogCreate) SetOperator(v string) *PaymentAuditLogCreate {
_c.mutation.SetOperator(v)
return _c
}
// SetNillableOperator sets the "operator" field if the given value is not nil.
func (_c *PaymentAuditLogCreate) SetNillableOperator(v *string) *PaymentAuditLogCreate {
if v != nil {
_c.SetOperator(*v)
}
return _c
}
// SetCreatedAt sets the "created_at" field.
func (_c *PaymentAuditLogCreate) SetCreatedAt(v time.Time) *PaymentAuditLogCreate {
_c.mutation.SetCreatedAt(v)
return _c
}
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
func (_c *PaymentAuditLogCreate) SetNillableCreatedAt(v *time.Time) *PaymentAuditLogCreate {
if v != nil {
_c.SetCreatedAt(*v)
}
return _c
}
// Mutation returns the PaymentAuditLogMutation object of the builder.
func (_c *PaymentAuditLogCreate) Mutation() *PaymentAuditLogMutation {
return _c.mutation
}
// Save creates the PaymentAuditLog in the database.
func (_c *PaymentAuditLogCreate) Save(ctx context.Context) (*PaymentAuditLog, error) {
_c.defaults()
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
}
// SaveX calls Save and panics if Save returns an error.
func (_c *PaymentAuditLogCreate) SaveX(ctx context.Context) *PaymentAuditLog {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *PaymentAuditLogCreate) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *PaymentAuditLogCreate) ExecX(ctx context.Context) {
if err := _c.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (_c *PaymentAuditLogCreate) defaults() {
if _, ok := _c.mutation.Detail(); !ok {
v := paymentauditlog.DefaultDetail
_c.mutation.SetDetail(v)
}
if _, ok := _c.mutation.Operator(); !ok {
v := paymentauditlog.DefaultOperator
_c.mutation.SetOperator(v)
}
if _, ok := _c.mutation.CreatedAt(); !ok {
v := paymentauditlog.DefaultCreatedAt()
_c.mutation.SetCreatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_c *PaymentAuditLogCreate) check() error {
if _, ok := _c.mutation.OrderID(); !ok {
return &ValidationError{Name: "order_id", err: errors.New(`ent: missing required field "PaymentAuditLog.order_id"`)}
}
if v, ok := _c.mutation.OrderID(); ok {
if err := paymentauditlog.OrderIDValidator(v); err != nil {
return &ValidationError{Name: "order_id", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.order_id": %w`, err)}
}
}
if _, ok := _c.mutation.Action(); !ok {
return &ValidationError{Name: "action", err: errors.New(`ent: missing required field "PaymentAuditLog.action"`)}
}
if v, ok := _c.mutation.Action(); ok {
if err := paymentauditlog.ActionValidator(v); err != nil {
return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.action": %w`, err)}
}
}
if _, ok := _c.mutation.Detail(); !ok {
return &ValidationError{Name: "detail", err: errors.New(`ent: missing required field "PaymentAuditLog.detail"`)}
}
if _, ok := _c.mutation.Operator(); !ok {
return &ValidationError{Name: "operator", err: errors.New(`ent: missing required field "PaymentAuditLog.operator"`)}
}
if v, ok := _c.mutation.Operator(); ok {
if err := paymentauditlog.OperatorValidator(v); err != nil {
return &ValidationError{Name: "operator", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.operator": %w`, err)}
}
}
if _, ok := _c.mutation.CreatedAt(); !ok {
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "PaymentAuditLog.created_at"`)}
}
return nil
}
func (_c *PaymentAuditLogCreate) sqlSave(ctx context.Context) (*PaymentAuditLog, error) {
if err := _c.check(); err != nil {
return nil, err
}
_node, _spec := _c.createSpec()
if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
id := _spec.ID.Value.(int64)
_node.ID = int64(id)
_c.mutation.id = &_node.ID
_c.mutation.done = true
return _node, nil
}
func (_c *PaymentAuditLogCreate) createSpec() (*PaymentAuditLog, *sqlgraph.CreateSpec) {
var (
_node = &PaymentAuditLog{config: _c.config}
_spec = sqlgraph.NewCreateSpec(paymentauditlog.Table, sqlgraph.NewFieldSpec(paymentauditlog.FieldID, field.TypeInt64))
)
_spec.OnConflict = _c.conflict
if value, ok := _c.mutation.OrderID(); ok {
_spec.SetField(paymentauditlog.FieldOrderID, field.TypeString, value)
_node.OrderID = value
}
if value, ok := _c.mutation.Action(); ok {
_spec.SetField(paymentauditlog.FieldAction, field.TypeString, value)
_node.Action = value
}
if value, ok := _c.mutation.Detail(); ok {
_spec.SetField(paymentauditlog.FieldDetail, field.TypeString, value)
_node.Detail = value
}
if value, ok := _c.mutation.Operator(); ok {
_spec.SetField(paymentauditlog.FieldOperator, field.TypeString, value)
_node.Operator = value
}
if value, ok := _c.mutation.CreatedAt(); ok {
_spec.SetField(paymentauditlog.FieldCreatedAt, field.TypeTime, value)
_node.CreatedAt = value
}
return _node, _spec
}
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
// of the `INSERT` statement. For example:
//
// client.PaymentAuditLog.Create().
// SetOrderID(v).
// OnConflict(
// // Update the row with the new values
// // the was proposed for insertion.
// sql.ResolveWithNewValues(),
// ).
// // Override some of the fields with custom
// // update values.
// Update(func(u *ent.PaymentAuditLogUpsert) {
// SetOrderID(v+v).
// }).
// Exec(ctx)
func (_c *PaymentAuditLogCreate) OnConflict(opts ...sql.ConflictOption) *PaymentAuditLogUpsertOne {
_c.conflict = opts
return &PaymentAuditLogUpsertOne{
create: _c,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.PaymentAuditLog.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (_c *PaymentAuditLogCreate) OnConflictColumns(columns ...string) *PaymentAuditLogUpsertOne {
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
return &PaymentAuditLogUpsertOne{
create: _c,
}
}
type (
// PaymentAuditLogUpsertOne is the builder for "upsert"-ing
// one PaymentAuditLog node.
PaymentAuditLogUpsertOne struct {
create *PaymentAuditLogCreate
}
// PaymentAuditLogUpsert is the "OnConflict" setter.
PaymentAuditLogUpsert struct {
*sql.UpdateSet
}
)
// SetOrderID sets the "order_id" field.
func (u *PaymentAuditLogUpsert) SetOrderID(v string) *PaymentAuditLogUpsert {
u.Set(paymentauditlog.FieldOrderID, v)
return u
}
// UpdateOrderID sets the "order_id" field to the value that was provided on create.
func (u *PaymentAuditLogUpsert) UpdateOrderID() *PaymentAuditLogUpsert {
u.SetExcluded(paymentauditlog.FieldOrderID)
return u
}
// SetAction sets the "action" field.
func (u *PaymentAuditLogUpsert) SetAction(v string) *PaymentAuditLogUpsert {
u.Set(paymentauditlog.FieldAction, v)
return u
}
// UpdateAction sets the "action" field to the value that was provided on create.
func (u *PaymentAuditLogUpsert) UpdateAction() *PaymentAuditLogUpsert {
u.SetExcluded(paymentauditlog.FieldAction)
return u
}
// SetDetail sets the "detail" field.
func (u *PaymentAuditLogUpsert) SetDetail(v string) *PaymentAuditLogUpsert {
u.Set(paymentauditlog.FieldDetail, v)
return u
}
// UpdateDetail sets the "detail" field to the value that was provided on create.
func (u *PaymentAuditLogUpsert) UpdateDetail() *PaymentAuditLogUpsert {
u.SetExcluded(paymentauditlog.FieldDetail)
return u
}
// SetOperator sets the "operator" field.
func (u *PaymentAuditLogUpsert) SetOperator(v string) *PaymentAuditLogUpsert {
u.Set(paymentauditlog.FieldOperator, v)
return u
}
// UpdateOperator sets the "operator" field to the value that was provided on create.
func (u *PaymentAuditLogUpsert) UpdateOperator() *PaymentAuditLogUpsert {
u.SetExcluded(paymentauditlog.FieldOperator)
return u
}
// UpdateNewValues updates the mutable fields using the new values that were set on create.
// Using this option is equivalent to using:
//
// client.PaymentAuditLog.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *PaymentAuditLogUpsertOne) UpdateNewValues() *PaymentAuditLogUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
if _, exists := u.create.mutation.CreatedAt(); exists {
s.SetIgnore(paymentauditlog.FieldCreatedAt)
}
}))
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.PaymentAuditLog.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *PaymentAuditLogUpsertOne) Ignore() *PaymentAuditLogUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
return u
}
// DoNothing configures the conflict_action to `DO NOTHING`.
// Supported only by SQLite and PostgreSQL.
func (u *PaymentAuditLogUpsertOne) DoNothing() *PaymentAuditLogUpsertOne {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the PaymentAuditLogCreate.OnConflict
// documentation for more info.
func (u *PaymentAuditLogUpsertOne) Update(set func(*PaymentAuditLogUpsert)) *PaymentAuditLogUpsertOne {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&PaymentAuditLogUpsert{UpdateSet: update})
}))
return u
}
// SetOrderID sets the "order_id" field.
func (u *PaymentAuditLogUpsertOne) SetOrderID(v string) *PaymentAuditLogUpsertOne {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.SetOrderID(v)
})
}
// UpdateOrderID sets the "order_id" field to the value that was provided on create.
func (u *PaymentAuditLogUpsertOne) UpdateOrderID() *PaymentAuditLogUpsertOne {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.UpdateOrderID()
})
}
// SetAction sets the "action" field.
func (u *PaymentAuditLogUpsertOne) SetAction(v string) *PaymentAuditLogUpsertOne {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.SetAction(v)
})
}
// UpdateAction sets the "action" field to the value that was provided on create.
func (u *PaymentAuditLogUpsertOne) UpdateAction() *PaymentAuditLogUpsertOne {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.UpdateAction()
})
}
// SetDetail sets the "detail" field.
func (u *PaymentAuditLogUpsertOne) SetDetail(v string) *PaymentAuditLogUpsertOne {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.SetDetail(v)
})
}
// UpdateDetail sets the "detail" field to the value that was provided on create.
func (u *PaymentAuditLogUpsertOne) UpdateDetail() *PaymentAuditLogUpsertOne {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.UpdateDetail()
})
}
// SetOperator sets the "operator" field.
func (u *PaymentAuditLogUpsertOne) SetOperator(v string) *PaymentAuditLogUpsertOne {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.SetOperator(v)
})
}
// UpdateOperator sets the "operator" field to the value that was provided on create.
func (u *PaymentAuditLogUpsertOne) UpdateOperator() *PaymentAuditLogUpsertOne {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.UpdateOperator()
})
}
// Exec executes the query.
func (u *PaymentAuditLogUpsertOne) Exec(ctx context.Context) error {
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for PaymentAuditLogCreate.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *PaymentAuditLogUpsertOne) ExecX(ctx context.Context) {
if err := u.create.Exec(ctx); err != nil {
panic(err)
}
}
// Exec executes the UPSERT query and returns the inserted/updated ID.
func (u *PaymentAuditLogUpsertOne) ID(ctx context.Context) (id int64, err error) {
node, err := u.create.Save(ctx)
if err != nil {
return id, err
}
return node.ID, nil
}
// IDX is like ID, but panics if an error occurs.
func (u *PaymentAuditLogUpsertOne) IDX(ctx context.Context) int64 {
id, err := u.ID(ctx)
if err != nil {
panic(err)
}
return id
}
// PaymentAuditLogCreateBulk is the builder for creating many PaymentAuditLog entities in bulk.
type PaymentAuditLogCreateBulk struct {
config
err error
builders []*PaymentAuditLogCreate
conflict []sql.ConflictOption
}
// Save creates the PaymentAuditLog entities in the database.
func (_c *PaymentAuditLogCreateBulk) Save(ctx context.Context) ([]*PaymentAuditLog, error) {
if _c.err != nil {
return nil, _c.err
}
specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
nodes := make([]*PaymentAuditLog, len(_c.builders))
mutators := make([]Mutator, len(_c.builders))
for i := range _c.builders {
func(i int, root context.Context) {
builder := _c.builders[i]
builder.defaults()
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
mutation, ok := m.(*PaymentAuditLogMutation)
if !ok {
return nil, fmt.Errorf("unexpected mutation type %T", m)
}
if err := builder.check(); err != nil {
return nil, err
}
builder.mutation = mutation
var err error
nodes[i], specs[i] = builder.createSpec()
if i < len(mutators)-1 {
_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)
} else {
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
spec.OnConflict = _c.conflict
// Invoke the actual operation on the latest mutation in the chain.
if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {
if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
}
}
if err != nil {
return nil, err
}
mutation.id = &nodes[i].ID
if specs[i].ID.Value != nil {
id := specs[i].ID.Value.(int64)
nodes[i].ID = int64(id)
}
mutation.done = true
return nodes[i], nil
})
for i := len(builder.hooks) - 1; i >= 0; i-- {
mut = builder.hooks[i](mut)
}
mutators[i] = mut
}(i, ctx)
}
if len(mutators) > 0 {
if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {
return nil, err
}
}
return nodes, nil
}
// SaveX is like Save, but panics if an error occurs.
func (_c *PaymentAuditLogCreateBulk) SaveX(ctx context.Context) []*PaymentAuditLog {
v, err := _c.Save(ctx)
if err != nil {
panic(err)
}
return v
}
// Exec executes the query.
func (_c *PaymentAuditLogCreateBulk) Exec(ctx context.Context) error {
_, err := _c.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_c *PaymentAuditLogCreateBulk) ExecX(ctx context.Context) {
if err := _c.Exec(ctx); err != nil {
panic(err)
}
}
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
// of the `INSERT` statement. For example:
//
// client.PaymentAuditLog.CreateBulk(builders...).
// OnConflict(
// // Update the row with the new values
// // the was proposed for insertion.
// sql.ResolveWithNewValues(),
// ).
// // Override some of the fields with custom
// // update values.
// Update(func(u *ent.PaymentAuditLogUpsert) {
// SetOrderID(v+v).
// }).
// Exec(ctx)
func (_c *PaymentAuditLogCreateBulk) OnConflict(opts ...sql.ConflictOption) *PaymentAuditLogUpsertBulk {
_c.conflict = opts
return &PaymentAuditLogUpsertBulk{
create: _c,
}
}
// OnConflictColumns calls `OnConflict` and configures the columns
// as conflict target. Using this option is equivalent to using:
//
// client.PaymentAuditLog.Create().
// OnConflict(sql.ConflictColumns(columns...)).
// Exec(ctx)
func (_c *PaymentAuditLogCreateBulk) OnConflictColumns(columns ...string) *PaymentAuditLogUpsertBulk {
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
return &PaymentAuditLogUpsertBulk{
create: _c,
}
}
// PaymentAuditLogUpsertBulk is the builder for "upsert"-ing
// a bulk of PaymentAuditLog nodes.
type PaymentAuditLogUpsertBulk struct {
create *PaymentAuditLogCreateBulk
}
// UpdateNewValues updates the mutable fields using the new values that
// were set on create. Using this option is equivalent to using:
//
// client.PaymentAuditLog.Create().
// OnConflict(
// sql.ResolveWithNewValues(),
// ).
// Exec(ctx)
func (u *PaymentAuditLogUpsertBulk) UpdateNewValues() *PaymentAuditLogUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
for _, b := range u.create.builders {
if _, exists := b.mutation.CreatedAt(); exists {
s.SetIgnore(paymentauditlog.FieldCreatedAt)
}
}
}))
return u
}
// Ignore sets each column to itself in case of conflict.
// Using this option is equivalent to using:
//
// client.PaymentAuditLog.Create().
// OnConflict(sql.ResolveWithIgnore()).
// Exec(ctx)
func (u *PaymentAuditLogUpsertBulk) Ignore() *PaymentAuditLogUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
return u
}
// DoNothing configures the conflict_action to `DO NOTHING`.
// Supported only by SQLite and PostgreSQL.
func (u *PaymentAuditLogUpsertBulk) DoNothing() *PaymentAuditLogUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.DoNothing())
return u
}
// Update allows overriding fields `UPDATE` values. See the PaymentAuditLogCreateBulk.OnConflict
// documentation for more info.
func (u *PaymentAuditLogUpsertBulk) Update(set func(*PaymentAuditLogUpsert)) *PaymentAuditLogUpsertBulk {
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
set(&PaymentAuditLogUpsert{UpdateSet: update})
}))
return u
}
// SetOrderID sets the "order_id" field.
func (u *PaymentAuditLogUpsertBulk) SetOrderID(v string) *PaymentAuditLogUpsertBulk {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.SetOrderID(v)
})
}
// UpdateOrderID sets the "order_id" field to the value that was provided on create.
func (u *PaymentAuditLogUpsertBulk) UpdateOrderID() *PaymentAuditLogUpsertBulk {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.UpdateOrderID()
})
}
// SetAction sets the "action" field.
func (u *PaymentAuditLogUpsertBulk) SetAction(v string) *PaymentAuditLogUpsertBulk {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.SetAction(v)
})
}
// UpdateAction sets the "action" field to the value that was provided on create.
func (u *PaymentAuditLogUpsertBulk) UpdateAction() *PaymentAuditLogUpsertBulk {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.UpdateAction()
})
}
// SetDetail sets the "detail" field.
func (u *PaymentAuditLogUpsertBulk) SetDetail(v string) *PaymentAuditLogUpsertBulk {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.SetDetail(v)
})
}
// UpdateDetail sets the "detail" field to the value that was provided on create.
func (u *PaymentAuditLogUpsertBulk) UpdateDetail() *PaymentAuditLogUpsertBulk {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.UpdateDetail()
})
}
// SetOperator sets the "operator" field.
func (u *PaymentAuditLogUpsertBulk) SetOperator(v string) *PaymentAuditLogUpsertBulk {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.SetOperator(v)
})
}
// UpdateOperator sets the "operator" field to the value that was provided on create.
func (u *PaymentAuditLogUpsertBulk) UpdateOperator() *PaymentAuditLogUpsertBulk {
return u.Update(func(s *PaymentAuditLogUpsert) {
s.UpdateOperator()
})
}
// Exec executes the query.
func (u *PaymentAuditLogUpsertBulk) Exec(ctx context.Context) error {
if u.create.err != nil {
return u.create.err
}
for i, b := range u.create.builders {
if len(b.conflict) != 0 {
return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the PaymentAuditLogCreateBulk instead", i)
}
}
if len(u.create.conflict) == 0 {
return errors.New("ent: missing options for PaymentAuditLogCreateBulk.OnConflict")
}
return u.create.Exec(ctx)
}
// ExecX is like Exec, but panics if an error occurs.
func (u *PaymentAuditLogUpsertBulk) ExecX(ctx context.Context) {
if err := u.create.Exec(ctx); err != nil {
panic(err)
}
}

View File

@ -0,0 +1,88 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/paymentauditlog"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// PaymentAuditLogDelete is the builder for deleting a PaymentAuditLog entity.
type PaymentAuditLogDelete struct {
config
hooks []Hook
mutation *PaymentAuditLogMutation
}
// Where appends a list predicates to the PaymentAuditLogDelete builder.
func (_d *PaymentAuditLogDelete) Where(ps ...predicate.PaymentAuditLog) *PaymentAuditLogDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *PaymentAuditLogDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *PaymentAuditLogDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *PaymentAuditLogDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(paymentauditlog.Table, sqlgraph.NewFieldSpec(paymentauditlog.FieldID, field.TypeInt64))
if ps := _d.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
_d.mutation.done = true
return affected, err
}
// PaymentAuditLogDeleteOne is the builder for deleting a single PaymentAuditLog entity.
type PaymentAuditLogDeleteOne struct {
_d *PaymentAuditLogDelete
}
// Where appends a list predicates to the PaymentAuditLogDelete builder.
func (_d *PaymentAuditLogDeleteOne) Where(ps ...predicate.PaymentAuditLog) *PaymentAuditLogDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *PaymentAuditLogDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{paymentauditlog.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *PaymentAuditLogDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}

View File

@ -0,0 +1,564 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"fmt"
"math"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/paymentauditlog"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// PaymentAuditLogQuery is the builder for querying PaymentAuditLog entities.
type PaymentAuditLogQuery struct {
config
ctx *QueryContext
order []paymentauditlog.OrderOption
inters []Interceptor
predicates []predicate.PaymentAuditLog
modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Where adds a new predicate for the PaymentAuditLogQuery builder.
func (_q *PaymentAuditLogQuery) Where(ps ...predicate.PaymentAuditLog) *PaymentAuditLogQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *PaymentAuditLogQuery) Limit(limit int) *PaymentAuditLogQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *PaymentAuditLogQuery) Offset(offset int) *PaymentAuditLogQuery {
_q.ctx.Offset = &offset
return _q
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func (_q *PaymentAuditLogQuery) Unique(unique bool) *PaymentAuditLogQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *PaymentAuditLogQuery) Order(o ...paymentauditlog.OrderOption) *PaymentAuditLogQuery {
_q.order = append(_q.order, o...)
return _q
}
// First returns the first PaymentAuditLog entity from the query.
// Returns a *NotFoundError when no PaymentAuditLog was found.
func (_q *PaymentAuditLogQuery) First(ctx context.Context) (*PaymentAuditLog, error) {
nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{paymentauditlog.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *PaymentAuditLogQuery) FirstX(ctx context.Context) *PaymentAuditLog {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first PaymentAuditLog ID from the query.
// Returns a *NotFoundError when no PaymentAuditLog ID was found.
func (_q *PaymentAuditLogQuery) FirstID(ctx context.Context) (id int64, err error) {
var ids []int64
if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{paymentauditlog.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *PaymentAuditLogQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single PaymentAuditLog entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one PaymentAuditLog entity is found.
// Returns a *NotFoundError when no PaymentAuditLog entities are found.
func (_q *PaymentAuditLogQuery) Only(ctx context.Context) (*PaymentAuditLog, error) {
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{paymentauditlog.Label}
default:
return nil, &NotSingularError{paymentauditlog.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *PaymentAuditLogQuery) OnlyX(ctx context.Context) *PaymentAuditLog {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only PaymentAuditLog ID in the query.
// Returns a *NotSingularError when more than one PaymentAuditLog ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *PaymentAuditLogQuery) OnlyID(ctx context.Context) (id int64, err error) {
var ids []int64
if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{paymentauditlog.Label}
default:
err = &NotSingularError{paymentauditlog.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *PaymentAuditLogQuery) OnlyIDX(ctx context.Context) int64 {
id, err := _q.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
// All executes the query and returns a list of PaymentAuditLogs.
func (_q *PaymentAuditLogQuery) All(ctx context.Context) ([]*PaymentAuditLog, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*PaymentAuditLog, *PaymentAuditLogQuery]()
return withInterceptors[[]*PaymentAuditLog](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *PaymentAuditLogQuery) AllX(ctx context.Context) []*PaymentAuditLog {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of PaymentAuditLog IDs.
func (_q *PaymentAuditLogQuery) IDs(ctx context.Context) (ids []int64, err error) {
if _q.ctx.Unique == nil && _q.path != nil {
_q.Unique(true)
}
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
if err = _q.Select(paymentauditlog.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *PaymentAuditLogQuery) IDsX(ctx context.Context) []int64 {
ids, err := _q.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
// Count returns the count of the given query.
func (_q *PaymentAuditLogQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
if err := _q.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, _q, querierCount[*PaymentAuditLogQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *PaymentAuditLogQuery) CountX(ctx context.Context) int {
count, err := _q.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func (_q *PaymentAuditLogQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
switch _, err := _q.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
}
// ExistX is like Exist, but panics if an error occurs.
func (_q *PaymentAuditLogQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the PaymentAuditLogQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (_q *PaymentAuditLogQuery) Clone() *PaymentAuditLogQuery {
if _q == nil {
return nil
}
return &PaymentAuditLogQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]paymentauditlog.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.PaymentAuditLog{}, _q.predicates...),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
// Example:
//
// var v []struct {
// OrderID string `json:"order_id,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.PaymentAuditLog.Query().
// GroupBy(paymentauditlog.FieldOrderID).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *PaymentAuditLogQuery) GroupBy(field string, fields ...string) *PaymentAuditLogGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &PaymentAuditLogGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = paymentauditlog.Label
grbuild.scan = grbuild.Scan
return grbuild
}
// Select allows the selection one or more fields/columns for the given query,
// instead of selecting all fields in the entity.
//
// Example:
//
// var v []struct {
// OrderID string `json:"order_id,omitempty"`
// }
//
// client.PaymentAuditLog.Query().
// Select(paymentauditlog.FieldOrderID).
// Scan(ctx, &v)
func (_q *PaymentAuditLogQuery) Select(fields ...string) *PaymentAuditLogSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &PaymentAuditLogSelect{PaymentAuditLogQuery: _q}
sbuild.label = paymentauditlog.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a PaymentAuditLogSelect configured with the given aggregations.
func (_q *PaymentAuditLogQuery) Aggregate(fns ...AggregateFunc) *PaymentAuditLogSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *PaymentAuditLogQuery) prepareQuery(ctx context.Context) error {
for _, inter := range _q.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, _q); err != nil {
return err
}
}
}
for _, f := range _q.ctx.Fields {
if !paymentauditlog.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
if _q.path != nil {
prev, err := _q.path(ctx)
if err != nil {
return err
}
_q.sql = prev
}
return nil
}
func (_q *PaymentAuditLogQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*PaymentAuditLog, error) {
var (
nodes = []*PaymentAuditLog{}
_spec = _q.querySpec()
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*PaymentAuditLog).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &PaymentAuditLog{config: _q.config}
nodes = append(nodes, node)
return node.assignValues(columns, values)
}
if len(_q.modifiers) > 0 {
_spec.Modifiers = _q.modifiers
}
for i := range hooks {
hooks[i](ctx, _spec)
}
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
return nodes, nil
}
func (_q *PaymentAuditLogQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
if len(_q.modifiers) > 0 {
_spec.Modifiers = _q.modifiers
}
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
}
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
}
func (_q *PaymentAuditLogQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(paymentauditlog.Table, paymentauditlog.Columns, sqlgraph.NewFieldSpec(paymentauditlog.FieldID, field.TypeInt64))
_spec.From = _q.sql
if unique := _q.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if _q.path != nil {
_spec.Unique = true
}
if fields := _q.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, paymentauditlog.FieldID)
for i := range fields {
if fields[i] != paymentauditlog.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
}
if ps := _q.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := _q.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := _q.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := _q.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
func (_q *PaymentAuditLogQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(paymentauditlog.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = paymentauditlog.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if _q.sql != nil {
selector = _q.sql
selector.Select(selector.Columns(columns...)...)
}
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
for _, m := range _q.modifiers {
m(selector)
}
for _, p := range _q.predicates {
p(selector)
}
for _, p := range _q.order {
p(selector)
}
if offset := _q.ctx.Offset; offset != nil {
// limit is mandatory for offset clause. We start
// with default value, and override it below if needed.
selector.Offset(*offset).Limit(math.MaxInt32)
}
if limit := _q.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
// updated, deleted or "selected ... for update" by other sessions, until the transaction is
// either committed or rolled-back.
func (_q *PaymentAuditLogQuery) ForUpdate(opts ...sql.LockOption) *PaymentAuditLogQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForUpdate(opts...)
})
return _q
}
// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
// on any rows that are read. Other sessions can read the rows, but cannot modify them
// until your transaction commits.
func (_q *PaymentAuditLogQuery) ForShare(opts ...sql.LockOption) *PaymentAuditLogQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// PaymentAuditLogGroupBy is the group-by builder for PaymentAuditLog entities.
type PaymentAuditLogGroupBy struct {
selector
build *PaymentAuditLogQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *PaymentAuditLogGroupBy) Aggregate(fns ...AggregateFunc) *PaymentAuditLogGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *PaymentAuditLogGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
if err := _g.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*PaymentAuditLogQuery, *PaymentAuditLogGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *PaymentAuditLogGroupBy) sqlScan(ctx context.Context, root *PaymentAuditLogQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(_g.fns))
for _, fn := range _g.fns {
aggregation = append(aggregation, fn(selector))
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
for _, f := range *_g.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*_g.flds...)...)
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
// PaymentAuditLogSelect is the builder for selecting fields of PaymentAuditLog entities.
type PaymentAuditLogSelect struct {
*PaymentAuditLogQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *PaymentAuditLogSelect) Aggregate(fns ...AggregateFunc) *PaymentAuditLogSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *PaymentAuditLogSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
if err := _s.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*PaymentAuditLogQuery, *PaymentAuditLogSelect](ctx, _s.PaymentAuditLogQuery, _s, _s.inters, v)
}
func (_s *PaymentAuditLogSelect) sqlScan(ctx context.Context, root *PaymentAuditLogQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(_s.fns))
for _, fn := range _s.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*_s.selector.flds); {
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _s.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}

View File

@ -0,0 +1,357 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/paymentauditlog"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// PaymentAuditLogUpdate is the builder for updating PaymentAuditLog entities.
type PaymentAuditLogUpdate struct {
config
hooks []Hook
mutation *PaymentAuditLogMutation
}
// Where appends a list predicates to the PaymentAuditLogUpdate builder.
func (_u *PaymentAuditLogUpdate) Where(ps ...predicate.PaymentAuditLog) *PaymentAuditLogUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetOrderID sets the "order_id" field.
func (_u *PaymentAuditLogUpdate) SetOrderID(v string) *PaymentAuditLogUpdate {
_u.mutation.SetOrderID(v)
return _u
}
// SetNillableOrderID sets the "order_id" field if the given value is not nil.
func (_u *PaymentAuditLogUpdate) SetNillableOrderID(v *string) *PaymentAuditLogUpdate {
if v != nil {
_u.SetOrderID(*v)
}
return _u
}
// SetAction sets the "action" field.
func (_u *PaymentAuditLogUpdate) SetAction(v string) *PaymentAuditLogUpdate {
_u.mutation.SetAction(v)
return _u
}
// SetNillableAction sets the "action" field if the given value is not nil.
func (_u *PaymentAuditLogUpdate) SetNillableAction(v *string) *PaymentAuditLogUpdate {
if v != nil {
_u.SetAction(*v)
}
return _u
}
// SetDetail sets the "detail" field.
func (_u *PaymentAuditLogUpdate) SetDetail(v string) *PaymentAuditLogUpdate {
_u.mutation.SetDetail(v)
return _u
}
// SetNillableDetail sets the "detail" field if the given value is not nil.
func (_u *PaymentAuditLogUpdate) SetNillableDetail(v *string) *PaymentAuditLogUpdate {
if v != nil {
_u.SetDetail(*v)
}
return _u
}
// SetOperator sets the "operator" field.
func (_u *PaymentAuditLogUpdate) SetOperator(v string) *PaymentAuditLogUpdate {
_u.mutation.SetOperator(v)
return _u
}
// SetNillableOperator sets the "operator" field if the given value is not nil.
func (_u *PaymentAuditLogUpdate) SetNillableOperator(v *string) *PaymentAuditLogUpdate {
if v != nil {
_u.SetOperator(*v)
}
return _u
}
// Mutation returns the PaymentAuditLogMutation object of the builder.
func (_u *PaymentAuditLogUpdate) Mutation() *PaymentAuditLogMutation {
return _u.mutation
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *PaymentAuditLogUpdate) Save(ctx context.Context) (int, error) {
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *PaymentAuditLogUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *PaymentAuditLogUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *PaymentAuditLogUpdate) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *PaymentAuditLogUpdate) check() error {
if v, ok := _u.mutation.OrderID(); ok {
if err := paymentauditlog.OrderIDValidator(v); err != nil {
return &ValidationError{Name: "order_id", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.order_id": %w`, err)}
}
}
if v, ok := _u.mutation.Action(); ok {
if err := paymentauditlog.ActionValidator(v); err != nil {
return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.action": %w`, err)}
}
}
if v, ok := _u.mutation.Operator(); ok {
if err := paymentauditlog.OperatorValidator(v); err != nil {
return &ValidationError{Name: "operator", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.operator": %w`, err)}
}
}
return nil
}
func (_u *PaymentAuditLogUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(paymentauditlog.Table, paymentauditlog.Columns, sqlgraph.NewFieldSpec(paymentauditlog.FieldID, field.TypeInt64))
if ps := _u.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := _u.mutation.OrderID(); ok {
_spec.SetField(paymentauditlog.FieldOrderID, field.TypeString, value)
}
if value, ok := _u.mutation.Action(); ok {
_spec.SetField(paymentauditlog.FieldAction, field.TypeString, value)
}
if value, ok := _u.mutation.Detail(); ok {
_spec.SetField(paymentauditlog.FieldDetail, field.TypeString, value)
}
if value, ok := _u.mutation.Operator(); ok {
_spec.SetField(paymentauditlog.FieldOperator, field.TypeString, value)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{paymentauditlog.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// PaymentAuditLogUpdateOne is the builder for updating a single PaymentAuditLog entity.
type PaymentAuditLogUpdateOne struct {
config
fields []string
hooks []Hook
mutation *PaymentAuditLogMutation
}
// SetOrderID sets the "order_id" field.
func (_u *PaymentAuditLogUpdateOne) SetOrderID(v string) *PaymentAuditLogUpdateOne {
_u.mutation.SetOrderID(v)
return _u
}
// SetNillableOrderID sets the "order_id" field if the given value is not nil.
func (_u *PaymentAuditLogUpdateOne) SetNillableOrderID(v *string) *PaymentAuditLogUpdateOne {
if v != nil {
_u.SetOrderID(*v)
}
return _u
}
// SetAction sets the "action" field.
func (_u *PaymentAuditLogUpdateOne) SetAction(v string) *PaymentAuditLogUpdateOne {
_u.mutation.SetAction(v)
return _u
}
// SetNillableAction sets the "action" field if the given value is not nil.
func (_u *PaymentAuditLogUpdateOne) SetNillableAction(v *string) *PaymentAuditLogUpdateOne {
if v != nil {
_u.SetAction(*v)
}
return _u
}
// SetDetail sets the "detail" field.
func (_u *PaymentAuditLogUpdateOne) SetDetail(v string) *PaymentAuditLogUpdateOne {
_u.mutation.SetDetail(v)
return _u
}
// SetNillableDetail sets the "detail" field if the given value is not nil.
func (_u *PaymentAuditLogUpdateOne) SetNillableDetail(v *string) *PaymentAuditLogUpdateOne {
if v != nil {
_u.SetDetail(*v)
}
return _u
}
// SetOperator sets the "operator" field.
func (_u *PaymentAuditLogUpdateOne) SetOperator(v string) *PaymentAuditLogUpdateOne {
_u.mutation.SetOperator(v)
return _u
}
// SetNillableOperator sets the "operator" field if the given value is not nil.
func (_u *PaymentAuditLogUpdateOne) SetNillableOperator(v *string) *PaymentAuditLogUpdateOne {
if v != nil {
_u.SetOperator(*v)
}
return _u
}
// Mutation returns the PaymentAuditLogMutation object of the builder.
func (_u *PaymentAuditLogUpdateOne) Mutation() *PaymentAuditLogMutation {
return _u.mutation
}
// Where appends a list predicates to the PaymentAuditLogUpdate builder.
func (_u *PaymentAuditLogUpdateOne) Where(ps ...predicate.PaymentAuditLog) *PaymentAuditLogUpdateOne {
_u.mutation.Where(ps...)
return _u
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (_u *PaymentAuditLogUpdateOne) Select(field string, fields ...string) *PaymentAuditLogUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated PaymentAuditLog entity.
func (_u *PaymentAuditLogUpdateOne) Save(ctx context.Context) (*PaymentAuditLog, error) {
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *PaymentAuditLogUpdateOne) SaveX(ctx context.Context) *PaymentAuditLog {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *PaymentAuditLogUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *PaymentAuditLogUpdateOne) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *PaymentAuditLogUpdateOne) check() error {
if v, ok := _u.mutation.OrderID(); ok {
if err := paymentauditlog.OrderIDValidator(v); err != nil {
return &ValidationError{Name: "order_id", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.order_id": %w`, err)}
}
}
if v, ok := _u.mutation.Action(); ok {
if err := paymentauditlog.ActionValidator(v); err != nil {
return &ValidationError{Name: "action", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.action": %w`, err)}
}
}
if v, ok := _u.mutation.Operator(); ok {
if err := paymentauditlog.OperatorValidator(v); err != nil {
return &ValidationError{Name: "operator", err: fmt.Errorf(`ent: validator failed for field "PaymentAuditLog.operator": %w`, err)}
}
}
return nil
}
func (_u *PaymentAuditLogUpdateOne) sqlSave(ctx context.Context) (_node *PaymentAuditLog, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(paymentauditlog.Table, paymentauditlog.Columns, sqlgraph.NewFieldSpec(paymentauditlog.FieldID, field.TypeInt64))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "PaymentAuditLog.id" for update`)}
}
_spec.Node.ID.Value = id
if fields := _u.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, paymentauditlog.FieldID)
for _, f := range fields {
if !paymentauditlog.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != paymentauditlog.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, f)
}
}
}
if ps := _u.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := _u.mutation.OrderID(); ok {
_spec.SetField(paymentauditlog.FieldOrderID, field.TypeString, value)
}
if value, ok := _u.mutation.Action(); ok {
_spec.SetField(paymentauditlog.FieldAction, field.TypeString, value)
}
if value, ok := _u.mutation.Detail(); ok {
_spec.SetField(paymentauditlog.FieldDetail, field.TypeString, value)
}
if value, ok := _u.mutation.Operator(); ok {
_spec.SetField(paymentauditlog.FieldOperator, field.TypeString, value)
}
_node = &PaymentAuditLog{config: _u.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{paymentauditlog.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
_u.mutation.done = true
return _node, nil
}

589
backend/ent/paymentorder.go Normal file
View File

@ -0,0 +1,589 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/user"
)
// PaymentOrder is the model entity for the PaymentOrder schema.
type PaymentOrder struct {
config `json:"-"`
// ID of the ent.
ID int64 `json:"id,omitempty"`
// UserID holds the value of the "user_id" field.
UserID int64 `json:"user_id,omitempty"`
// UserEmail holds the value of the "user_email" field.
UserEmail string `json:"user_email,omitempty"`
// UserName holds the value of the "user_name" field.
UserName string `json:"user_name,omitempty"`
// UserNotes holds the value of the "user_notes" field.
UserNotes *string `json:"user_notes,omitempty"`
// Amount holds the value of the "amount" field.
Amount float64 `json:"amount,omitempty"`
// PayAmount holds the value of the "pay_amount" field.
PayAmount float64 `json:"pay_amount,omitempty"`
// FeeRate holds the value of the "fee_rate" field.
FeeRate float64 `json:"fee_rate,omitempty"`
// RechargeCode holds the value of the "recharge_code" field.
RechargeCode string `json:"recharge_code,omitempty"`
// OutTradeNo holds the value of the "out_trade_no" field.
OutTradeNo string `json:"out_trade_no,omitempty"`
// PaymentType holds the value of the "payment_type" field.
PaymentType string `json:"payment_type,omitempty"`
// PaymentTradeNo holds the value of the "payment_trade_no" field.
PaymentTradeNo string `json:"payment_trade_no,omitempty"`
// PayURL holds the value of the "pay_url" field.
PayURL *string `json:"pay_url,omitempty"`
// QrCode holds the value of the "qr_code" field.
QrCode *string `json:"qr_code,omitempty"`
// QrCodeImg holds the value of the "qr_code_img" field.
QrCodeImg *string `json:"qr_code_img,omitempty"`
// OrderType holds the value of the "order_type" field.
OrderType string `json:"order_type,omitempty"`
// PlanID holds the value of the "plan_id" field.
PlanID *int64 `json:"plan_id,omitempty"`
// SubscriptionGroupID holds the value of the "subscription_group_id" field.
SubscriptionGroupID *int64 `json:"subscription_group_id,omitempty"`
// SubscriptionDays holds the value of the "subscription_days" field.
SubscriptionDays *int `json:"subscription_days,omitempty"`
// ProviderInstanceID holds the value of the "provider_instance_id" field.
ProviderInstanceID *string `json:"provider_instance_id,omitempty"`
// Status holds the value of the "status" field.
Status string `json:"status,omitempty"`
// RefundAmount holds the value of the "refund_amount" field.
RefundAmount float64 `json:"refund_amount,omitempty"`
// RefundReason holds the value of the "refund_reason" field.
RefundReason *string `json:"refund_reason,omitempty"`
// RefundAt holds the value of the "refund_at" field.
RefundAt *time.Time `json:"refund_at,omitempty"`
// ForceRefund holds the value of the "force_refund" field.
ForceRefund bool `json:"force_refund,omitempty"`
// RefundRequestedAt holds the value of the "refund_requested_at" field.
RefundRequestedAt *time.Time `json:"refund_requested_at,omitempty"`
// RefundRequestReason holds the value of the "refund_request_reason" field.
RefundRequestReason *string `json:"refund_request_reason,omitempty"`
// RefundRequestedBy holds the value of the "refund_requested_by" field.
RefundRequestedBy *string `json:"refund_requested_by,omitempty"`
// ExpiresAt holds the value of the "expires_at" field.
ExpiresAt time.Time `json:"expires_at,omitempty"`
// PaidAt holds the value of the "paid_at" field.
PaidAt *time.Time `json:"paid_at,omitempty"`
// CompletedAt holds the value of the "completed_at" field.
CompletedAt *time.Time `json:"completed_at,omitempty"`
// FailedAt holds the value of the "failed_at" field.
FailedAt *time.Time `json:"failed_at,omitempty"`
// FailedReason holds the value of the "failed_reason" field.
FailedReason *string `json:"failed_reason,omitempty"`
// ClientIP holds the value of the "client_ip" field.
ClientIP string `json:"client_ip,omitempty"`
// SrcHost holds the value of the "src_host" field.
SrcHost string `json:"src_host,omitempty"`
// SrcURL holds the value of the "src_url" field.
SrcURL *string `json:"src_url,omitempty"`
// CreatedAt holds the value of the "created_at" field.
CreatedAt time.Time `json:"created_at,omitempty"`
// UpdatedAt holds the value of the "updated_at" field.
UpdatedAt time.Time `json:"updated_at,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the PaymentOrderQuery when eager-loading is set.
Edges PaymentOrderEdges `json:"edges"`
selectValues sql.SelectValues
}
// PaymentOrderEdges holds the relations/edges for other nodes in the graph.
type PaymentOrderEdges struct {
// User holds the value of the user edge.
User *User `json:"user,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [1]bool
}
// UserOrErr returns the User value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e PaymentOrderEdges) UserOrErr() (*User, error) {
if e.User != nil {
return e.User, nil
} else if e.loadedTypes[0] {
return nil, &NotFoundError{label: user.Label}
}
return nil, &NotLoadedError{edge: "user"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*PaymentOrder) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case paymentorder.FieldForceRefund:
values[i] = new(sql.NullBool)
case paymentorder.FieldAmount, paymentorder.FieldPayAmount, paymentorder.FieldFeeRate, paymentorder.FieldRefundAmount:
values[i] = new(sql.NullFloat64)
case paymentorder.FieldID, paymentorder.FieldUserID, paymentorder.FieldPlanID, paymentorder.FieldSubscriptionGroupID, paymentorder.FieldSubscriptionDays:
values[i] = new(sql.NullInt64)
case paymentorder.FieldUserEmail, paymentorder.FieldUserName, paymentorder.FieldUserNotes, paymentorder.FieldRechargeCode, paymentorder.FieldOutTradeNo, paymentorder.FieldPaymentType, paymentorder.FieldPaymentTradeNo, paymentorder.FieldPayURL, paymentorder.FieldQrCode, paymentorder.FieldQrCodeImg, paymentorder.FieldOrderType, paymentorder.FieldProviderInstanceID, paymentorder.FieldStatus, paymentorder.FieldRefundReason, paymentorder.FieldRefundRequestReason, paymentorder.FieldRefundRequestedBy, paymentorder.FieldFailedReason, paymentorder.FieldClientIP, paymentorder.FieldSrcHost, paymentorder.FieldSrcURL:
values[i] = new(sql.NullString)
case paymentorder.FieldRefundAt, paymentorder.FieldRefundRequestedAt, paymentorder.FieldExpiresAt, paymentorder.FieldPaidAt, paymentorder.FieldCompletedAt, paymentorder.FieldFailedAt, paymentorder.FieldCreatedAt, paymentorder.FieldUpdatedAt:
values[i] = new(sql.NullTime)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the PaymentOrder fields.
func (_m *PaymentOrder) assignValues(columns []string, values []any) error {
if m, n := len(values), len(columns); m < n {
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
}
for i := range columns {
switch columns[i] {
case paymentorder.FieldID:
value, ok := values[i].(*sql.NullInt64)
if !ok {
return fmt.Errorf("unexpected type %T for field id", value)
}
_m.ID = int64(value.Int64)
case paymentorder.FieldUserID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field user_id", values[i])
} else if value.Valid {
_m.UserID = value.Int64
}
case paymentorder.FieldUserEmail:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field user_email", values[i])
} else if value.Valid {
_m.UserEmail = value.String
}
case paymentorder.FieldUserName:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field user_name", values[i])
} else if value.Valid {
_m.UserName = value.String
}
case paymentorder.FieldUserNotes:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field user_notes", values[i])
} else if value.Valid {
_m.UserNotes = new(string)
*_m.UserNotes = value.String
}
case paymentorder.FieldAmount:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field amount", values[i])
} else if value.Valid {
_m.Amount = value.Float64
}
case paymentorder.FieldPayAmount:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field pay_amount", values[i])
} else if value.Valid {
_m.PayAmount = value.Float64
}
case paymentorder.FieldFeeRate:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field fee_rate", values[i])
} else if value.Valid {
_m.FeeRate = value.Float64
}
case paymentorder.FieldRechargeCode:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field recharge_code", values[i])
} else if value.Valid {
_m.RechargeCode = value.String
}
case paymentorder.FieldOutTradeNo:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field out_trade_no", values[i])
} else if value.Valid {
_m.OutTradeNo = value.String
}
case paymentorder.FieldPaymentType:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field payment_type", values[i])
} else if value.Valid {
_m.PaymentType = value.String
}
case paymentorder.FieldPaymentTradeNo:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field payment_trade_no", values[i])
} else if value.Valid {
_m.PaymentTradeNo = value.String
}
case paymentorder.FieldPayURL:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field pay_url", values[i])
} else if value.Valid {
_m.PayURL = new(string)
*_m.PayURL = value.String
}
case paymentorder.FieldQrCode:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field qr_code", values[i])
} else if value.Valid {
_m.QrCode = new(string)
*_m.QrCode = value.String
}
case paymentorder.FieldQrCodeImg:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field qr_code_img", values[i])
} else if value.Valid {
_m.QrCodeImg = new(string)
*_m.QrCodeImg = value.String
}
case paymentorder.FieldOrderType:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field order_type", values[i])
} else if value.Valid {
_m.OrderType = value.String
}
case paymentorder.FieldPlanID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field plan_id", values[i])
} else if value.Valid {
_m.PlanID = new(int64)
*_m.PlanID = value.Int64
}
case paymentorder.FieldSubscriptionGroupID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field subscription_group_id", values[i])
} else if value.Valid {
_m.SubscriptionGroupID = new(int64)
*_m.SubscriptionGroupID = value.Int64
}
case paymentorder.FieldSubscriptionDays:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field subscription_days", values[i])
} else if value.Valid {
_m.SubscriptionDays = new(int)
*_m.SubscriptionDays = int(value.Int64)
}
case paymentorder.FieldProviderInstanceID:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field provider_instance_id", values[i])
} else if value.Valid {
_m.ProviderInstanceID = new(string)
*_m.ProviderInstanceID = value.String
}
case paymentorder.FieldStatus:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field status", values[i])
} else if value.Valid {
_m.Status = value.String
}
case paymentorder.FieldRefundAmount:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field refund_amount", values[i])
} else if value.Valid {
_m.RefundAmount = value.Float64
}
case paymentorder.FieldRefundReason:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field refund_reason", values[i])
} else if value.Valid {
_m.RefundReason = new(string)
*_m.RefundReason = value.String
}
case paymentorder.FieldRefundAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field refund_at", values[i])
} else if value.Valid {
_m.RefundAt = new(time.Time)
*_m.RefundAt = value.Time
}
case paymentorder.FieldForceRefund:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field force_refund", values[i])
} else if value.Valid {
_m.ForceRefund = value.Bool
}
case paymentorder.FieldRefundRequestedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field refund_requested_at", values[i])
} else if value.Valid {
_m.RefundRequestedAt = new(time.Time)
*_m.RefundRequestedAt = value.Time
}
case paymentorder.FieldRefundRequestReason:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field refund_request_reason", values[i])
} else if value.Valid {
_m.RefundRequestReason = new(string)
*_m.RefundRequestReason = value.String
}
case paymentorder.FieldRefundRequestedBy:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field refund_requested_by", values[i])
} else if value.Valid {
_m.RefundRequestedBy = new(string)
*_m.RefundRequestedBy = value.String
}
case paymentorder.FieldExpiresAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field expires_at", values[i])
} else if value.Valid {
_m.ExpiresAt = value.Time
}
case paymentorder.FieldPaidAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field paid_at", values[i])
} else if value.Valid {
_m.PaidAt = new(time.Time)
*_m.PaidAt = value.Time
}
case paymentorder.FieldCompletedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field completed_at", values[i])
} else if value.Valid {
_m.CompletedAt = new(time.Time)
*_m.CompletedAt = value.Time
}
case paymentorder.FieldFailedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field failed_at", values[i])
} else if value.Valid {
_m.FailedAt = new(time.Time)
*_m.FailedAt = value.Time
}
case paymentorder.FieldFailedReason:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field failed_reason", values[i])
} else if value.Valid {
_m.FailedReason = new(string)
*_m.FailedReason = value.String
}
case paymentorder.FieldClientIP:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field client_ip", values[i])
} else if value.Valid {
_m.ClientIP = value.String
}
case paymentorder.FieldSrcHost:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field src_host", values[i])
} else if value.Valid {
_m.SrcHost = value.String
}
case paymentorder.FieldSrcURL:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field src_url", values[i])
} else if value.Valid {
_m.SrcURL = new(string)
*_m.SrcURL = value.String
}
case paymentorder.FieldCreatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field created_at", values[i])
} else if value.Valid {
_m.CreatedAt = value.Time
}
case paymentorder.FieldUpdatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field updated_at", values[i])
} else if value.Valid {
_m.UpdatedAt = value.Time
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the PaymentOrder.
// This includes values selected through modifiers, order, etc.
func (_m *PaymentOrder) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryUser queries the "user" edge of the PaymentOrder entity.
func (_m *PaymentOrder) QueryUser() *UserQuery {
return NewPaymentOrderClient(_m.config).QueryUser(_m)
}
// Update returns a builder for updating this PaymentOrder.
// Note that you need to call PaymentOrder.Unwrap() before calling this method if this PaymentOrder
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *PaymentOrder) Update() *PaymentOrderUpdateOne {
return NewPaymentOrderClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the PaymentOrder entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (_m *PaymentOrder) Unwrap() *PaymentOrder {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: PaymentOrder is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *PaymentOrder) String() string {
var builder strings.Builder
builder.WriteString("PaymentOrder(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("user_id=")
builder.WriteString(fmt.Sprintf("%v", _m.UserID))
builder.WriteString(", ")
builder.WriteString("user_email=")
builder.WriteString(_m.UserEmail)
builder.WriteString(", ")
builder.WriteString("user_name=")
builder.WriteString(_m.UserName)
builder.WriteString(", ")
if v := _m.UserNotes; v != nil {
builder.WriteString("user_notes=")
builder.WriteString(*v)
}
builder.WriteString(", ")
builder.WriteString("amount=")
builder.WriteString(fmt.Sprintf("%v", _m.Amount))
builder.WriteString(", ")
builder.WriteString("pay_amount=")
builder.WriteString(fmt.Sprintf("%v", _m.PayAmount))
builder.WriteString(", ")
builder.WriteString("fee_rate=")
builder.WriteString(fmt.Sprintf("%v", _m.FeeRate))
builder.WriteString(", ")
builder.WriteString("recharge_code=")
builder.WriteString(_m.RechargeCode)
builder.WriteString(", ")
builder.WriteString("out_trade_no=")
builder.WriteString(_m.OutTradeNo)
builder.WriteString(", ")
builder.WriteString("payment_type=")
builder.WriteString(_m.PaymentType)
builder.WriteString(", ")
builder.WriteString("payment_trade_no=")
builder.WriteString(_m.PaymentTradeNo)
builder.WriteString(", ")
if v := _m.PayURL; v != nil {
builder.WriteString("pay_url=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.QrCode; v != nil {
builder.WriteString("qr_code=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.QrCodeImg; v != nil {
builder.WriteString("qr_code_img=")
builder.WriteString(*v)
}
builder.WriteString(", ")
builder.WriteString("order_type=")
builder.WriteString(_m.OrderType)
builder.WriteString(", ")
if v := _m.PlanID; v != nil {
builder.WriteString("plan_id=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.SubscriptionGroupID; v != nil {
builder.WriteString("subscription_group_id=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.SubscriptionDays; v != nil {
builder.WriteString("subscription_days=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
if v := _m.ProviderInstanceID; v != nil {
builder.WriteString("provider_instance_id=")
builder.WriteString(*v)
}
builder.WriteString(", ")
builder.WriteString("status=")
builder.WriteString(_m.Status)
builder.WriteString(", ")
builder.WriteString("refund_amount=")
builder.WriteString(fmt.Sprintf("%v", _m.RefundAmount))
builder.WriteString(", ")
if v := _m.RefundReason; v != nil {
builder.WriteString("refund_reason=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.RefundAt; v != nil {
builder.WriteString("refund_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
builder.WriteString("force_refund=")
builder.WriteString(fmt.Sprintf("%v", _m.ForceRefund))
builder.WriteString(", ")
if v := _m.RefundRequestedAt; v != nil {
builder.WriteString("refund_requested_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.RefundRequestReason; v != nil {
builder.WriteString("refund_request_reason=")
builder.WriteString(*v)
}
builder.WriteString(", ")
if v := _m.RefundRequestedBy; v != nil {
builder.WriteString("refund_requested_by=")
builder.WriteString(*v)
}
builder.WriteString(", ")
builder.WriteString("expires_at=")
builder.WriteString(_m.ExpiresAt.Format(time.ANSIC))
builder.WriteString(", ")
if v := _m.PaidAt; v != nil {
builder.WriteString("paid_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.CompletedAt; v != nil {
builder.WriteString("completed_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.FailedAt; v != nil {
builder.WriteString("failed_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.FailedReason; v != nil {
builder.WriteString("failed_reason=")
builder.WriteString(*v)
}
builder.WriteString(", ")
builder.WriteString("client_ip=")
builder.WriteString(_m.ClientIP)
builder.WriteString(", ")
builder.WriteString("src_host=")
builder.WriteString(_m.SrcHost)
builder.WriteString(", ")
if v := _m.SrcURL; v != nil {
builder.WriteString("src_url=")
builder.WriteString(*v)
}
builder.WriteString(", ")
builder.WriteString("created_at=")
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
builder.WriteString(", ")
builder.WriteString("updated_at=")
builder.WriteString(_m.UpdatedAt.Format(time.ANSIC))
builder.WriteByte(')')
return builder.String()
}
// PaymentOrders is a parsable slice of PaymentOrder.
type PaymentOrders []*PaymentOrder

View File

@ -0,0 +1,406 @@
// Code generated by ent, DO NOT EDIT.
package paymentorder
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the paymentorder type in the database.
Label = "payment_order"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldUserID holds the string denoting the user_id field in the database.
FieldUserID = "user_id"
// FieldUserEmail holds the string denoting the user_email field in the database.
FieldUserEmail = "user_email"
// FieldUserName holds the string denoting the user_name field in the database.
FieldUserName = "user_name"
// FieldUserNotes holds the string denoting the user_notes field in the database.
FieldUserNotes = "user_notes"
// FieldAmount holds the string denoting the amount field in the database.
FieldAmount = "amount"
// FieldPayAmount holds the string denoting the pay_amount field in the database.
FieldPayAmount = "pay_amount"
// FieldFeeRate holds the string denoting the fee_rate field in the database.
FieldFeeRate = "fee_rate"
// FieldRechargeCode holds the string denoting the recharge_code field in the database.
FieldRechargeCode = "recharge_code"
// FieldOutTradeNo holds the string denoting the out_trade_no field in the database.
FieldOutTradeNo = "out_trade_no"
// FieldPaymentType holds the string denoting the payment_type field in the database.
FieldPaymentType = "payment_type"
// FieldPaymentTradeNo holds the string denoting the payment_trade_no field in the database.
FieldPaymentTradeNo = "payment_trade_no"
// FieldPayURL holds the string denoting the pay_url field in the database.
FieldPayURL = "pay_url"
// FieldQrCode holds the string denoting the qr_code field in the database.
FieldQrCode = "qr_code"
// FieldQrCodeImg holds the string denoting the qr_code_img field in the database.
FieldQrCodeImg = "qr_code_img"
// FieldOrderType holds the string denoting the order_type field in the database.
FieldOrderType = "order_type"
// FieldPlanID holds the string denoting the plan_id field in the database.
FieldPlanID = "plan_id"
// FieldSubscriptionGroupID holds the string denoting the subscription_group_id field in the database.
FieldSubscriptionGroupID = "subscription_group_id"
// FieldSubscriptionDays holds the string denoting the subscription_days field in the database.
FieldSubscriptionDays = "subscription_days"
// FieldProviderInstanceID holds the string denoting the provider_instance_id field in the database.
FieldProviderInstanceID = "provider_instance_id"
// FieldStatus holds the string denoting the status field in the database.
FieldStatus = "status"
// FieldRefundAmount holds the string denoting the refund_amount field in the database.
FieldRefundAmount = "refund_amount"
// FieldRefundReason holds the string denoting the refund_reason field in the database.
FieldRefundReason = "refund_reason"
// FieldRefundAt holds the string denoting the refund_at field in the database.
FieldRefundAt = "refund_at"
// FieldForceRefund holds the string denoting the force_refund field in the database.
FieldForceRefund = "force_refund"
// FieldRefundRequestedAt holds the string denoting the refund_requested_at field in the database.
FieldRefundRequestedAt = "refund_requested_at"
// FieldRefundRequestReason holds the string denoting the refund_request_reason field in the database.
FieldRefundRequestReason = "refund_request_reason"
// FieldRefundRequestedBy holds the string denoting the refund_requested_by field in the database.
FieldRefundRequestedBy = "refund_requested_by"
// FieldExpiresAt holds the string denoting the expires_at field in the database.
FieldExpiresAt = "expires_at"
// FieldPaidAt holds the string denoting the paid_at field in the database.
FieldPaidAt = "paid_at"
// FieldCompletedAt holds the string denoting the completed_at field in the database.
FieldCompletedAt = "completed_at"
// FieldFailedAt holds the string denoting the failed_at field in the database.
FieldFailedAt = "failed_at"
// FieldFailedReason holds the string denoting the failed_reason field in the database.
FieldFailedReason = "failed_reason"
// FieldClientIP holds the string denoting the client_ip field in the database.
FieldClientIP = "client_ip"
// FieldSrcHost holds the string denoting the src_host field in the database.
FieldSrcHost = "src_host"
// FieldSrcURL holds the string denoting the src_url field in the database.
FieldSrcURL = "src_url"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt = "created_at"
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
FieldUpdatedAt = "updated_at"
// EdgeUser holds the string denoting the user edge name in mutations.
EdgeUser = "user"
// Table holds the table name of the paymentorder in the database.
Table = "payment_orders"
// UserTable is the table that holds the user relation/edge.
UserTable = "payment_orders"
// UserInverseTable is the table name for the User entity.
// It exists in this package in order to avoid circular dependency with the "user" package.
UserInverseTable = "users"
// UserColumn is the table column denoting the user relation/edge.
UserColumn = "user_id"
)
// Columns holds all SQL columns for paymentorder fields.
var Columns = []string{
FieldID,
FieldUserID,
FieldUserEmail,
FieldUserName,
FieldUserNotes,
FieldAmount,
FieldPayAmount,
FieldFeeRate,
FieldRechargeCode,
FieldOutTradeNo,
FieldPaymentType,
FieldPaymentTradeNo,
FieldPayURL,
FieldQrCode,
FieldQrCodeImg,
FieldOrderType,
FieldPlanID,
FieldSubscriptionGroupID,
FieldSubscriptionDays,
FieldProviderInstanceID,
FieldStatus,
FieldRefundAmount,
FieldRefundReason,
FieldRefundAt,
FieldForceRefund,
FieldRefundRequestedAt,
FieldRefundRequestReason,
FieldRefundRequestedBy,
FieldExpiresAt,
FieldPaidAt,
FieldCompletedAt,
FieldFailedAt,
FieldFailedReason,
FieldClientIP,
FieldSrcHost,
FieldSrcURL,
FieldCreatedAt,
FieldUpdatedAt,
}
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
return false
}
var (
// UserEmailValidator is a validator for the "user_email" field. It is called by the builders before save.
UserEmailValidator func(string) error
// UserNameValidator is a validator for the "user_name" field. It is called by the builders before save.
UserNameValidator func(string) error
// DefaultFeeRate holds the default value on creation for the "fee_rate" field.
DefaultFeeRate float64
// RechargeCodeValidator is a validator for the "recharge_code" field. It is called by the builders before save.
RechargeCodeValidator func(string) error
// DefaultOutTradeNo holds the default value on creation for the "out_trade_no" field.
DefaultOutTradeNo string
// OutTradeNoValidator is a validator for the "out_trade_no" field. It is called by the builders before save.
OutTradeNoValidator func(string) error
// PaymentTypeValidator is a validator for the "payment_type" field. It is called by the builders before save.
PaymentTypeValidator func(string) error
// PaymentTradeNoValidator is a validator for the "payment_trade_no" field. It is called by the builders before save.
PaymentTradeNoValidator func(string) error
// DefaultOrderType holds the default value on creation for the "order_type" field.
DefaultOrderType string
// OrderTypeValidator is a validator for the "order_type" field. It is called by the builders before save.
OrderTypeValidator func(string) error
// ProviderInstanceIDValidator is a validator for the "provider_instance_id" field. It is called by the builders before save.
ProviderInstanceIDValidator func(string) error
// DefaultStatus holds the default value on creation for the "status" field.
DefaultStatus string
// StatusValidator is a validator for the "status" field. It is called by the builders before save.
StatusValidator func(string) error
// DefaultRefundAmount holds the default value on creation for the "refund_amount" field.
DefaultRefundAmount float64
// DefaultForceRefund holds the default value on creation for the "force_refund" field.
DefaultForceRefund bool
// RefundRequestedByValidator is a validator for the "refund_requested_by" field. It is called by the builders before save.
RefundRequestedByValidator func(string) error
// ClientIPValidator is a validator for the "client_ip" field. It is called by the builders before save.
ClientIPValidator func(string) error
// SrcHostValidator is a validator for the "src_host" field. It is called by the builders before save.
SrcHostValidator func(string) error
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
DefaultCreatedAt func() time.Time
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
DefaultUpdatedAt func() time.Time
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
UpdateDefaultUpdatedAt func() time.Time
)
// OrderOption defines the ordering options for the PaymentOrder queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByUserID orders the results by the user_id field.
func ByUserID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUserID, opts...).ToFunc()
}
// ByUserEmail orders the results by the user_email field.
func ByUserEmail(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUserEmail, opts...).ToFunc()
}
// ByUserName orders the results by the user_name field.
func ByUserName(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUserName, opts...).ToFunc()
}
// ByUserNotes orders the results by the user_notes field.
func ByUserNotes(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUserNotes, opts...).ToFunc()
}
// ByAmount orders the results by the amount field.
func ByAmount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAmount, opts...).ToFunc()
}
// ByPayAmount orders the results by the pay_amount field.
func ByPayAmount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPayAmount, opts...).ToFunc()
}
// ByFeeRate orders the results by the fee_rate field.
func ByFeeRate(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldFeeRate, opts...).ToFunc()
}
// ByRechargeCode orders the results by the recharge_code field.
func ByRechargeCode(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRechargeCode, opts...).ToFunc()
}
// ByOutTradeNo orders the results by the out_trade_no field.
func ByOutTradeNo(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldOutTradeNo, opts...).ToFunc()
}
// ByPaymentType orders the results by the payment_type field.
func ByPaymentType(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPaymentType, opts...).ToFunc()
}
// ByPaymentTradeNo orders the results by the payment_trade_no field.
func ByPaymentTradeNo(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPaymentTradeNo, opts...).ToFunc()
}
// ByPayURL orders the results by the pay_url field.
func ByPayURL(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPayURL, opts...).ToFunc()
}
// ByQrCode orders the results by the qr_code field.
func ByQrCode(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldQrCode, opts...).ToFunc()
}
// ByQrCodeImg orders the results by the qr_code_img field.
func ByQrCodeImg(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldQrCodeImg, opts...).ToFunc()
}
// ByOrderType orders the results by the order_type field.
func ByOrderType(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldOrderType, opts...).ToFunc()
}
// ByPlanID orders the results by the plan_id field.
func ByPlanID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPlanID, opts...).ToFunc()
}
// BySubscriptionGroupID orders the results by the subscription_group_id field.
func BySubscriptionGroupID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSubscriptionGroupID, opts...).ToFunc()
}
// BySubscriptionDays orders the results by the subscription_days field.
func BySubscriptionDays(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSubscriptionDays, opts...).ToFunc()
}
// ByProviderInstanceID orders the results by the provider_instance_id field.
func ByProviderInstanceID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProviderInstanceID, opts...).ToFunc()
}
// ByStatus orders the results by the status field.
func ByStatus(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldStatus, opts...).ToFunc()
}
// ByRefundAmount orders the results by the refund_amount field.
func ByRefundAmount(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRefundAmount, opts...).ToFunc()
}
// ByRefundReason orders the results by the refund_reason field.
func ByRefundReason(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRefundReason, opts...).ToFunc()
}
// ByRefundAt orders the results by the refund_at field.
func ByRefundAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRefundAt, opts...).ToFunc()
}
// ByForceRefund orders the results by the force_refund field.
func ByForceRefund(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldForceRefund, opts...).ToFunc()
}
// ByRefundRequestedAt orders the results by the refund_requested_at field.
func ByRefundRequestedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRefundRequestedAt, opts...).ToFunc()
}
// ByRefundRequestReason orders the results by the refund_request_reason field.
func ByRefundRequestReason(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRefundRequestReason, opts...).ToFunc()
}
// ByRefundRequestedBy orders the results by the refund_requested_by field.
func ByRefundRequestedBy(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRefundRequestedBy, opts...).ToFunc()
}
// ByExpiresAt orders the results by the expires_at field.
func ByExpiresAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldExpiresAt, opts...).ToFunc()
}
// ByPaidAt orders the results by the paid_at field.
func ByPaidAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPaidAt, opts...).ToFunc()
}
// ByCompletedAt orders the results by the completed_at field.
func ByCompletedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCompletedAt, opts...).ToFunc()
}
// ByFailedAt orders the results by the failed_at field.
func ByFailedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldFailedAt, opts...).ToFunc()
}
// ByFailedReason orders the results by the failed_reason field.
func ByFailedReason(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldFailedReason, opts...).ToFunc()
}
// ByClientIP orders the results by the client_ip field.
func ByClientIP(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldClientIP, opts...).ToFunc()
}
// BySrcHost orders the results by the src_host field.
func BySrcHost(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSrcHost, opts...).ToFunc()
}
// BySrcURL orders the results by the src_url field.
func BySrcURL(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSrcURL, opts...).ToFunc()
}
// ByCreatedAt orders the results by the created_at field.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
}
// ByUpdatedAt orders the results by the updated_at field.
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
}
// ByUserField orders the results by user field.
func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newUserStep(), sql.OrderByField(field, opts...))
}
}
func newUserStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(UserInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, UserTable, UserColumn),
)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,88 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// PaymentOrderDelete is the builder for deleting a PaymentOrder entity.
type PaymentOrderDelete struct {
config
hooks []Hook
mutation *PaymentOrderMutation
}
// Where appends a list predicates to the PaymentOrderDelete builder.
func (_d *PaymentOrderDelete) Where(ps ...predicate.PaymentOrder) *PaymentOrderDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *PaymentOrderDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *PaymentOrderDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *PaymentOrderDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(paymentorder.Table, sqlgraph.NewFieldSpec(paymentorder.FieldID, field.TypeInt64))
if ps := _d.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
_d.mutation.done = true
return affected, err
}
// PaymentOrderDeleteOne is the builder for deleting a single PaymentOrder entity.
type PaymentOrderDeleteOne struct {
_d *PaymentOrderDelete
}
// Where appends a list predicates to the PaymentOrderDelete builder.
func (_d *PaymentOrderDeleteOne) Where(ps ...predicate.PaymentOrder) *PaymentOrderDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *PaymentOrderDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{paymentorder.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *PaymentOrderDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}

View File

@ -0,0 +1,643 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"fmt"
"math"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/user"
)
// PaymentOrderQuery is the builder for querying PaymentOrder entities.
type PaymentOrderQuery struct {
config
ctx *QueryContext
order []paymentorder.OrderOption
inters []Interceptor
predicates []predicate.PaymentOrder
withUser *UserQuery
modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Where adds a new predicate for the PaymentOrderQuery builder.
func (_q *PaymentOrderQuery) Where(ps ...predicate.PaymentOrder) *PaymentOrderQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *PaymentOrderQuery) Limit(limit int) *PaymentOrderQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *PaymentOrderQuery) Offset(offset int) *PaymentOrderQuery {
_q.ctx.Offset = &offset
return _q
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func (_q *PaymentOrderQuery) Unique(unique bool) *PaymentOrderQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *PaymentOrderQuery) Order(o ...paymentorder.OrderOption) *PaymentOrderQuery {
_q.order = append(_q.order, o...)
return _q
}
// QueryUser chains the current query on the "user" edge.
func (_q *PaymentOrderQuery) QueryUser() *UserQuery {
query := (&UserClient{config: _q.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
selector := _q.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(paymentorder.Table, paymentorder.FieldID, selector),
sqlgraph.To(user.Table, user.FieldID),
sqlgraph.Edge(sqlgraph.M2O, true, paymentorder.UserTable, paymentorder.UserColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first PaymentOrder entity from the query.
// Returns a *NotFoundError when no PaymentOrder was found.
func (_q *PaymentOrderQuery) First(ctx context.Context) (*PaymentOrder, error) {
nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{paymentorder.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *PaymentOrderQuery) FirstX(ctx context.Context) *PaymentOrder {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first PaymentOrder ID from the query.
// Returns a *NotFoundError when no PaymentOrder ID was found.
func (_q *PaymentOrderQuery) FirstID(ctx context.Context) (id int64, err error) {
var ids []int64
if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{paymentorder.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *PaymentOrderQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single PaymentOrder entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one PaymentOrder entity is found.
// Returns a *NotFoundError when no PaymentOrder entities are found.
func (_q *PaymentOrderQuery) Only(ctx context.Context) (*PaymentOrder, error) {
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{paymentorder.Label}
default:
return nil, &NotSingularError{paymentorder.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *PaymentOrderQuery) OnlyX(ctx context.Context) *PaymentOrder {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only PaymentOrder ID in the query.
// Returns a *NotSingularError when more than one PaymentOrder ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *PaymentOrderQuery) OnlyID(ctx context.Context) (id int64, err error) {
var ids []int64
if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{paymentorder.Label}
default:
err = &NotSingularError{paymentorder.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *PaymentOrderQuery) OnlyIDX(ctx context.Context) int64 {
id, err := _q.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
// All executes the query and returns a list of PaymentOrders.
func (_q *PaymentOrderQuery) All(ctx context.Context) ([]*PaymentOrder, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*PaymentOrder, *PaymentOrderQuery]()
return withInterceptors[[]*PaymentOrder](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *PaymentOrderQuery) AllX(ctx context.Context) []*PaymentOrder {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of PaymentOrder IDs.
func (_q *PaymentOrderQuery) IDs(ctx context.Context) (ids []int64, err error) {
if _q.ctx.Unique == nil && _q.path != nil {
_q.Unique(true)
}
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
if err = _q.Select(paymentorder.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *PaymentOrderQuery) IDsX(ctx context.Context) []int64 {
ids, err := _q.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
// Count returns the count of the given query.
func (_q *PaymentOrderQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
if err := _q.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, _q, querierCount[*PaymentOrderQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *PaymentOrderQuery) CountX(ctx context.Context) int {
count, err := _q.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func (_q *PaymentOrderQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
switch _, err := _q.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
}
// ExistX is like Exist, but panics if an error occurs.
func (_q *PaymentOrderQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the PaymentOrderQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (_q *PaymentOrderQuery) Clone() *PaymentOrderQuery {
if _q == nil {
return nil
}
return &PaymentOrderQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]paymentorder.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.PaymentOrder{}, _q.predicates...),
withUser: _q.withUser.Clone(),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// WithUser tells the query-builder to eager-load the nodes that are connected to
// the "user" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *PaymentOrderQuery) WithUser(opts ...func(*UserQuery)) *PaymentOrderQuery {
query := (&UserClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withUser = query
return _q
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
// Example:
//
// var v []struct {
// UserID int64 `json:"user_id,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.PaymentOrder.Query().
// GroupBy(paymentorder.FieldUserID).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *PaymentOrderQuery) GroupBy(field string, fields ...string) *PaymentOrderGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &PaymentOrderGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = paymentorder.Label
grbuild.scan = grbuild.Scan
return grbuild
}
// Select allows the selection one or more fields/columns for the given query,
// instead of selecting all fields in the entity.
//
// Example:
//
// var v []struct {
// UserID int64 `json:"user_id,omitempty"`
// }
//
// client.PaymentOrder.Query().
// Select(paymentorder.FieldUserID).
// Scan(ctx, &v)
func (_q *PaymentOrderQuery) Select(fields ...string) *PaymentOrderSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &PaymentOrderSelect{PaymentOrderQuery: _q}
sbuild.label = paymentorder.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a PaymentOrderSelect configured with the given aggregations.
func (_q *PaymentOrderQuery) Aggregate(fns ...AggregateFunc) *PaymentOrderSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *PaymentOrderQuery) prepareQuery(ctx context.Context) error {
for _, inter := range _q.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, _q); err != nil {
return err
}
}
}
for _, f := range _q.ctx.Fields {
if !paymentorder.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
if _q.path != nil {
prev, err := _q.path(ctx)
if err != nil {
return err
}
_q.sql = prev
}
return nil
}
func (_q *PaymentOrderQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*PaymentOrder, error) {
var (
nodes = []*PaymentOrder{}
_spec = _q.querySpec()
loadedTypes = [1]bool{
_q.withUser != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*PaymentOrder).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &PaymentOrder{config: _q.config}
nodes = append(nodes, node)
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
if len(_q.modifiers) > 0 {
_spec.Modifiers = _q.modifiers
}
for i := range hooks {
hooks[i](ctx, _spec)
}
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
if query := _q.withUser; query != nil {
if err := _q.loadUser(ctx, query, nodes, nil,
func(n *PaymentOrder, e *User) { n.Edges.User = e }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (_q *PaymentOrderQuery) loadUser(ctx context.Context, query *UserQuery, nodes []*PaymentOrder, init func(*PaymentOrder), assign func(*PaymentOrder, *User)) error {
ids := make([]int64, 0, len(nodes))
nodeids := make(map[int64][]*PaymentOrder)
for i := range nodes {
fk := nodes[i].UserID
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(user.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
nodes, ok := nodeids[n.ID]
if !ok {
return fmt.Errorf(`unexpected foreign-key "user_id" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (_q *PaymentOrderQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
if len(_q.modifiers) > 0 {
_spec.Modifiers = _q.modifiers
}
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
}
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
}
func (_q *PaymentOrderQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(paymentorder.Table, paymentorder.Columns, sqlgraph.NewFieldSpec(paymentorder.FieldID, field.TypeInt64))
_spec.From = _q.sql
if unique := _q.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if _q.path != nil {
_spec.Unique = true
}
if fields := _q.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, paymentorder.FieldID)
for i := range fields {
if fields[i] != paymentorder.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
if _q.withUser != nil {
_spec.Node.AddColumnOnce(paymentorder.FieldUserID)
}
}
if ps := _q.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := _q.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := _q.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := _q.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
func (_q *PaymentOrderQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(paymentorder.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = paymentorder.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if _q.sql != nil {
selector = _q.sql
selector.Select(selector.Columns(columns...)...)
}
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
for _, m := range _q.modifiers {
m(selector)
}
for _, p := range _q.predicates {
p(selector)
}
for _, p := range _q.order {
p(selector)
}
if offset := _q.ctx.Offset; offset != nil {
// limit is mandatory for offset clause. We start
// with default value, and override it below if needed.
selector.Offset(*offset).Limit(math.MaxInt32)
}
if limit := _q.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
// updated, deleted or "selected ... for update" by other sessions, until the transaction is
// either committed or rolled-back.
func (_q *PaymentOrderQuery) ForUpdate(opts ...sql.LockOption) *PaymentOrderQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForUpdate(opts...)
})
return _q
}
// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
// on any rows that are read. Other sessions can read the rows, but cannot modify them
// until your transaction commits.
func (_q *PaymentOrderQuery) ForShare(opts ...sql.LockOption) *PaymentOrderQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// PaymentOrderGroupBy is the group-by builder for PaymentOrder entities.
type PaymentOrderGroupBy struct {
selector
build *PaymentOrderQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *PaymentOrderGroupBy) Aggregate(fns ...AggregateFunc) *PaymentOrderGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *PaymentOrderGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
if err := _g.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*PaymentOrderQuery, *PaymentOrderGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *PaymentOrderGroupBy) sqlScan(ctx context.Context, root *PaymentOrderQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(_g.fns))
for _, fn := range _g.fns {
aggregation = append(aggregation, fn(selector))
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
for _, f := range *_g.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*_g.flds...)...)
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
// PaymentOrderSelect is the builder for selecting fields of PaymentOrder entities.
type PaymentOrderSelect struct {
*PaymentOrderQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *PaymentOrderSelect) Aggregate(fns ...AggregateFunc) *PaymentOrderSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *PaymentOrderSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
if err := _s.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*PaymentOrderQuery, *PaymentOrderSelect](ctx, _s.PaymentOrderQuery, _s, _s.inters, v)
}
func (_s *PaymentOrderSelect) sqlScan(ctx context.Context, root *PaymentOrderQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(_s.fns))
for _, fn := range _s.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*_s.selector.flds); {
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _s.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,218 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
)
// PaymentProviderInstance is the model entity for the PaymentProviderInstance schema.
type PaymentProviderInstance struct {
config `json:"-"`
// ID of the ent.
ID int64 `json:"id,omitempty"`
// ProviderKey holds the value of the "provider_key" field.
ProviderKey string `json:"provider_key,omitempty"`
// Name holds the value of the "name" field.
Name string `json:"name,omitempty"`
// Config holds the value of the "config" field.
Config string `json:"config,omitempty"`
// SupportedTypes holds the value of the "supported_types" field.
SupportedTypes string `json:"supported_types,omitempty"`
// Enabled holds the value of the "enabled" field.
Enabled bool `json:"enabled,omitempty"`
// PaymentMode holds the value of the "payment_mode" field.
PaymentMode string `json:"payment_mode,omitempty"`
// SortOrder holds the value of the "sort_order" field.
SortOrder int `json:"sort_order,omitempty"`
// Limits holds the value of the "limits" field.
Limits string `json:"limits,omitempty"`
// RefundEnabled holds the value of the "refund_enabled" field.
RefundEnabled bool `json:"refund_enabled,omitempty"`
// CreatedAt holds the value of the "created_at" field.
CreatedAt time.Time `json:"created_at,omitempty"`
// UpdatedAt holds the value of the "updated_at" field.
UpdatedAt time.Time `json:"updated_at,omitempty"`
selectValues sql.SelectValues
}
// scanValues returns the types for scanning values from sql.Rows.
func (*PaymentProviderInstance) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case paymentproviderinstance.FieldEnabled, paymentproviderinstance.FieldRefundEnabled:
values[i] = new(sql.NullBool)
case paymentproviderinstance.FieldID, paymentproviderinstance.FieldSortOrder:
values[i] = new(sql.NullInt64)
case paymentproviderinstance.FieldProviderKey, paymentproviderinstance.FieldName, paymentproviderinstance.FieldConfig, paymentproviderinstance.FieldSupportedTypes, paymentproviderinstance.FieldPaymentMode, paymentproviderinstance.FieldLimits:
values[i] = new(sql.NullString)
case paymentproviderinstance.FieldCreatedAt, paymentproviderinstance.FieldUpdatedAt:
values[i] = new(sql.NullTime)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the PaymentProviderInstance fields.
func (_m *PaymentProviderInstance) assignValues(columns []string, values []any) error {
if m, n := len(values), len(columns); m < n {
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
}
for i := range columns {
switch columns[i] {
case paymentproviderinstance.FieldID:
value, ok := values[i].(*sql.NullInt64)
if !ok {
return fmt.Errorf("unexpected type %T for field id", value)
}
_m.ID = int64(value.Int64)
case paymentproviderinstance.FieldProviderKey:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field provider_key", values[i])
} else if value.Valid {
_m.ProviderKey = value.String
}
case paymentproviderinstance.FieldName:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field name", values[i])
} else if value.Valid {
_m.Name = value.String
}
case paymentproviderinstance.FieldConfig:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field config", values[i])
} else if value.Valid {
_m.Config = value.String
}
case paymentproviderinstance.FieldSupportedTypes:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field supported_types", values[i])
} else if value.Valid {
_m.SupportedTypes = value.String
}
case paymentproviderinstance.FieldEnabled:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field enabled", values[i])
} else if value.Valid {
_m.Enabled = value.Bool
}
case paymentproviderinstance.FieldPaymentMode:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field payment_mode", values[i])
} else if value.Valid {
_m.PaymentMode = value.String
}
case paymentproviderinstance.FieldSortOrder:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field sort_order", values[i])
} else if value.Valid {
_m.SortOrder = int(value.Int64)
}
case paymentproviderinstance.FieldLimits:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field limits", values[i])
} else if value.Valid {
_m.Limits = value.String
}
case paymentproviderinstance.FieldRefundEnabled:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field refund_enabled", values[i])
} else if value.Valid {
_m.RefundEnabled = value.Bool
}
case paymentproviderinstance.FieldCreatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field created_at", values[i])
} else if value.Valid {
_m.CreatedAt = value.Time
}
case paymentproviderinstance.FieldUpdatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field updated_at", values[i])
} else if value.Valid {
_m.UpdatedAt = value.Time
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the PaymentProviderInstance.
// This includes values selected through modifiers, order, etc.
func (_m *PaymentProviderInstance) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// Update returns a builder for updating this PaymentProviderInstance.
// Note that you need to call PaymentProviderInstance.Unwrap() before calling this method if this PaymentProviderInstance
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *PaymentProviderInstance) Update() *PaymentProviderInstanceUpdateOne {
return NewPaymentProviderInstanceClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the PaymentProviderInstance entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (_m *PaymentProviderInstance) Unwrap() *PaymentProviderInstance {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: PaymentProviderInstance is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *PaymentProviderInstance) String() string {
var builder strings.Builder
builder.WriteString("PaymentProviderInstance(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("provider_key=")
builder.WriteString(_m.ProviderKey)
builder.WriteString(", ")
builder.WriteString("name=")
builder.WriteString(_m.Name)
builder.WriteString(", ")
builder.WriteString("config=")
builder.WriteString(_m.Config)
builder.WriteString(", ")
builder.WriteString("supported_types=")
builder.WriteString(_m.SupportedTypes)
builder.WriteString(", ")
builder.WriteString("enabled=")
builder.WriteString(fmt.Sprintf("%v", _m.Enabled))
builder.WriteString(", ")
builder.WriteString("payment_mode=")
builder.WriteString(_m.PaymentMode)
builder.WriteString(", ")
builder.WriteString("sort_order=")
builder.WriteString(fmt.Sprintf("%v", _m.SortOrder))
builder.WriteString(", ")
builder.WriteString("limits=")
builder.WriteString(_m.Limits)
builder.WriteString(", ")
builder.WriteString("refund_enabled=")
builder.WriteString(fmt.Sprintf("%v", _m.RefundEnabled))
builder.WriteString(", ")
builder.WriteString("created_at=")
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
builder.WriteString(", ")
builder.WriteString("updated_at=")
builder.WriteString(_m.UpdatedAt.Format(time.ANSIC))
builder.WriteByte(')')
return builder.String()
}
// PaymentProviderInstances is a parsable slice of PaymentProviderInstance.
type PaymentProviderInstances []*PaymentProviderInstance

View File

@ -0,0 +1,160 @@
// Code generated by ent, DO NOT EDIT.
package paymentproviderinstance
import (
"time"
"entgo.io/ent/dialect/sql"
)
const (
// Label holds the string label denoting the paymentproviderinstance type in the database.
Label = "payment_provider_instance"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldProviderKey holds the string denoting the provider_key field in the database.
FieldProviderKey = "provider_key"
// FieldName holds the string denoting the name field in the database.
FieldName = "name"
// FieldConfig holds the string denoting the config field in the database.
FieldConfig = "config"
// FieldSupportedTypes holds the string denoting the supported_types field in the database.
FieldSupportedTypes = "supported_types"
// FieldEnabled holds the string denoting the enabled field in the database.
FieldEnabled = "enabled"
// FieldPaymentMode holds the string denoting the payment_mode field in the database.
FieldPaymentMode = "payment_mode"
// FieldSortOrder holds the string denoting the sort_order field in the database.
FieldSortOrder = "sort_order"
// FieldLimits holds the string denoting the limits field in the database.
FieldLimits = "limits"
// FieldRefundEnabled holds the string denoting the refund_enabled field in the database.
FieldRefundEnabled = "refund_enabled"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt = "created_at"
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
FieldUpdatedAt = "updated_at"
// Table holds the table name of the paymentproviderinstance in the database.
Table = "payment_provider_instances"
)
// Columns holds all SQL columns for paymentproviderinstance fields.
var Columns = []string{
FieldID,
FieldProviderKey,
FieldName,
FieldConfig,
FieldSupportedTypes,
FieldEnabled,
FieldPaymentMode,
FieldSortOrder,
FieldLimits,
FieldRefundEnabled,
FieldCreatedAt,
FieldUpdatedAt,
}
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
return false
}
var (
// ProviderKeyValidator is a validator for the "provider_key" field. It is called by the builders before save.
ProviderKeyValidator func(string) error
// DefaultName holds the default value on creation for the "name" field.
DefaultName string
// NameValidator is a validator for the "name" field. It is called by the builders before save.
NameValidator func(string) error
// DefaultSupportedTypes holds the default value on creation for the "supported_types" field.
DefaultSupportedTypes string
// SupportedTypesValidator is a validator for the "supported_types" field. It is called by the builders before save.
SupportedTypesValidator func(string) error
// DefaultEnabled holds the default value on creation for the "enabled" field.
DefaultEnabled bool
// DefaultPaymentMode holds the default value on creation for the "payment_mode" field.
DefaultPaymentMode string
// PaymentModeValidator is a validator for the "payment_mode" field. It is called by the builders before save.
PaymentModeValidator func(string) error
// DefaultSortOrder holds the default value on creation for the "sort_order" field.
DefaultSortOrder int
// DefaultLimits holds the default value on creation for the "limits" field.
DefaultLimits string
// DefaultRefundEnabled holds the default value on creation for the "refund_enabled" field.
DefaultRefundEnabled bool
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
DefaultCreatedAt func() time.Time
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
DefaultUpdatedAt func() time.Time
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
UpdateDefaultUpdatedAt func() time.Time
)
// OrderOption defines the ordering options for the PaymentProviderInstance queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByProviderKey orders the results by the provider_key field.
func ByProviderKey(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProviderKey, opts...).ToFunc()
}
// ByName orders the results by the name field.
func ByName(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldName, opts...).ToFunc()
}
// ByConfig orders the results by the config field.
func ByConfig(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldConfig, opts...).ToFunc()
}
// BySupportedTypes orders the results by the supported_types field.
func BySupportedTypes(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSupportedTypes, opts...).ToFunc()
}
// ByEnabled orders the results by the enabled field.
func ByEnabled(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldEnabled, opts...).ToFunc()
}
// ByPaymentMode orders the results by the payment_mode field.
func ByPaymentMode(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPaymentMode, opts...).ToFunc()
}
// BySortOrder orders the results by the sort_order field.
func BySortOrder(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSortOrder, opts...).ToFunc()
}
// ByLimits orders the results by the limits field.
func ByLimits(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldLimits, opts...).ToFunc()
}
// ByRefundEnabled orders the results by the refund_enabled field.
func ByRefundEnabled(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRefundEnabled, opts...).ToFunc()
}
// ByCreatedAt orders the results by the created_at field.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
}
// ByUpdatedAt orders the results by the updated_at field.
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
}

View File

@ -0,0 +1,655 @@
// Code generated by ent, DO NOT EDIT.
package paymentproviderinstance
import (
"time"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id int64) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int64) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int64) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int64) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int64) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int64) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int64) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int64) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int64) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldID, id))
}
// ProviderKey applies equality check predicate on the "provider_key" field. It's identical to ProviderKeyEQ.
func ProviderKey(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldProviderKey, v))
}
// Name applies equality check predicate on the "name" field. It's identical to NameEQ.
func Name(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldName, v))
}
// Config applies equality check predicate on the "config" field. It's identical to ConfigEQ.
func Config(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldConfig, v))
}
// SupportedTypes applies equality check predicate on the "supported_types" field. It's identical to SupportedTypesEQ.
func SupportedTypes(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldSupportedTypes, v))
}
// Enabled applies equality check predicate on the "enabled" field. It's identical to EnabledEQ.
func Enabled(v bool) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldEnabled, v))
}
// PaymentMode applies equality check predicate on the "payment_mode" field. It's identical to PaymentModeEQ.
func PaymentMode(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldPaymentMode, v))
}
// SortOrder applies equality check predicate on the "sort_order" field. It's identical to SortOrderEQ.
func SortOrder(v int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldSortOrder, v))
}
// Limits applies equality check predicate on the "limits" field. It's identical to LimitsEQ.
func Limits(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldLimits, v))
}
// RefundEnabled applies equality check predicate on the "refund_enabled" field. It's identical to RefundEnabledEQ.
func RefundEnabled(v bool) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldRefundEnabled, v))
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldCreatedAt, v))
}
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
func UpdatedAt(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldUpdatedAt, v))
}
// ProviderKeyEQ applies the EQ predicate on the "provider_key" field.
func ProviderKeyEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldProviderKey, v))
}
// ProviderKeyNEQ applies the NEQ predicate on the "provider_key" field.
func ProviderKeyNEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldProviderKey, v))
}
// ProviderKeyIn applies the In predicate on the "provider_key" field.
func ProviderKeyIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldProviderKey, vs...))
}
// ProviderKeyNotIn applies the NotIn predicate on the "provider_key" field.
func ProviderKeyNotIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldProviderKey, vs...))
}
// ProviderKeyGT applies the GT predicate on the "provider_key" field.
func ProviderKeyGT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldProviderKey, v))
}
// ProviderKeyGTE applies the GTE predicate on the "provider_key" field.
func ProviderKeyGTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldProviderKey, v))
}
// ProviderKeyLT applies the LT predicate on the "provider_key" field.
func ProviderKeyLT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldProviderKey, v))
}
// ProviderKeyLTE applies the LTE predicate on the "provider_key" field.
func ProviderKeyLTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldProviderKey, v))
}
// ProviderKeyContains applies the Contains predicate on the "provider_key" field.
func ProviderKeyContains(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContains(FieldProviderKey, v))
}
// ProviderKeyHasPrefix applies the HasPrefix predicate on the "provider_key" field.
func ProviderKeyHasPrefix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldProviderKey, v))
}
// ProviderKeyHasSuffix applies the HasSuffix predicate on the "provider_key" field.
func ProviderKeyHasSuffix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldProviderKey, v))
}
// ProviderKeyEqualFold applies the EqualFold predicate on the "provider_key" field.
func ProviderKeyEqualFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldProviderKey, v))
}
// ProviderKeyContainsFold applies the ContainsFold predicate on the "provider_key" field.
func ProviderKeyContainsFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldProviderKey, v))
}
// NameEQ applies the EQ predicate on the "name" field.
func NameEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldName, v))
}
// NameNEQ applies the NEQ predicate on the "name" field.
func NameNEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldName, v))
}
// NameIn applies the In predicate on the "name" field.
func NameIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldName, vs...))
}
// NameNotIn applies the NotIn predicate on the "name" field.
func NameNotIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldName, vs...))
}
// NameGT applies the GT predicate on the "name" field.
func NameGT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldName, v))
}
// NameGTE applies the GTE predicate on the "name" field.
func NameGTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldName, v))
}
// NameLT applies the LT predicate on the "name" field.
func NameLT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldName, v))
}
// NameLTE applies the LTE predicate on the "name" field.
func NameLTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldName, v))
}
// NameContains applies the Contains predicate on the "name" field.
func NameContains(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContains(FieldName, v))
}
// NameHasPrefix applies the HasPrefix predicate on the "name" field.
func NameHasPrefix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldName, v))
}
// NameHasSuffix applies the HasSuffix predicate on the "name" field.
func NameHasSuffix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldName, v))
}
// NameEqualFold applies the EqualFold predicate on the "name" field.
func NameEqualFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldName, v))
}
// NameContainsFold applies the ContainsFold predicate on the "name" field.
func NameContainsFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldName, v))
}
// ConfigEQ applies the EQ predicate on the "config" field.
func ConfigEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldConfig, v))
}
// ConfigNEQ applies the NEQ predicate on the "config" field.
func ConfigNEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldConfig, v))
}
// ConfigIn applies the In predicate on the "config" field.
func ConfigIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldConfig, vs...))
}
// ConfigNotIn applies the NotIn predicate on the "config" field.
func ConfigNotIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldConfig, vs...))
}
// ConfigGT applies the GT predicate on the "config" field.
func ConfigGT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldConfig, v))
}
// ConfigGTE applies the GTE predicate on the "config" field.
func ConfigGTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldConfig, v))
}
// ConfigLT applies the LT predicate on the "config" field.
func ConfigLT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldConfig, v))
}
// ConfigLTE applies the LTE predicate on the "config" field.
func ConfigLTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldConfig, v))
}
// ConfigContains applies the Contains predicate on the "config" field.
func ConfigContains(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContains(FieldConfig, v))
}
// ConfigHasPrefix applies the HasPrefix predicate on the "config" field.
func ConfigHasPrefix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldConfig, v))
}
// ConfigHasSuffix applies the HasSuffix predicate on the "config" field.
func ConfigHasSuffix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldConfig, v))
}
// ConfigEqualFold applies the EqualFold predicate on the "config" field.
func ConfigEqualFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldConfig, v))
}
// ConfigContainsFold applies the ContainsFold predicate on the "config" field.
func ConfigContainsFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldConfig, v))
}
// SupportedTypesEQ applies the EQ predicate on the "supported_types" field.
func SupportedTypesEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldSupportedTypes, v))
}
// SupportedTypesNEQ applies the NEQ predicate on the "supported_types" field.
func SupportedTypesNEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldSupportedTypes, v))
}
// SupportedTypesIn applies the In predicate on the "supported_types" field.
func SupportedTypesIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldSupportedTypes, vs...))
}
// SupportedTypesNotIn applies the NotIn predicate on the "supported_types" field.
func SupportedTypesNotIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldSupportedTypes, vs...))
}
// SupportedTypesGT applies the GT predicate on the "supported_types" field.
func SupportedTypesGT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldSupportedTypes, v))
}
// SupportedTypesGTE applies the GTE predicate on the "supported_types" field.
func SupportedTypesGTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldSupportedTypes, v))
}
// SupportedTypesLT applies the LT predicate on the "supported_types" field.
func SupportedTypesLT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldSupportedTypes, v))
}
// SupportedTypesLTE applies the LTE predicate on the "supported_types" field.
func SupportedTypesLTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldSupportedTypes, v))
}
// SupportedTypesContains applies the Contains predicate on the "supported_types" field.
func SupportedTypesContains(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContains(FieldSupportedTypes, v))
}
// SupportedTypesHasPrefix applies the HasPrefix predicate on the "supported_types" field.
func SupportedTypesHasPrefix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldSupportedTypes, v))
}
// SupportedTypesHasSuffix applies the HasSuffix predicate on the "supported_types" field.
func SupportedTypesHasSuffix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldSupportedTypes, v))
}
// SupportedTypesEqualFold applies the EqualFold predicate on the "supported_types" field.
func SupportedTypesEqualFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldSupportedTypes, v))
}
// SupportedTypesContainsFold applies the ContainsFold predicate on the "supported_types" field.
func SupportedTypesContainsFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldSupportedTypes, v))
}
// EnabledEQ applies the EQ predicate on the "enabled" field.
func EnabledEQ(v bool) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldEnabled, v))
}
// EnabledNEQ applies the NEQ predicate on the "enabled" field.
func EnabledNEQ(v bool) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldEnabled, v))
}
// PaymentModeEQ applies the EQ predicate on the "payment_mode" field.
func PaymentModeEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldPaymentMode, v))
}
// PaymentModeNEQ applies the NEQ predicate on the "payment_mode" field.
func PaymentModeNEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldPaymentMode, v))
}
// PaymentModeIn applies the In predicate on the "payment_mode" field.
func PaymentModeIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldPaymentMode, vs...))
}
// PaymentModeNotIn applies the NotIn predicate on the "payment_mode" field.
func PaymentModeNotIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldPaymentMode, vs...))
}
// PaymentModeGT applies the GT predicate on the "payment_mode" field.
func PaymentModeGT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldPaymentMode, v))
}
// PaymentModeGTE applies the GTE predicate on the "payment_mode" field.
func PaymentModeGTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldPaymentMode, v))
}
// PaymentModeLT applies the LT predicate on the "payment_mode" field.
func PaymentModeLT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldPaymentMode, v))
}
// PaymentModeLTE applies the LTE predicate on the "payment_mode" field.
func PaymentModeLTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldPaymentMode, v))
}
// PaymentModeContains applies the Contains predicate on the "payment_mode" field.
func PaymentModeContains(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContains(FieldPaymentMode, v))
}
// PaymentModeHasPrefix applies the HasPrefix predicate on the "payment_mode" field.
func PaymentModeHasPrefix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldPaymentMode, v))
}
// PaymentModeHasSuffix applies the HasSuffix predicate on the "payment_mode" field.
func PaymentModeHasSuffix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldPaymentMode, v))
}
// PaymentModeEqualFold applies the EqualFold predicate on the "payment_mode" field.
func PaymentModeEqualFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldPaymentMode, v))
}
// PaymentModeContainsFold applies the ContainsFold predicate on the "payment_mode" field.
func PaymentModeContainsFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldPaymentMode, v))
}
// SortOrderEQ applies the EQ predicate on the "sort_order" field.
func SortOrderEQ(v int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldSortOrder, v))
}
// SortOrderNEQ applies the NEQ predicate on the "sort_order" field.
func SortOrderNEQ(v int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldSortOrder, v))
}
// SortOrderIn applies the In predicate on the "sort_order" field.
func SortOrderIn(vs ...int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldSortOrder, vs...))
}
// SortOrderNotIn applies the NotIn predicate on the "sort_order" field.
func SortOrderNotIn(vs ...int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldSortOrder, vs...))
}
// SortOrderGT applies the GT predicate on the "sort_order" field.
func SortOrderGT(v int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldSortOrder, v))
}
// SortOrderGTE applies the GTE predicate on the "sort_order" field.
func SortOrderGTE(v int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldSortOrder, v))
}
// SortOrderLT applies the LT predicate on the "sort_order" field.
func SortOrderLT(v int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldSortOrder, v))
}
// SortOrderLTE applies the LTE predicate on the "sort_order" field.
func SortOrderLTE(v int) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldSortOrder, v))
}
// LimitsEQ applies the EQ predicate on the "limits" field.
func LimitsEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldLimits, v))
}
// LimitsNEQ applies the NEQ predicate on the "limits" field.
func LimitsNEQ(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldLimits, v))
}
// LimitsIn applies the In predicate on the "limits" field.
func LimitsIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldLimits, vs...))
}
// LimitsNotIn applies the NotIn predicate on the "limits" field.
func LimitsNotIn(vs ...string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldLimits, vs...))
}
// LimitsGT applies the GT predicate on the "limits" field.
func LimitsGT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldLimits, v))
}
// LimitsGTE applies the GTE predicate on the "limits" field.
func LimitsGTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldLimits, v))
}
// LimitsLT applies the LT predicate on the "limits" field.
func LimitsLT(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldLimits, v))
}
// LimitsLTE applies the LTE predicate on the "limits" field.
func LimitsLTE(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldLimits, v))
}
// LimitsContains applies the Contains predicate on the "limits" field.
func LimitsContains(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContains(FieldLimits, v))
}
// LimitsHasPrefix applies the HasPrefix predicate on the "limits" field.
func LimitsHasPrefix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasPrefix(FieldLimits, v))
}
// LimitsHasSuffix applies the HasSuffix predicate on the "limits" field.
func LimitsHasSuffix(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldHasSuffix(FieldLimits, v))
}
// LimitsEqualFold applies the EqualFold predicate on the "limits" field.
func LimitsEqualFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEqualFold(FieldLimits, v))
}
// LimitsContainsFold applies the ContainsFold predicate on the "limits" field.
func LimitsContainsFold(v string) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldContainsFold(FieldLimits, v))
}
// RefundEnabledEQ applies the EQ predicate on the "refund_enabled" field.
func RefundEnabledEQ(v bool) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldRefundEnabled, v))
}
// RefundEnabledNEQ applies the NEQ predicate on the "refund_enabled" field.
func RefundEnabledNEQ(v bool) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldRefundEnabled, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldCreatedAt, v))
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldEQ(FieldUpdatedAt, v))
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNEQ(FieldUpdatedAt, v))
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldIn(FieldUpdatedAt, vs...))
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldNotIn(FieldUpdatedAt, vs...))
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGT(FieldUpdatedAt, v))
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldGTE(FieldUpdatedAt, v))
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLT(FieldUpdatedAt, v))
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.FieldLTE(FieldUpdatedAt, v))
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.PaymentProviderInstance) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.PaymentProviderInstance) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.PaymentProviderInstance) predicate.PaymentProviderInstance {
return predicate.PaymentProviderInstance(sql.NotPredicates(p))
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,88 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// PaymentProviderInstanceDelete is the builder for deleting a PaymentProviderInstance entity.
type PaymentProviderInstanceDelete struct {
config
hooks []Hook
mutation *PaymentProviderInstanceMutation
}
// Where appends a list predicates to the PaymentProviderInstanceDelete builder.
func (_d *PaymentProviderInstanceDelete) Where(ps ...predicate.PaymentProviderInstance) *PaymentProviderInstanceDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *PaymentProviderInstanceDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *PaymentProviderInstanceDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *PaymentProviderInstanceDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(paymentproviderinstance.Table, sqlgraph.NewFieldSpec(paymentproviderinstance.FieldID, field.TypeInt64))
if ps := _d.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
_d.mutation.done = true
return affected, err
}
// PaymentProviderInstanceDeleteOne is the builder for deleting a single PaymentProviderInstance entity.
type PaymentProviderInstanceDeleteOne struct {
_d *PaymentProviderInstanceDelete
}
// Where appends a list predicates to the PaymentProviderInstanceDelete builder.
func (_d *PaymentProviderInstanceDeleteOne) Where(ps ...predicate.PaymentProviderInstance) *PaymentProviderInstanceDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *PaymentProviderInstanceDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{paymentproviderinstance.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *PaymentProviderInstanceDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}

View File

@ -0,0 +1,564 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"fmt"
"math"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// PaymentProviderInstanceQuery is the builder for querying PaymentProviderInstance entities.
type PaymentProviderInstanceQuery struct {
config
ctx *QueryContext
order []paymentproviderinstance.OrderOption
inters []Interceptor
predicates []predicate.PaymentProviderInstance
modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Where adds a new predicate for the PaymentProviderInstanceQuery builder.
func (_q *PaymentProviderInstanceQuery) Where(ps ...predicate.PaymentProviderInstance) *PaymentProviderInstanceQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *PaymentProviderInstanceQuery) Limit(limit int) *PaymentProviderInstanceQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *PaymentProviderInstanceQuery) Offset(offset int) *PaymentProviderInstanceQuery {
_q.ctx.Offset = &offset
return _q
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func (_q *PaymentProviderInstanceQuery) Unique(unique bool) *PaymentProviderInstanceQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *PaymentProviderInstanceQuery) Order(o ...paymentproviderinstance.OrderOption) *PaymentProviderInstanceQuery {
_q.order = append(_q.order, o...)
return _q
}
// First returns the first PaymentProviderInstance entity from the query.
// Returns a *NotFoundError when no PaymentProviderInstance was found.
func (_q *PaymentProviderInstanceQuery) First(ctx context.Context) (*PaymentProviderInstance, error) {
nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{paymentproviderinstance.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *PaymentProviderInstanceQuery) FirstX(ctx context.Context) *PaymentProviderInstance {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first PaymentProviderInstance ID from the query.
// Returns a *NotFoundError when no PaymentProviderInstance ID was found.
func (_q *PaymentProviderInstanceQuery) FirstID(ctx context.Context) (id int64, err error) {
var ids []int64
if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{paymentproviderinstance.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *PaymentProviderInstanceQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single PaymentProviderInstance entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one PaymentProviderInstance entity is found.
// Returns a *NotFoundError when no PaymentProviderInstance entities are found.
func (_q *PaymentProviderInstanceQuery) Only(ctx context.Context) (*PaymentProviderInstance, error) {
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{paymentproviderinstance.Label}
default:
return nil, &NotSingularError{paymentproviderinstance.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *PaymentProviderInstanceQuery) OnlyX(ctx context.Context) *PaymentProviderInstance {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only PaymentProviderInstance ID in the query.
// Returns a *NotSingularError when more than one PaymentProviderInstance ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *PaymentProviderInstanceQuery) OnlyID(ctx context.Context) (id int64, err error) {
var ids []int64
if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{paymentproviderinstance.Label}
default:
err = &NotSingularError{paymentproviderinstance.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *PaymentProviderInstanceQuery) OnlyIDX(ctx context.Context) int64 {
id, err := _q.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
// All executes the query and returns a list of PaymentProviderInstances.
func (_q *PaymentProviderInstanceQuery) All(ctx context.Context) ([]*PaymentProviderInstance, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*PaymentProviderInstance, *PaymentProviderInstanceQuery]()
return withInterceptors[[]*PaymentProviderInstance](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *PaymentProviderInstanceQuery) AllX(ctx context.Context) []*PaymentProviderInstance {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of PaymentProviderInstance IDs.
func (_q *PaymentProviderInstanceQuery) IDs(ctx context.Context) (ids []int64, err error) {
if _q.ctx.Unique == nil && _q.path != nil {
_q.Unique(true)
}
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
if err = _q.Select(paymentproviderinstance.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *PaymentProviderInstanceQuery) IDsX(ctx context.Context) []int64 {
ids, err := _q.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
// Count returns the count of the given query.
func (_q *PaymentProviderInstanceQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
if err := _q.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, _q, querierCount[*PaymentProviderInstanceQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *PaymentProviderInstanceQuery) CountX(ctx context.Context) int {
count, err := _q.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func (_q *PaymentProviderInstanceQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
switch _, err := _q.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
}
// ExistX is like Exist, but panics if an error occurs.
func (_q *PaymentProviderInstanceQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the PaymentProviderInstanceQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (_q *PaymentProviderInstanceQuery) Clone() *PaymentProviderInstanceQuery {
if _q == nil {
return nil
}
return &PaymentProviderInstanceQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]paymentproviderinstance.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.PaymentProviderInstance{}, _q.predicates...),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
// Example:
//
// var v []struct {
// ProviderKey string `json:"provider_key,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.PaymentProviderInstance.Query().
// GroupBy(paymentproviderinstance.FieldProviderKey).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *PaymentProviderInstanceQuery) GroupBy(field string, fields ...string) *PaymentProviderInstanceGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &PaymentProviderInstanceGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = paymentproviderinstance.Label
grbuild.scan = grbuild.Scan
return grbuild
}
// Select allows the selection one or more fields/columns for the given query,
// instead of selecting all fields in the entity.
//
// Example:
//
// var v []struct {
// ProviderKey string `json:"provider_key,omitempty"`
// }
//
// client.PaymentProviderInstance.Query().
// Select(paymentproviderinstance.FieldProviderKey).
// Scan(ctx, &v)
func (_q *PaymentProviderInstanceQuery) Select(fields ...string) *PaymentProviderInstanceSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &PaymentProviderInstanceSelect{PaymentProviderInstanceQuery: _q}
sbuild.label = paymentproviderinstance.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a PaymentProviderInstanceSelect configured with the given aggregations.
func (_q *PaymentProviderInstanceQuery) Aggregate(fns ...AggregateFunc) *PaymentProviderInstanceSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *PaymentProviderInstanceQuery) prepareQuery(ctx context.Context) error {
for _, inter := range _q.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, _q); err != nil {
return err
}
}
}
for _, f := range _q.ctx.Fields {
if !paymentproviderinstance.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
if _q.path != nil {
prev, err := _q.path(ctx)
if err != nil {
return err
}
_q.sql = prev
}
return nil
}
func (_q *PaymentProviderInstanceQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*PaymentProviderInstance, error) {
var (
nodes = []*PaymentProviderInstance{}
_spec = _q.querySpec()
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*PaymentProviderInstance).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &PaymentProviderInstance{config: _q.config}
nodes = append(nodes, node)
return node.assignValues(columns, values)
}
if len(_q.modifiers) > 0 {
_spec.Modifiers = _q.modifiers
}
for i := range hooks {
hooks[i](ctx, _spec)
}
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
return nodes, nil
}
func (_q *PaymentProviderInstanceQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
if len(_q.modifiers) > 0 {
_spec.Modifiers = _q.modifiers
}
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
}
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
}
func (_q *PaymentProviderInstanceQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(paymentproviderinstance.Table, paymentproviderinstance.Columns, sqlgraph.NewFieldSpec(paymentproviderinstance.FieldID, field.TypeInt64))
_spec.From = _q.sql
if unique := _q.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if _q.path != nil {
_spec.Unique = true
}
if fields := _q.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, paymentproviderinstance.FieldID)
for i := range fields {
if fields[i] != paymentproviderinstance.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
}
if ps := _q.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := _q.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := _q.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := _q.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
func (_q *PaymentProviderInstanceQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(paymentproviderinstance.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = paymentproviderinstance.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if _q.sql != nil {
selector = _q.sql
selector.Select(selector.Columns(columns...)...)
}
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
for _, m := range _q.modifiers {
m(selector)
}
for _, p := range _q.predicates {
p(selector)
}
for _, p := range _q.order {
p(selector)
}
if offset := _q.ctx.Offset; offset != nil {
// limit is mandatory for offset clause. We start
// with default value, and override it below if needed.
selector.Offset(*offset).Limit(math.MaxInt32)
}
if limit := _q.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
// updated, deleted or "selected ... for update" by other sessions, until the transaction is
// either committed or rolled-back.
func (_q *PaymentProviderInstanceQuery) ForUpdate(opts ...sql.LockOption) *PaymentProviderInstanceQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForUpdate(opts...)
})
return _q
}
// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
// on any rows that are read. Other sessions can read the rows, but cannot modify them
// until your transaction commits.
func (_q *PaymentProviderInstanceQuery) ForShare(opts ...sql.LockOption) *PaymentProviderInstanceQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// PaymentProviderInstanceGroupBy is the group-by builder for PaymentProviderInstance entities.
type PaymentProviderInstanceGroupBy struct {
selector
build *PaymentProviderInstanceQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *PaymentProviderInstanceGroupBy) Aggregate(fns ...AggregateFunc) *PaymentProviderInstanceGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *PaymentProviderInstanceGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
if err := _g.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*PaymentProviderInstanceQuery, *PaymentProviderInstanceGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *PaymentProviderInstanceGroupBy) sqlScan(ctx context.Context, root *PaymentProviderInstanceQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(_g.fns))
for _, fn := range _g.fns {
aggregation = append(aggregation, fn(selector))
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
for _, f := range *_g.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*_g.flds...)...)
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
// PaymentProviderInstanceSelect is the builder for selecting fields of PaymentProviderInstance entities.
type PaymentProviderInstanceSelect struct {
*PaymentProviderInstanceQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *PaymentProviderInstanceSelect) Aggregate(fns ...AggregateFunc) *PaymentProviderInstanceSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *PaymentProviderInstanceSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
if err := _s.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*PaymentProviderInstanceQuery, *PaymentProviderInstanceSelect](ctx, _s.PaymentProviderInstanceQuery, _s, _s.inters, v)
}
func (_s *PaymentProviderInstanceSelect) sqlScan(ctx context.Context, root *PaymentProviderInstanceQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(_s.fns))
for _, fn := range _s.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*_s.selector.flds); {
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _s.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}

View File

@ -0,0 +1,594 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// PaymentProviderInstanceUpdate is the builder for updating PaymentProviderInstance entities.
type PaymentProviderInstanceUpdate struct {
config
hooks []Hook
mutation *PaymentProviderInstanceMutation
}
// Where appends a list predicates to the PaymentProviderInstanceUpdate builder.
func (_u *PaymentProviderInstanceUpdate) Where(ps ...predicate.PaymentProviderInstance) *PaymentProviderInstanceUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetProviderKey sets the "provider_key" field.
func (_u *PaymentProviderInstanceUpdate) SetProviderKey(v string) *PaymentProviderInstanceUpdate {
_u.mutation.SetProviderKey(v)
return _u
}
// SetNillableProviderKey sets the "provider_key" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillableProviderKey(v *string) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetProviderKey(*v)
}
return _u
}
// SetName sets the "name" field.
func (_u *PaymentProviderInstanceUpdate) SetName(v string) *PaymentProviderInstanceUpdate {
_u.mutation.SetName(v)
return _u
}
// SetNillableName sets the "name" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillableName(v *string) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetName(*v)
}
return _u
}
// SetConfig sets the "config" field.
func (_u *PaymentProviderInstanceUpdate) SetConfig(v string) *PaymentProviderInstanceUpdate {
_u.mutation.SetConfig(v)
return _u
}
// SetNillableConfig sets the "config" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillableConfig(v *string) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetConfig(*v)
}
return _u
}
// SetSupportedTypes sets the "supported_types" field.
func (_u *PaymentProviderInstanceUpdate) SetSupportedTypes(v string) *PaymentProviderInstanceUpdate {
_u.mutation.SetSupportedTypes(v)
return _u
}
// SetNillableSupportedTypes sets the "supported_types" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillableSupportedTypes(v *string) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetSupportedTypes(*v)
}
return _u
}
// SetEnabled sets the "enabled" field.
func (_u *PaymentProviderInstanceUpdate) SetEnabled(v bool) *PaymentProviderInstanceUpdate {
_u.mutation.SetEnabled(v)
return _u
}
// SetNillableEnabled sets the "enabled" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillableEnabled(v *bool) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetEnabled(*v)
}
return _u
}
// SetPaymentMode sets the "payment_mode" field.
func (_u *PaymentProviderInstanceUpdate) SetPaymentMode(v string) *PaymentProviderInstanceUpdate {
_u.mutation.SetPaymentMode(v)
return _u
}
// SetNillablePaymentMode sets the "payment_mode" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillablePaymentMode(v *string) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetPaymentMode(*v)
}
return _u
}
// SetSortOrder sets the "sort_order" field.
func (_u *PaymentProviderInstanceUpdate) SetSortOrder(v int) *PaymentProviderInstanceUpdate {
_u.mutation.ResetSortOrder()
_u.mutation.SetSortOrder(v)
return _u
}
// SetNillableSortOrder sets the "sort_order" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillableSortOrder(v *int) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetSortOrder(*v)
}
return _u
}
// AddSortOrder adds value to the "sort_order" field.
func (_u *PaymentProviderInstanceUpdate) AddSortOrder(v int) *PaymentProviderInstanceUpdate {
_u.mutation.AddSortOrder(v)
return _u
}
// SetLimits sets the "limits" field.
func (_u *PaymentProviderInstanceUpdate) SetLimits(v string) *PaymentProviderInstanceUpdate {
_u.mutation.SetLimits(v)
return _u
}
// SetNillableLimits sets the "limits" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillableLimits(v *string) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetLimits(*v)
}
return _u
}
// SetRefundEnabled sets the "refund_enabled" field.
func (_u *PaymentProviderInstanceUpdate) SetRefundEnabled(v bool) *PaymentProviderInstanceUpdate {
_u.mutation.SetRefundEnabled(v)
return _u
}
// SetNillableRefundEnabled sets the "refund_enabled" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdate) SetNillableRefundEnabled(v *bool) *PaymentProviderInstanceUpdate {
if v != nil {
_u.SetRefundEnabled(*v)
}
return _u
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *PaymentProviderInstanceUpdate) SetUpdatedAt(v time.Time) *PaymentProviderInstanceUpdate {
_u.mutation.SetUpdatedAt(v)
return _u
}
// Mutation returns the PaymentProviderInstanceMutation object of the builder.
func (_u *PaymentProviderInstanceUpdate) Mutation() *PaymentProviderInstanceMutation {
return _u.mutation
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *PaymentProviderInstanceUpdate) Save(ctx context.Context) (int, error) {
_u.defaults()
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *PaymentProviderInstanceUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *PaymentProviderInstanceUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *PaymentProviderInstanceUpdate) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (_u *PaymentProviderInstanceUpdate) defaults() {
if _, ok := _u.mutation.UpdatedAt(); !ok {
v := paymentproviderinstance.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *PaymentProviderInstanceUpdate) check() error {
if v, ok := _u.mutation.ProviderKey(); ok {
if err := paymentproviderinstance.ProviderKeyValidator(v); err != nil {
return &ValidationError{Name: "provider_key", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.provider_key": %w`, err)}
}
}
if v, ok := _u.mutation.Name(); ok {
if err := paymentproviderinstance.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.name": %w`, err)}
}
}
if v, ok := _u.mutation.SupportedTypes(); ok {
if err := paymentproviderinstance.SupportedTypesValidator(v); err != nil {
return &ValidationError{Name: "supported_types", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.supported_types": %w`, err)}
}
}
if v, ok := _u.mutation.PaymentMode(); ok {
if err := paymentproviderinstance.PaymentModeValidator(v); err != nil {
return &ValidationError{Name: "payment_mode", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.payment_mode": %w`, err)}
}
}
return nil
}
func (_u *PaymentProviderInstanceUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(paymentproviderinstance.Table, paymentproviderinstance.Columns, sqlgraph.NewFieldSpec(paymentproviderinstance.FieldID, field.TypeInt64))
if ps := _u.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := _u.mutation.ProviderKey(); ok {
_spec.SetField(paymentproviderinstance.FieldProviderKey, field.TypeString, value)
}
if value, ok := _u.mutation.Name(); ok {
_spec.SetField(paymentproviderinstance.FieldName, field.TypeString, value)
}
if value, ok := _u.mutation.Config(); ok {
_spec.SetField(paymentproviderinstance.FieldConfig, field.TypeString, value)
}
if value, ok := _u.mutation.SupportedTypes(); ok {
_spec.SetField(paymentproviderinstance.FieldSupportedTypes, field.TypeString, value)
}
if value, ok := _u.mutation.Enabled(); ok {
_spec.SetField(paymentproviderinstance.FieldEnabled, field.TypeBool, value)
}
if value, ok := _u.mutation.PaymentMode(); ok {
_spec.SetField(paymentproviderinstance.FieldPaymentMode, field.TypeString, value)
}
if value, ok := _u.mutation.SortOrder(); ok {
_spec.SetField(paymentproviderinstance.FieldSortOrder, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedSortOrder(); ok {
_spec.AddField(paymentproviderinstance.FieldSortOrder, field.TypeInt, value)
}
if value, ok := _u.mutation.Limits(); ok {
_spec.SetField(paymentproviderinstance.FieldLimits, field.TypeString, value)
}
if value, ok := _u.mutation.RefundEnabled(); ok {
_spec.SetField(paymentproviderinstance.FieldRefundEnabled, field.TypeBool, value)
}
if value, ok := _u.mutation.UpdatedAt(); ok {
_spec.SetField(paymentproviderinstance.FieldUpdatedAt, field.TypeTime, value)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{paymentproviderinstance.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// PaymentProviderInstanceUpdateOne is the builder for updating a single PaymentProviderInstance entity.
type PaymentProviderInstanceUpdateOne struct {
config
fields []string
hooks []Hook
mutation *PaymentProviderInstanceMutation
}
// SetProviderKey sets the "provider_key" field.
func (_u *PaymentProviderInstanceUpdateOne) SetProviderKey(v string) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetProviderKey(v)
return _u
}
// SetNillableProviderKey sets the "provider_key" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillableProviderKey(v *string) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetProviderKey(*v)
}
return _u
}
// SetName sets the "name" field.
func (_u *PaymentProviderInstanceUpdateOne) SetName(v string) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetName(v)
return _u
}
// SetNillableName sets the "name" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillableName(v *string) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetName(*v)
}
return _u
}
// SetConfig sets the "config" field.
func (_u *PaymentProviderInstanceUpdateOne) SetConfig(v string) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetConfig(v)
return _u
}
// SetNillableConfig sets the "config" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillableConfig(v *string) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetConfig(*v)
}
return _u
}
// SetSupportedTypes sets the "supported_types" field.
func (_u *PaymentProviderInstanceUpdateOne) SetSupportedTypes(v string) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetSupportedTypes(v)
return _u
}
// SetNillableSupportedTypes sets the "supported_types" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillableSupportedTypes(v *string) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetSupportedTypes(*v)
}
return _u
}
// SetEnabled sets the "enabled" field.
func (_u *PaymentProviderInstanceUpdateOne) SetEnabled(v bool) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetEnabled(v)
return _u
}
// SetNillableEnabled sets the "enabled" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillableEnabled(v *bool) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetEnabled(*v)
}
return _u
}
// SetPaymentMode sets the "payment_mode" field.
func (_u *PaymentProviderInstanceUpdateOne) SetPaymentMode(v string) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetPaymentMode(v)
return _u
}
// SetNillablePaymentMode sets the "payment_mode" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillablePaymentMode(v *string) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetPaymentMode(*v)
}
return _u
}
// SetSortOrder sets the "sort_order" field.
func (_u *PaymentProviderInstanceUpdateOne) SetSortOrder(v int) *PaymentProviderInstanceUpdateOne {
_u.mutation.ResetSortOrder()
_u.mutation.SetSortOrder(v)
return _u
}
// SetNillableSortOrder sets the "sort_order" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillableSortOrder(v *int) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetSortOrder(*v)
}
return _u
}
// AddSortOrder adds value to the "sort_order" field.
func (_u *PaymentProviderInstanceUpdateOne) AddSortOrder(v int) *PaymentProviderInstanceUpdateOne {
_u.mutation.AddSortOrder(v)
return _u
}
// SetLimits sets the "limits" field.
func (_u *PaymentProviderInstanceUpdateOne) SetLimits(v string) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetLimits(v)
return _u
}
// SetNillableLimits sets the "limits" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillableLimits(v *string) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetLimits(*v)
}
return _u
}
// SetRefundEnabled sets the "refund_enabled" field.
func (_u *PaymentProviderInstanceUpdateOne) SetRefundEnabled(v bool) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetRefundEnabled(v)
return _u
}
// SetNillableRefundEnabled sets the "refund_enabled" field if the given value is not nil.
func (_u *PaymentProviderInstanceUpdateOne) SetNillableRefundEnabled(v *bool) *PaymentProviderInstanceUpdateOne {
if v != nil {
_u.SetRefundEnabled(*v)
}
return _u
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *PaymentProviderInstanceUpdateOne) SetUpdatedAt(v time.Time) *PaymentProviderInstanceUpdateOne {
_u.mutation.SetUpdatedAt(v)
return _u
}
// Mutation returns the PaymentProviderInstanceMutation object of the builder.
func (_u *PaymentProviderInstanceUpdateOne) Mutation() *PaymentProviderInstanceMutation {
return _u.mutation
}
// Where appends a list predicates to the PaymentProviderInstanceUpdate builder.
func (_u *PaymentProviderInstanceUpdateOne) Where(ps ...predicate.PaymentProviderInstance) *PaymentProviderInstanceUpdateOne {
_u.mutation.Where(ps...)
return _u
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (_u *PaymentProviderInstanceUpdateOne) Select(field string, fields ...string) *PaymentProviderInstanceUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated PaymentProviderInstance entity.
func (_u *PaymentProviderInstanceUpdateOne) Save(ctx context.Context) (*PaymentProviderInstance, error) {
_u.defaults()
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *PaymentProviderInstanceUpdateOne) SaveX(ctx context.Context) *PaymentProviderInstance {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *PaymentProviderInstanceUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *PaymentProviderInstanceUpdateOne) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (_u *PaymentProviderInstanceUpdateOne) defaults() {
if _, ok := _u.mutation.UpdatedAt(); !ok {
v := paymentproviderinstance.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *PaymentProviderInstanceUpdateOne) check() error {
if v, ok := _u.mutation.ProviderKey(); ok {
if err := paymentproviderinstance.ProviderKeyValidator(v); err != nil {
return &ValidationError{Name: "provider_key", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.provider_key": %w`, err)}
}
}
if v, ok := _u.mutation.Name(); ok {
if err := paymentproviderinstance.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.name": %w`, err)}
}
}
if v, ok := _u.mutation.SupportedTypes(); ok {
if err := paymentproviderinstance.SupportedTypesValidator(v); err != nil {
return &ValidationError{Name: "supported_types", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.supported_types": %w`, err)}
}
}
if v, ok := _u.mutation.PaymentMode(); ok {
if err := paymentproviderinstance.PaymentModeValidator(v); err != nil {
return &ValidationError{Name: "payment_mode", err: fmt.Errorf(`ent: validator failed for field "PaymentProviderInstance.payment_mode": %w`, err)}
}
}
return nil
}
func (_u *PaymentProviderInstanceUpdateOne) sqlSave(ctx context.Context) (_node *PaymentProviderInstance, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(paymentproviderinstance.Table, paymentproviderinstance.Columns, sqlgraph.NewFieldSpec(paymentproviderinstance.FieldID, field.TypeInt64))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "PaymentProviderInstance.id" for update`)}
}
_spec.Node.ID.Value = id
if fields := _u.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, paymentproviderinstance.FieldID)
for _, f := range fields {
if !paymentproviderinstance.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != paymentproviderinstance.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, f)
}
}
}
if ps := _u.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := _u.mutation.ProviderKey(); ok {
_spec.SetField(paymentproviderinstance.FieldProviderKey, field.TypeString, value)
}
if value, ok := _u.mutation.Name(); ok {
_spec.SetField(paymentproviderinstance.FieldName, field.TypeString, value)
}
if value, ok := _u.mutation.Config(); ok {
_spec.SetField(paymentproviderinstance.FieldConfig, field.TypeString, value)
}
if value, ok := _u.mutation.SupportedTypes(); ok {
_spec.SetField(paymentproviderinstance.FieldSupportedTypes, field.TypeString, value)
}
if value, ok := _u.mutation.Enabled(); ok {
_spec.SetField(paymentproviderinstance.FieldEnabled, field.TypeBool, value)
}
if value, ok := _u.mutation.PaymentMode(); ok {
_spec.SetField(paymentproviderinstance.FieldPaymentMode, field.TypeString, value)
}
if value, ok := _u.mutation.SortOrder(); ok {
_spec.SetField(paymentproviderinstance.FieldSortOrder, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedSortOrder(); ok {
_spec.AddField(paymentproviderinstance.FieldSortOrder, field.TypeInt, value)
}
if value, ok := _u.mutation.Limits(); ok {
_spec.SetField(paymentproviderinstance.FieldLimits, field.TypeString, value)
}
if value, ok := _u.mutation.RefundEnabled(); ok {
_spec.SetField(paymentproviderinstance.FieldRefundEnabled, field.TypeBool, value)
}
if value, ok := _u.mutation.UpdatedAt(); ok {
_spec.SetField(paymentproviderinstance.FieldUpdatedAt, field.TypeTime, value)
}
_node = &PaymentProviderInstance{config: _u.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{paymentproviderinstance.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
_u.mutation.done = true
return _node, nil
}

View File

@ -30,6 +30,15 @@ type Group func(*sql.Selector)
// IdempotencyRecord is the predicate function for idempotencyrecord builders.
type IdempotencyRecord func(*sql.Selector)
// PaymentAuditLog is the predicate function for paymentauditlog builders.
type PaymentAuditLog func(*sql.Selector)
// PaymentOrder is the predicate function for paymentorder builders.
type PaymentOrder func(*sql.Selector)
// PaymentProviderInstance is the predicate function for paymentproviderinstance builders.
type PaymentProviderInstance func(*sql.Selector)
// PromoCode is the predicate function for promocode builders.
type PromoCode func(*sql.Selector)
@ -48,6 +57,9 @@ type SecuritySecret func(*sql.Selector)
// Setting is the predicate function for setting builders.
type Setting func(*sql.Selector)
// SubscriptionPlan is the predicate function for subscriptionplan builders.
type SubscriptionPlan func(*sql.Selector)
// TLSFingerprintProfile is the predicate function for tlsfingerprintprofile builders.
type TLSFingerprintProfile func(*sql.Selector)

View File

@ -13,6 +13,9 @@ import (
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
"github.com/Wei-Shaw/sub2api/ent/paymentauditlog"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
"github.com/Wei-Shaw/sub2api/ent/promocode"
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/ent/proxy"
@ -20,6 +23,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/schema"
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
"github.com/Wei-Shaw/sub2api/ent/setting"
"github.com/Wei-Shaw/sub2api/ent/subscriptionplan"
"github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile"
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
"github.com/Wei-Shaw/sub2api/ent/usagelog"
@ -508,6 +512,172 @@ func init() {
idempotencyrecordDescErrorReason := idempotencyrecordFields[6].Descriptor()
// idempotencyrecord.ErrorReasonValidator is a validator for the "error_reason" field. It is called by the builders before save.
idempotencyrecord.ErrorReasonValidator = idempotencyrecordDescErrorReason.Validators[0].(func(string) error)
paymentauditlogFields := schema.PaymentAuditLog{}.Fields()
_ = paymentauditlogFields
// paymentauditlogDescOrderID is the schema descriptor for order_id field.
paymentauditlogDescOrderID := paymentauditlogFields[0].Descriptor()
// paymentauditlog.OrderIDValidator is a validator for the "order_id" field. It is called by the builders before save.
paymentauditlog.OrderIDValidator = paymentauditlogDescOrderID.Validators[0].(func(string) error)
// paymentauditlogDescAction is the schema descriptor for action field.
paymentauditlogDescAction := paymentauditlogFields[1].Descriptor()
// paymentauditlog.ActionValidator is a validator for the "action" field. It is called by the builders before save.
paymentauditlog.ActionValidator = paymentauditlogDescAction.Validators[0].(func(string) error)
// paymentauditlogDescDetail is the schema descriptor for detail field.
paymentauditlogDescDetail := paymentauditlogFields[2].Descriptor()
// paymentauditlog.DefaultDetail holds the default value on creation for the detail field.
paymentauditlog.DefaultDetail = paymentauditlogDescDetail.Default.(string)
// paymentauditlogDescOperator is the schema descriptor for operator field.
paymentauditlogDescOperator := paymentauditlogFields[3].Descriptor()
// paymentauditlog.DefaultOperator holds the default value on creation for the operator field.
paymentauditlog.DefaultOperator = paymentauditlogDescOperator.Default.(string)
// paymentauditlog.OperatorValidator is a validator for the "operator" field. It is called by the builders before save.
paymentauditlog.OperatorValidator = paymentauditlogDescOperator.Validators[0].(func(string) error)
// paymentauditlogDescCreatedAt is the schema descriptor for created_at field.
paymentauditlogDescCreatedAt := paymentauditlogFields[4].Descriptor()
// paymentauditlog.DefaultCreatedAt holds the default value on creation for the created_at field.
paymentauditlog.DefaultCreatedAt = paymentauditlogDescCreatedAt.Default.(func() time.Time)
paymentorderFields := schema.PaymentOrder{}.Fields()
_ = paymentorderFields
// paymentorderDescUserEmail is the schema descriptor for user_email field.
paymentorderDescUserEmail := paymentorderFields[1].Descriptor()
// paymentorder.UserEmailValidator is a validator for the "user_email" field. It is called by the builders before save.
paymentorder.UserEmailValidator = paymentorderDescUserEmail.Validators[0].(func(string) error)
// paymentorderDescUserName is the schema descriptor for user_name field.
paymentorderDescUserName := paymentorderFields[2].Descriptor()
// paymentorder.UserNameValidator is a validator for the "user_name" field. It is called by the builders before save.
paymentorder.UserNameValidator = paymentorderDescUserName.Validators[0].(func(string) error)
// paymentorderDescFeeRate is the schema descriptor for fee_rate field.
paymentorderDescFeeRate := paymentorderFields[6].Descriptor()
// paymentorder.DefaultFeeRate holds the default value on creation for the fee_rate field.
paymentorder.DefaultFeeRate = paymentorderDescFeeRate.Default.(float64)
// paymentorderDescRechargeCode is the schema descriptor for recharge_code field.
paymentorderDescRechargeCode := paymentorderFields[7].Descriptor()
// paymentorder.RechargeCodeValidator is a validator for the "recharge_code" field. It is called by the builders before save.
paymentorder.RechargeCodeValidator = paymentorderDescRechargeCode.Validators[0].(func(string) error)
// paymentorderDescOutTradeNo is the schema descriptor for out_trade_no field.
paymentorderDescOutTradeNo := paymentorderFields[8].Descriptor()
// paymentorder.DefaultOutTradeNo holds the default value on creation for the out_trade_no field.
paymentorder.DefaultOutTradeNo = paymentorderDescOutTradeNo.Default.(string)
// paymentorder.OutTradeNoValidator is a validator for the "out_trade_no" field. It is called by the builders before save.
paymentorder.OutTradeNoValidator = paymentorderDescOutTradeNo.Validators[0].(func(string) error)
// paymentorderDescPaymentType is the schema descriptor for payment_type field.
paymentorderDescPaymentType := paymentorderFields[9].Descriptor()
// paymentorder.PaymentTypeValidator is a validator for the "payment_type" field. It is called by the builders before save.
paymentorder.PaymentTypeValidator = paymentorderDescPaymentType.Validators[0].(func(string) error)
// paymentorderDescPaymentTradeNo is the schema descriptor for payment_trade_no field.
paymentorderDescPaymentTradeNo := paymentorderFields[10].Descriptor()
// paymentorder.PaymentTradeNoValidator is a validator for the "payment_trade_no" field. It is called by the builders before save.
paymentorder.PaymentTradeNoValidator = paymentorderDescPaymentTradeNo.Validators[0].(func(string) error)
// paymentorderDescOrderType is the schema descriptor for order_type field.
paymentorderDescOrderType := paymentorderFields[14].Descriptor()
// paymentorder.DefaultOrderType holds the default value on creation for the order_type field.
paymentorder.DefaultOrderType = paymentorderDescOrderType.Default.(string)
// paymentorder.OrderTypeValidator is a validator for the "order_type" field. It is called by the builders before save.
paymentorder.OrderTypeValidator = paymentorderDescOrderType.Validators[0].(func(string) error)
// paymentorderDescProviderInstanceID is the schema descriptor for provider_instance_id field.
paymentorderDescProviderInstanceID := paymentorderFields[18].Descriptor()
// paymentorder.ProviderInstanceIDValidator is a validator for the "provider_instance_id" field. It is called by the builders before save.
paymentorder.ProviderInstanceIDValidator = paymentorderDescProviderInstanceID.Validators[0].(func(string) error)
// paymentorderDescStatus is the schema descriptor for status field.
paymentorderDescStatus := paymentorderFields[19].Descriptor()
// paymentorder.DefaultStatus holds the default value on creation for the status field.
paymentorder.DefaultStatus = paymentorderDescStatus.Default.(string)
// paymentorder.StatusValidator is a validator for the "status" field. It is called by the builders before save.
paymentorder.StatusValidator = paymentorderDescStatus.Validators[0].(func(string) error)
// paymentorderDescRefundAmount is the schema descriptor for refund_amount field.
paymentorderDescRefundAmount := paymentorderFields[20].Descriptor()
// paymentorder.DefaultRefundAmount holds the default value on creation for the refund_amount field.
paymentorder.DefaultRefundAmount = paymentorderDescRefundAmount.Default.(float64)
// paymentorderDescForceRefund is the schema descriptor for force_refund field.
paymentorderDescForceRefund := paymentorderFields[23].Descriptor()
// paymentorder.DefaultForceRefund holds the default value on creation for the force_refund field.
paymentorder.DefaultForceRefund = paymentorderDescForceRefund.Default.(bool)
// paymentorderDescRefundRequestedBy is the schema descriptor for refund_requested_by field.
paymentorderDescRefundRequestedBy := paymentorderFields[26].Descriptor()
// paymentorder.RefundRequestedByValidator is a validator for the "refund_requested_by" field. It is called by the builders before save.
paymentorder.RefundRequestedByValidator = paymentorderDescRefundRequestedBy.Validators[0].(func(string) error)
// paymentorderDescClientIP is the schema descriptor for client_ip field.
paymentorderDescClientIP := paymentorderFields[32].Descriptor()
// paymentorder.ClientIPValidator is a validator for the "client_ip" field. It is called by the builders before save.
paymentorder.ClientIPValidator = paymentorderDescClientIP.Validators[0].(func(string) error)
// paymentorderDescSrcHost is the schema descriptor for src_host field.
paymentorderDescSrcHost := paymentorderFields[33].Descriptor()
// paymentorder.SrcHostValidator is a validator for the "src_host" field. It is called by the builders before save.
paymentorder.SrcHostValidator = paymentorderDescSrcHost.Validators[0].(func(string) error)
// paymentorderDescCreatedAt is the schema descriptor for created_at field.
paymentorderDescCreatedAt := paymentorderFields[35].Descriptor()
// paymentorder.DefaultCreatedAt holds the default value on creation for the created_at field.
paymentorder.DefaultCreatedAt = paymentorderDescCreatedAt.Default.(func() time.Time)
// paymentorderDescUpdatedAt is the schema descriptor for updated_at field.
paymentorderDescUpdatedAt := paymentorderFields[36].Descriptor()
// paymentorder.DefaultUpdatedAt holds the default value on creation for the updated_at field.
paymentorder.DefaultUpdatedAt = paymentorderDescUpdatedAt.Default.(func() time.Time)
// paymentorder.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
paymentorder.UpdateDefaultUpdatedAt = paymentorderDescUpdatedAt.UpdateDefault.(func() time.Time)
paymentproviderinstanceFields := schema.PaymentProviderInstance{}.Fields()
_ = paymentproviderinstanceFields
// paymentproviderinstanceDescProviderKey is the schema descriptor for provider_key field.
paymentproviderinstanceDescProviderKey := paymentproviderinstanceFields[0].Descriptor()
// paymentproviderinstance.ProviderKeyValidator is a validator for the "provider_key" field. It is called by the builders before save.
paymentproviderinstance.ProviderKeyValidator = func() func(string) error {
validators := paymentproviderinstanceDescProviderKey.Validators
fns := [...]func(string) error{
validators[0].(func(string) error),
validators[1].(func(string) error),
}
return func(provider_key string) error {
for _, fn := range fns {
if err := fn(provider_key); err != nil {
return err
}
}
return nil
}
}()
// paymentproviderinstanceDescName is the schema descriptor for name field.
paymentproviderinstanceDescName := paymentproviderinstanceFields[1].Descriptor()
// paymentproviderinstance.DefaultName holds the default value on creation for the name field.
paymentproviderinstance.DefaultName = paymentproviderinstanceDescName.Default.(string)
// paymentproviderinstance.NameValidator is a validator for the "name" field. It is called by the builders before save.
paymentproviderinstance.NameValidator = paymentproviderinstanceDescName.Validators[0].(func(string) error)
// paymentproviderinstanceDescSupportedTypes is the schema descriptor for supported_types field.
paymentproviderinstanceDescSupportedTypes := paymentproviderinstanceFields[3].Descriptor()
// paymentproviderinstance.DefaultSupportedTypes holds the default value on creation for the supported_types field.
paymentproviderinstance.DefaultSupportedTypes = paymentproviderinstanceDescSupportedTypes.Default.(string)
// paymentproviderinstance.SupportedTypesValidator is a validator for the "supported_types" field. It is called by the builders before save.
paymentproviderinstance.SupportedTypesValidator = paymentproviderinstanceDescSupportedTypes.Validators[0].(func(string) error)
// paymentproviderinstanceDescEnabled is the schema descriptor for enabled field.
paymentproviderinstanceDescEnabled := paymentproviderinstanceFields[4].Descriptor()
// paymentproviderinstance.DefaultEnabled holds the default value on creation for the enabled field.
paymentproviderinstance.DefaultEnabled = paymentproviderinstanceDescEnabled.Default.(bool)
// paymentproviderinstanceDescPaymentMode is the schema descriptor for payment_mode field.
paymentproviderinstanceDescPaymentMode := paymentproviderinstanceFields[5].Descriptor()
// paymentproviderinstance.DefaultPaymentMode holds the default value on creation for the payment_mode field.
paymentproviderinstance.DefaultPaymentMode = paymentproviderinstanceDescPaymentMode.Default.(string)
// paymentproviderinstance.PaymentModeValidator is a validator for the "payment_mode" field. It is called by the builders before save.
paymentproviderinstance.PaymentModeValidator = paymentproviderinstanceDescPaymentMode.Validators[0].(func(string) error)
// paymentproviderinstanceDescSortOrder is the schema descriptor for sort_order field.
paymentproviderinstanceDescSortOrder := paymentproviderinstanceFields[6].Descriptor()
// paymentproviderinstance.DefaultSortOrder holds the default value on creation for the sort_order field.
paymentproviderinstance.DefaultSortOrder = paymentproviderinstanceDescSortOrder.Default.(int)
// paymentproviderinstanceDescLimits is the schema descriptor for limits field.
paymentproviderinstanceDescLimits := paymentproviderinstanceFields[7].Descriptor()
// paymentproviderinstance.DefaultLimits holds the default value on creation for the limits field.
paymentproviderinstance.DefaultLimits = paymentproviderinstanceDescLimits.Default.(string)
// paymentproviderinstanceDescRefundEnabled is the schema descriptor for refund_enabled field.
paymentproviderinstanceDescRefundEnabled := paymentproviderinstanceFields[8].Descriptor()
// paymentproviderinstance.DefaultRefundEnabled holds the default value on creation for the refund_enabled field.
paymentproviderinstance.DefaultRefundEnabled = paymentproviderinstanceDescRefundEnabled.Default.(bool)
// paymentproviderinstanceDescCreatedAt is the schema descriptor for created_at field.
paymentproviderinstanceDescCreatedAt := paymentproviderinstanceFields[9].Descriptor()
// paymentproviderinstance.DefaultCreatedAt holds the default value on creation for the created_at field.
paymentproviderinstance.DefaultCreatedAt = paymentproviderinstanceDescCreatedAt.Default.(func() time.Time)
// paymentproviderinstanceDescUpdatedAt is the schema descriptor for updated_at field.
paymentproviderinstanceDescUpdatedAt := paymentproviderinstanceFields[10].Descriptor()
// paymentproviderinstance.DefaultUpdatedAt holds the default value on creation for the updated_at field.
paymentproviderinstance.DefaultUpdatedAt = paymentproviderinstanceDescUpdatedAt.Default.(func() time.Time)
// paymentproviderinstance.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
paymentproviderinstance.UpdateDefaultUpdatedAt = paymentproviderinstanceDescUpdatedAt.UpdateDefault.(func() time.Time)
promocodeFields := schema.PromoCode{}.Fields()
_ = promocodeFields
// promocodeDescCode is the schema descriptor for code field.
@ -756,6 +926,68 @@ func init() {
setting.DefaultUpdatedAt = settingDescUpdatedAt.Default.(func() time.Time)
// setting.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
setting.UpdateDefaultUpdatedAt = settingDescUpdatedAt.UpdateDefault.(func() time.Time)
subscriptionplanFields := schema.SubscriptionPlan{}.Fields()
_ = subscriptionplanFields
// subscriptionplanDescName is the schema descriptor for name field.
subscriptionplanDescName := subscriptionplanFields[1].Descriptor()
// subscriptionplan.NameValidator is a validator for the "name" field. It is called by the builders before save.
subscriptionplan.NameValidator = func() func(string) error {
validators := subscriptionplanDescName.Validators
fns := [...]func(string) error{
validators[0].(func(string) error),
validators[1].(func(string) error),
}
return func(name string) error {
for _, fn := range fns {
if err := fn(name); err != nil {
return err
}
}
return nil
}
}()
// subscriptionplanDescDescription is the schema descriptor for description field.
subscriptionplanDescDescription := subscriptionplanFields[2].Descriptor()
// subscriptionplan.DefaultDescription holds the default value on creation for the description field.
subscriptionplan.DefaultDescription = subscriptionplanDescDescription.Default.(string)
// subscriptionplanDescValidityDays is the schema descriptor for validity_days field.
subscriptionplanDescValidityDays := subscriptionplanFields[5].Descriptor()
// subscriptionplan.DefaultValidityDays holds the default value on creation for the validity_days field.
subscriptionplan.DefaultValidityDays = subscriptionplanDescValidityDays.Default.(int)
// subscriptionplanDescValidityUnit is the schema descriptor for validity_unit field.
subscriptionplanDescValidityUnit := subscriptionplanFields[6].Descriptor()
// subscriptionplan.DefaultValidityUnit holds the default value on creation for the validity_unit field.
subscriptionplan.DefaultValidityUnit = subscriptionplanDescValidityUnit.Default.(string)
// subscriptionplan.ValidityUnitValidator is a validator for the "validity_unit" field. It is called by the builders before save.
subscriptionplan.ValidityUnitValidator = subscriptionplanDescValidityUnit.Validators[0].(func(string) error)
// subscriptionplanDescFeatures is the schema descriptor for features field.
subscriptionplanDescFeatures := subscriptionplanFields[7].Descriptor()
// subscriptionplan.DefaultFeatures holds the default value on creation for the features field.
subscriptionplan.DefaultFeatures = subscriptionplanDescFeatures.Default.(string)
// subscriptionplanDescProductName is the schema descriptor for product_name field.
subscriptionplanDescProductName := subscriptionplanFields[8].Descriptor()
// subscriptionplan.DefaultProductName holds the default value on creation for the product_name field.
subscriptionplan.DefaultProductName = subscriptionplanDescProductName.Default.(string)
// subscriptionplan.ProductNameValidator is a validator for the "product_name" field. It is called by the builders before save.
subscriptionplan.ProductNameValidator = subscriptionplanDescProductName.Validators[0].(func(string) error)
// subscriptionplanDescForSale is the schema descriptor for for_sale field.
subscriptionplanDescForSale := subscriptionplanFields[9].Descriptor()
// subscriptionplan.DefaultForSale holds the default value on creation for the for_sale field.
subscriptionplan.DefaultForSale = subscriptionplanDescForSale.Default.(bool)
// subscriptionplanDescSortOrder is the schema descriptor for sort_order field.
subscriptionplanDescSortOrder := subscriptionplanFields[10].Descriptor()
// subscriptionplan.DefaultSortOrder holds the default value on creation for the sort_order field.
subscriptionplan.DefaultSortOrder = subscriptionplanDescSortOrder.Default.(int)
// subscriptionplanDescCreatedAt is the schema descriptor for created_at field.
subscriptionplanDescCreatedAt := subscriptionplanFields[11].Descriptor()
// subscriptionplan.DefaultCreatedAt holds the default value on creation for the created_at field.
subscriptionplan.DefaultCreatedAt = subscriptionplanDescCreatedAt.Default.(func() time.Time)
// subscriptionplanDescUpdatedAt is the schema descriptor for updated_at field.
subscriptionplanDescUpdatedAt := subscriptionplanFields[12].Descriptor()
// subscriptionplan.DefaultUpdatedAt holds the default value on creation for the updated_at field.
subscriptionplan.DefaultUpdatedAt = subscriptionplanDescUpdatedAt.Default.(func() time.Time)
// subscriptionplan.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
subscriptionplan.UpdateDefaultUpdatedAt = subscriptionplanDescUpdatedAt.UpdateDefault.(func() time.Time)
tlsfingerprintprofileMixin := schema.TLSFingerprintProfile{}.Mixin()
tlsfingerprintprofileMixinFields0 := tlsfingerprintprofileMixin[0].Fields()
_ = tlsfingerprintprofileMixinFields0

View File

@ -0,0 +1,54 @@
package schema
import (
"time"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// PaymentAuditLog holds the schema definition for the PaymentAuditLog entity.
//
// 删除策略:硬删除
// PaymentAuditLog 使用硬删除而非软删除,原因如下:
// - 审计日志本身即为不可变记录,通常只追加不修改
// - 如需清理历史日志,直接按时间范围批量删除即可
// - 保持表结构简洁,提升插入和查询性能
type PaymentAuditLog struct {
ent.Schema
}
func (PaymentAuditLog) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "payment_audit_logs"},
}
}
func (PaymentAuditLog) Fields() []ent.Field {
return []ent.Field{
field.String("order_id").
MaxLen(64),
field.String("action").
MaxLen(50),
field.String("detail").
SchemaType(map[string]string{dialect.Postgres: "text"}).
Default(""),
field.String("operator").
MaxLen(100).
Default("system"),
field.Time("created_at").
Immutable().
Default(time.Now).
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
}
}
func (PaymentAuditLog) Indexes() []ent.Index {
return []ent.Index{
index.Fields("order_id"),
}
}

View File

@ -0,0 +1,190 @@
package schema
import (
"time"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// PaymentOrder holds the schema definition for the PaymentOrder entity.
//
// 删除策略:硬删除
// PaymentOrder 使用硬删除而非软删除,原因如下:
// - 订单通过 status 字段追踪完整生命周期,无需依赖软删除
// - 订单审计通过 PaymentAuditLog 表记录,删除前可归档
// - 减少查询复杂度,避免软删除过滤开销
type PaymentOrder struct {
ent.Schema
}
func (PaymentOrder) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "payment_orders"},
}
}
func (PaymentOrder) Fields() []ent.Field {
return []ent.Field{
// 用户信息(冗余存储,避免关联查询)
field.Int64("user_id"),
field.String("user_email").
MaxLen(255),
field.String("user_name").
MaxLen(100),
field.String("user_notes").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "text"}),
// 金额信息
field.Float("amount").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,2)"}),
field.Float("pay_amount").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,2)"}),
field.Float("fee_rate").
SchemaType(map[string]string{dialect.Postgres: "decimal(10,4)"}).
Default(0),
field.String("recharge_code").
MaxLen(64),
// 支付信息
field.String("out_trade_no").
MaxLen(64).
Default(""),
field.String("payment_type").
MaxLen(30),
field.String("payment_trade_no").
MaxLen(128),
field.String("pay_url").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "text"}),
field.String("qr_code").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "text"}),
field.String("qr_code_img").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "text"}),
// 订单类型 & 订阅关联
field.String("order_type").
MaxLen(20).
Default("balance"),
field.Int64("plan_id").
Optional().
Nillable(),
field.Int64("subscription_group_id").
Optional().
Nillable(),
field.Int("subscription_days").
Optional().
Nillable(),
field.String("provider_instance_id").
Optional().
Nillable().
MaxLen(64),
// 状态
field.String("status").
MaxLen(30).
Default("PENDING"),
// 退款信息
field.Float("refund_amount").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,2)"}).
Default(0),
field.String("refund_reason").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "text"}),
field.Time("refund_at").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Bool("force_refund").
Default(false),
field.Time("refund_requested_at").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.String("refund_request_reason").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "text"}),
field.String("refund_requested_by").
Optional().
Nillable().
MaxLen(20),
// 时间节点
field.Time("expires_at").
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("paid_at").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("completed_at").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("failed_at").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.String("failed_reason").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "text"}),
// 来源信息
field.String("client_ip").
MaxLen(50),
field.String("src_host").
MaxLen(255),
field.String("src_url").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "text"}),
// 时间戳
field.Time("created_at").
Immutable().
Default(time.Now).
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("updated_at").
Default(time.Now).
UpdateDefault(time.Now).
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
}
}
func (PaymentOrder) Edges() []ent.Edge {
return []ent.Edge{
edge.From("user", User.Type).
Ref("payment_orders").
Field("user_id").
Unique().
Required(),
}
}
func (PaymentOrder) Indexes() []ent.Index {
return []ent.Index{
index.Fields("out_trade_no"),
index.Fields("user_id"),
index.Fields("status"),
index.Fields("expires_at"),
index.Fields("created_at"),
index.Fields("paid_at"),
index.Fields("payment_type", "paid_at"),
index.Fields("order_type"),
}
}

View File

@ -0,0 +1,72 @@
package schema
import (
"time"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// PaymentProviderInstance holds the schema definition for the PaymentProviderInstance entity.
//
// 删除策略:硬删除
// PaymentProviderInstance 使用硬删除而非软删除,原因如下:
// - 服务商实例为管理员配置的支付通道,删除即表示废弃
// - 通过 enabled 字段控制是否启用,删除仅用于彻底移除
// - config 字段存储加密后的密钥信息,删除时应彻底清除
type PaymentProviderInstance struct {
ent.Schema
}
func (PaymentProviderInstance) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "payment_provider_instances"},
}
}
func (PaymentProviderInstance) Fields() []ent.Field {
return []ent.Field{
field.String("provider_key").
MaxLen(30).
NotEmpty(),
field.String("name").
MaxLen(100).
Default(""),
field.String("config").
SchemaType(map[string]string{dialect.Postgres: "text"}),
field.String("supported_types").
MaxLen(200).
Default(""),
field.Bool("enabled").
Default(true),
field.String("payment_mode").
MaxLen(20).
Default(""),
field.Int("sort_order").
Default(0),
field.String("limits").
SchemaType(map[string]string{dialect.Postgres: "text"}).
Default(""),
field.Bool("refund_enabled").
Default(false),
field.Time("created_at").
Immutable().
Default(time.Now).
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("updated_at").
Default(time.Now).
UpdateDefault(time.Now).
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
}
}
func (PaymentProviderInstance) Indexes() []ent.Index {
return []ent.Index{
index.Fields("provider_key"),
index.Fields("enabled"),
}
}

View File

@ -0,0 +1,77 @@
package schema
import (
"time"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// SubscriptionPlan holds the schema definition for the SubscriptionPlan entity.
//
// 删除策略:硬删除
// SubscriptionPlan 使用硬删除而非软删除,原因如下:
// - 套餐为管理员维护的商品配置,删除即表示下架移除
// - 通过 for_sale 字段控制是否在售,删除仅用于彻底移除
// - 已购买的订阅记录保存在 UserSubscription 中,不受套餐删除影响
type SubscriptionPlan struct {
ent.Schema
}
func (SubscriptionPlan) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "subscription_plans"},
}
}
func (SubscriptionPlan) Fields() []ent.Field {
return []ent.Field{
field.Int64("group_id"),
field.String("name").
MaxLen(100).
NotEmpty(),
field.String("description").
SchemaType(map[string]string{dialect.Postgres: "text"}).
Default(""),
field.Float("price").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,2)"}),
field.Float("original_price").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,2)"}).
Optional().
Nillable(),
field.Int("validity_days").
Default(30),
field.String("validity_unit").
MaxLen(10).
Default("day"),
field.String("features").
SchemaType(map[string]string{dialect.Postgres: "text"}).
Default(""),
field.String("product_name").
MaxLen(100).
Default(""),
field.Bool("for_sale").
Default(true),
field.Int("sort_order").
Default(0),
field.Time("created_at").
Immutable().
Default(time.Now).
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
field.Time("updated_at").
Default(time.Now).
UpdateDefault(time.Now).
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
}
}
func (SubscriptionPlan) Indexes() []ent.Index {
return []ent.Index{
index.Fields("group_id"),
index.Fields("for_sale"),
}
}

View File

@ -87,6 +87,7 @@ func (User) Edges() []ent.Edge {
edge.To("usage_logs", UsageLog.Type),
edge.To("attribute_values", UserAttributeValue.Type),
edge.To("promo_code_usages", PromoCodeUsage.Type),
edge.To("payment_orders", PaymentOrder.Type),
}
}

View File

@ -0,0 +1,245 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"fmt"
"strings"
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/subscriptionplan"
)
// SubscriptionPlan is the model entity for the SubscriptionPlan schema.
type SubscriptionPlan struct {
config `json:"-"`
// ID of the ent.
ID int64 `json:"id,omitempty"`
// GroupID holds the value of the "group_id" field.
GroupID int64 `json:"group_id,omitempty"`
// Name holds the value of the "name" field.
Name string `json:"name,omitempty"`
// Description holds the value of the "description" field.
Description string `json:"description,omitempty"`
// Price holds the value of the "price" field.
Price float64 `json:"price,omitempty"`
// OriginalPrice holds the value of the "original_price" field.
OriginalPrice *float64 `json:"original_price,omitempty"`
// ValidityDays holds the value of the "validity_days" field.
ValidityDays int `json:"validity_days,omitempty"`
// ValidityUnit holds the value of the "validity_unit" field.
ValidityUnit string `json:"validity_unit,omitempty"`
// Features holds the value of the "features" field.
Features string `json:"features,omitempty"`
// ProductName holds the value of the "product_name" field.
ProductName string `json:"product_name,omitempty"`
// ForSale holds the value of the "for_sale" field.
ForSale bool `json:"for_sale,omitempty"`
// SortOrder holds the value of the "sort_order" field.
SortOrder int `json:"sort_order,omitempty"`
// CreatedAt holds the value of the "created_at" field.
CreatedAt time.Time `json:"created_at,omitempty"`
// UpdatedAt holds the value of the "updated_at" field.
UpdatedAt time.Time `json:"updated_at,omitempty"`
selectValues sql.SelectValues
}
// scanValues returns the types for scanning values from sql.Rows.
func (*SubscriptionPlan) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case subscriptionplan.FieldForSale:
values[i] = new(sql.NullBool)
case subscriptionplan.FieldPrice, subscriptionplan.FieldOriginalPrice:
values[i] = new(sql.NullFloat64)
case subscriptionplan.FieldID, subscriptionplan.FieldGroupID, subscriptionplan.FieldValidityDays, subscriptionplan.FieldSortOrder:
values[i] = new(sql.NullInt64)
case subscriptionplan.FieldName, subscriptionplan.FieldDescription, subscriptionplan.FieldValidityUnit, subscriptionplan.FieldFeatures, subscriptionplan.FieldProductName:
values[i] = new(sql.NullString)
case subscriptionplan.FieldCreatedAt, subscriptionplan.FieldUpdatedAt:
values[i] = new(sql.NullTime)
default:
values[i] = new(sql.UnknownType)
}
}
return values, nil
}
// assignValues assigns the values that were returned from sql.Rows (after scanning)
// to the SubscriptionPlan fields.
func (_m *SubscriptionPlan) assignValues(columns []string, values []any) error {
if m, n := len(values), len(columns); m < n {
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
}
for i := range columns {
switch columns[i] {
case subscriptionplan.FieldID:
value, ok := values[i].(*sql.NullInt64)
if !ok {
return fmt.Errorf("unexpected type %T for field id", value)
}
_m.ID = int64(value.Int64)
case subscriptionplan.FieldGroupID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field group_id", values[i])
} else if value.Valid {
_m.GroupID = value.Int64
}
case subscriptionplan.FieldName:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field name", values[i])
} else if value.Valid {
_m.Name = value.String
}
case subscriptionplan.FieldDescription:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field description", values[i])
} else if value.Valid {
_m.Description = value.String
}
case subscriptionplan.FieldPrice:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field price", values[i])
} else if value.Valid {
_m.Price = value.Float64
}
case subscriptionplan.FieldOriginalPrice:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field original_price", values[i])
} else if value.Valid {
_m.OriginalPrice = new(float64)
*_m.OriginalPrice = value.Float64
}
case subscriptionplan.FieldValidityDays:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field validity_days", values[i])
} else if value.Valid {
_m.ValidityDays = int(value.Int64)
}
case subscriptionplan.FieldValidityUnit:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field validity_unit", values[i])
} else if value.Valid {
_m.ValidityUnit = value.String
}
case subscriptionplan.FieldFeatures:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field features", values[i])
} else if value.Valid {
_m.Features = value.String
}
case subscriptionplan.FieldProductName:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field product_name", values[i])
} else if value.Valid {
_m.ProductName = value.String
}
case subscriptionplan.FieldForSale:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field for_sale", values[i])
} else if value.Valid {
_m.ForSale = value.Bool
}
case subscriptionplan.FieldSortOrder:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field sort_order", values[i])
} else if value.Valid {
_m.SortOrder = int(value.Int64)
}
case subscriptionplan.FieldCreatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field created_at", values[i])
} else if value.Valid {
_m.CreatedAt = value.Time
}
case subscriptionplan.FieldUpdatedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field updated_at", values[i])
} else if value.Valid {
_m.UpdatedAt = value.Time
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the SubscriptionPlan.
// This includes values selected through modifiers, order, etc.
func (_m *SubscriptionPlan) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// Update returns a builder for updating this SubscriptionPlan.
// Note that you need to call SubscriptionPlan.Unwrap() before calling this method if this SubscriptionPlan
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *SubscriptionPlan) Update() *SubscriptionPlanUpdateOne {
return NewSubscriptionPlanClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the SubscriptionPlan entity that was returned from a transaction after it was closed,
// so that all future queries will be executed through the driver which created the transaction.
func (_m *SubscriptionPlan) Unwrap() *SubscriptionPlan {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: SubscriptionPlan is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *SubscriptionPlan) String() string {
var builder strings.Builder
builder.WriteString("SubscriptionPlan(")
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
builder.WriteString("group_id=")
builder.WriteString(fmt.Sprintf("%v", _m.GroupID))
builder.WriteString(", ")
builder.WriteString("name=")
builder.WriteString(_m.Name)
builder.WriteString(", ")
builder.WriteString("description=")
builder.WriteString(_m.Description)
builder.WriteString(", ")
builder.WriteString("price=")
builder.WriteString(fmt.Sprintf("%v", _m.Price))
builder.WriteString(", ")
if v := _m.OriginalPrice; v != nil {
builder.WriteString("original_price=")
builder.WriteString(fmt.Sprintf("%v", *v))
}
builder.WriteString(", ")
builder.WriteString("validity_days=")
builder.WriteString(fmt.Sprintf("%v", _m.ValidityDays))
builder.WriteString(", ")
builder.WriteString("validity_unit=")
builder.WriteString(_m.ValidityUnit)
builder.WriteString(", ")
builder.WriteString("features=")
builder.WriteString(_m.Features)
builder.WriteString(", ")
builder.WriteString("product_name=")
builder.WriteString(_m.ProductName)
builder.WriteString(", ")
builder.WriteString("for_sale=")
builder.WriteString(fmt.Sprintf("%v", _m.ForSale))
builder.WriteString(", ")
builder.WriteString("sort_order=")
builder.WriteString(fmt.Sprintf("%v", _m.SortOrder))
builder.WriteString(", ")
builder.WriteString("created_at=")
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
builder.WriteString(", ")
builder.WriteString("updated_at=")
builder.WriteString(_m.UpdatedAt.Format(time.ANSIC))
builder.WriteByte(')')
return builder.String()
}
// SubscriptionPlans is a parsable slice of SubscriptionPlan.
type SubscriptionPlans []*SubscriptionPlan

View File

@ -0,0 +1,174 @@
// Code generated by ent, DO NOT EDIT.
package subscriptionplan
import (
"time"
"entgo.io/ent/dialect/sql"
)
const (
// Label holds the string label denoting the subscriptionplan type in the database.
Label = "subscription_plan"
// FieldID holds the string denoting the id field in the database.
FieldID = "id"
// FieldGroupID holds the string denoting the group_id field in the database.
FieldGroupID = "group_id"
// FieldName holds the string denoting the name field in the database.
FieldName = "name"
// FieldDescription holds the string denoting the description field in the database.
FieldDescription = "description"
// FieldPrice holds the string denoting the price field in the database.
FieldPrice = "price"
// FieldOriginalPrice holds the string denoting the original_price field in the database.
FieldOriginalPrice = "original_price"
// FieldValidityDays holds the string denoting the validity_days field in the database.
FieldValidityDays = "validity_days"
// FieldValidityUnit holds the string denoting the validity_unit field in the database.
FieldValidityUnit = "validity_unit"
// FieldFeatures holds the string denoting the features field in the database.
FieldFeatures = "features"
// FieldProductName holds the string denoting the product_name field in the database.
FieldProductName = "product_name"
// FieldForSale holds the string denoting the for_sale field in the database.
FieldForSale = "for_sale"
// FieldSortOrder holds the string denoting the sort_order field in the database.
FieldSortOrder = "sort_order"
// FieldCreatedAt holds the string denoting the created_at field in the database.
FieldCreatedAt = "created_at"
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
FieldUpdatedAt = "updated_at"
// Table holds the table name of the subscriptionplan in the database.
Table = "subscription_plans"
)
// Columns holds all SQL columns for subscriptionplan fields.
var Columns = []string{
FieldID,
FieldGroupID,
FieldName,
FieldDescription,
FieldPrice,
FieldOriginalPrice,
FieldValidityDays,
FieldValidityUnit,
FieldFeatures,
FieldProductName,
FieldForSale,
FieldSortOrder,
FieldCreatedAt,
FieldUpdatedAt,
}
// ValidColumn reports if the column name is valid (part of the table columns).
func ValidColumn(column string) bool {
for i := range Columns {
if column == Columns[i] {
return true
}
}
return false
}
var (
// NameValidator is a validator for the "name" field. It is called by the builders before save.
NameValidator func(string) error
// DefaultDescription holds the default value on creation for the "description" field.
DefaultDescription string
// DefaultValidityDays holds the default value on creation for the "validity_days" field.
DefaultValidityDays int
// DefaultValidityUnit holds the default value on creation for the "validity_unit" field.
DefaultValidityUnit string
// ValidityUnitValidator is a validator for the "validity_unit" field. It is called by the builders before save.
ValidityUnitValidator func(string) error
// DefaultFeatures holds the default value on creation for the "features" field.
DefaultFeatures string
// DefaultProductName holds the default value on creation for the "product_name" field.
DefaultProductName string
// ProductNameValidator is a validator for the "product_name" field. It is called by the builders before save.
ProductNameValidator func(string) error
// DefaultForSale holds the default value on creation for the "for_sale" field.
DefaultForSale bool
// DefaultSortOrder holds the default value on creation for the "sort_order" field.
DefaultSortOrder int
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
DefaultCreatedAt func() time.Time
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
DefaultUpdatedAt func() time.Time
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
UpdateDefaultUpdatedAt func() time.Time
)
// OrderOption defines the ordering options for the SubscriptionPlan queries.
type OrderOption func(*sql.Selector)
// ByID orders the results by the id field.
func ByID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldID, opts...).ToFunc()
}
// ByGroupID orders the results by the group_id field.
func ByGroupID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldGroupID, opts...).ToFunc()
}
// ByName orders the results by the name field.
func ByName(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldName, opts...).ToFunc()
}
// ByDescription orders the results by the description field.
func ByDescription(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldDescription, opts...).ToFunc()
}
// ByPrice orders the results by the price field.
func ByPrice(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPrice, opts...).ToFunc()
}
// ByOriginalPrice orders the results by the original_price field.
func ByOriginalPrice(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldOriginalPrice, opts...).ToFunc()
}
// ByValidityDays orders the results by the validity_days field.
func ByValidityDays(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldValidityDays, opts...).ToFunc()
}
// ByValidityUnit orders the results by the validity_unit field.
func ByValidityUnit(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldValidityUnit, opts...).ToFunc()
}
// ByFeatures orders the results by the features field.
func ByFeatures(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldFeatures, opts...).ToFunc()
}
// ByProductName orders the results by the product_name field.
func ByProductName(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldProductName, opts...).ToFunc()
}
// ByForSale orders the results by the for_sale field.
func ByForSale(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldForSale, opts...).ToFunc()
}
// BySortOrder orders the results by the sort_order field.
func BySortOrder(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSortOrder, opts...).ToFunc()
}
// ByCreatedAt orders the results by the created_at field.
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
}
// ByUpdatedAt orders the results by the updated_at field.
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
}

View File

@ -0,0 +1,760 @@
// Code generated by ent, DO NOT EDIT.
package subscriptionplan
import (
"time"
"entgo.io/ent/dialect/sql"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLTE(FieldID, id))
}
// GroupID applies equality check predicate on the "group_id" field. It's identical to GroupIDEQ.
func GroupID(v int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldGroupID, v))
}
// Name applies equality check predicate on the "name" field. It's identical to NameEQ.
func Name(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldName, v))
}
// Description applies equality check predicate on the "description" field. It's identical to DescriptionEQ.
func Description(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldDescription, v))
}
// Price applies equality check predicate on the "price" field. It's identical to PriceEQ.
func Price(v float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldPrice, v))
}
// OriginalPrice applies equality check predicate on the "original_price" field. It's identical to OriginalPriceEQ.
func OriginalPrice(v float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldOriginalPrice, v))
}
// ValidityDays applies equality check predicate on the "validity_days" field. It's identical to ValidityDaysEQ.
func ValidityDays(v int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldValidityDays, v))
}
// ValidityUnit applies equality check predicate on the "validity_unit" field. It's identical to ValidityUnitEQ.
func ValidityUnit(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldValidityUnit, v))
}
// Features applies equality check predicate on the "features" field. It's identical to FeaturesEQ.
func Features(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldFeatures, v))
}
// ProductName applies equality check predicate on the "product_name" field. It's identical to ProductNameEQ.
func ProductName(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldProductName, v))
}
// ForSale applies equality check predicate on the "for_sale" field. It's identical to ForSaleEQ.
func ForSale(v bool) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldForSale, v))
}
// SortOrder applies equality check predicate on the "sort_order" field. It's identical to SortOrderEQ.
func SortOrder(v int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldSortOrder, v))
}
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
func CreatedAt(v time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldCreatedAt, v))
}
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
func UpdatedAt(v time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldUpdatedAt, v))
}
// GroupIDEQ applies the EQ predicate on the "group_id" field.
func GroupIDEQ(v int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldGroupID, v))
}
// GroupIDNEQ applies the NEQ predicate on the "group_id" field.
func GroupIDNEQ(v int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNEQ(FieldGroupID, v))
}
// GroupIDIn applies the In predicate on the "group_id" field.
func GroupIDIn(vs ...int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldIn(FieldGroupID, vs...))
}
// GroupIDNotIn applies the NotIn predicate on the "group_id" field.
func GroupIDNotIn(vs ...int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNotIn(FieldGroupID, vs...))
}
// GroupIDGT applies the GT predicate on the "group_id" field.
func GroupIDGT(v int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGT(FieldGroupID, v))
}
// GroupIDGTE applies the GTE predicate on the "group_id" field.
func GroupIDGTE(v int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGTE(FieldGroupID, v))
}
// GroupIDLT applies the LT predicate on the "group_id" field.
func GroupIDLT(v int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLT(FieldGroupID, v))
}
// GroupIDLTE applies the LTE predicate on the "group_id" field.
func GroupIDLTE(v int64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLTE(FieldGroupID, v))
}
// NameEQ applies the EQ predicate on the "name" field.
func NameEQ(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldName, v))
}
// NameNEQ applies the NEQ predicate on the "name" field.
func NameNEQ(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNEQ(FieldName, v))
}
// NameIn applies the In predicate on the "name" field.
func NameIn(vs ...string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldIn(FieldName, vs...))
}
// NameNotIn applies the NotIn predicate on the "name" field.
func NameNotIn(vs ...string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNotIn(FieldName, vs...))
}
// NameGT applies the GT predicate on the "name" field.
func NameGT(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGT(FieldName, v))
}
// NameGTE applies the GTE predicate on the "name" field.
func NameGTE(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGTE(FieldName, v))
}
// NameLT applies the LT predicate on the "name" field.
func NameLT(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLT(FieldName, v))
}
// NameLTE applies the LTE predicate on the "name" field.
func NameLTE(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLTE(FieldName, v))
}
// NameContains applies the Contains predicate on the "name" field.
func NameContains(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldContains(FieldName, v))
}
// NameHasPrefix applies the HasPrefix predicate on the "name" field.
func NameHasPrefix(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldHasPrefix(FieldName, v))
}
// NameHasSuffix applies the HasSuffix predicate on the "name" field.
func NameHasSuffix(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldHasSuffix(FieldName, v))
}
// NameEqualFold applies the EqualFold predicate on the "name" field.
func NameEqualFold(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEqualFold(FieldName, v))
}
// NameContainsFold applies the ContainsFold predicate on the "name" field.
func NameContainsFold(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldContainsFold(FieldName, v))
}
// DescriptionEQ applies the EQ predicate on the "description" field.
func DescriptionEQ(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldDescription, v))
}
// DescriptionNEQ applies the NEQ predicate on the "description" field.
func DescriptionNEQ(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNEQ(FieldDescription, v))
}
// DescriptionIn applies the In predicate on the "description" field.
func DescriptionIn(vs ...string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldIn(FieldDescription, vs...))
}
// DescriptionNotIn applies the NotIn predicate on the "description" field.
func DescriptionNotIn(vs ...string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNotIn(FieldDescription, vs...))
}
// DescriptionGT applies the GT predicate on the "description" field.
func DescriptionGT(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGT(FieldDescription, v))
}
// DescriptionGTE applies the GTE predicate on the "description" field.
func DescriptionGTE(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGTE(FieldDescription, v))
}
// DescriptionLT applies the LT predicate on the "description" field.
func DescriptionLT(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLT(FieldDescription, v))
}
// DescriptionLTE applies the LTE predicate on the "description" field.
func DescriptionLTE(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLTE(FieldDescription, v))
}
// DescriptionContains applies the Contains predicate on the "description" field.
func DescriptionContains(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldContains(FieldDescription, v))
}
// DescriptionHasPrefix applies the HasPrefix predicate on the "description" field.
func DescriptionHasPrefix(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldHasPrefix(FieldDescription, v))
}
// DescriptionHasSuffix applies the HasSuffix predicate on the "description" field.
func DescriptionHasSuffix(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldHasSuffix(FieldDescription, v))
}
// DescriptionEqualFold applies the EqualFold predicate on the "description" field.
func DescriptionEqualFold(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEqualFold(FieldDescription, v))
}
// DescriptionContainsFold applies the ContainsFold predicate on the "description" field.
func DescriptionContainsFold(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldContainsFold(FieldDescription, v))
}
// PriceEQ applies the EQ predicate on the "price" field.
func PriceEQ(v float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldPrice, v))
}
// PriceNEQ applies the NEQ predicate on the "price" field.
func PriceNEQ(v float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNEQ(FieldPrice, v))
}
// PriceIn applies the In predicate on the "price" field.
func PriceIn(vs ...float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldIn(FieldPrice, vs...))
}
// PriceNotIn applies the NotIn predicate on the "price" field.
func PriceNotIn(vs ...float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNotIn(FieldPrice, vs...))
}
// PriceGT applies the GT predicate on the "price" field.
func PriceGT(v float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGT(FieldPrice, v))
}
// PriceGTE applies the GTE predicate on the "price" field.
func PriceGTE(v float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGTE(FieldPrice, v))
}
// PriceLT applies the LT predicate on the "price" field.
func PriceLT(v float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLT(FieldPrice, v))
}
// PriceLTE applies the LTE predicate on the "price" field.
func PriceLTE(v float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLTE(FieldPrice, v))
}
// OriginalPriceEQ applies the EQ predicate on the "original_price" field.
func OriginalPriceEQ(v float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldOriginalPrice, v))
}
// OriginalPriceNEQ applies the NEQ predicate on the "original_price" field.
func OriginalPriceNEQ(v float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNEQ(FieldOriginalPrice, v))
}
// OriginalPriceIn applies the In predicate on the "original_price" field.
func OriginalPriceIn(vs ...float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldIn(FieldOriginalPrice, vs...))
}
// OriginalPriceNotIn applies the NotIn predicate on the "original_price" field.
func OriginalPriceNotIn(vs ...float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNotIn(FieldOriginalPrice, vs...))
}
// OriginalPriceGT applies the GT predicate on the "original_price" field.
func OriginalPriceGT(v float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGT(FieldOriginalPrice, v))
}
// OriginalPriceGTE applies the GTE predicate on the "original_price" field.
func OriginalPriceGTE(v float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGTE(FieldOriginalPrice, v))
}
// OriginalPriceLT applies the LT predicate on the "original_price" field.
func OriginalPriceLT(v float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLT(FieldOriginalPrice, v))
}
// OriginalPriceLTE applies the LTE predicate on the "original_price" field.
func OriginalPriceLTE(v float64) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLTE(FieldOriginalPrice, v))
}
// OriginalPriceIsNil applies the IsNil predicate on the "original_price" field.
func OriginalPriceIsNil() predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldIsNull(FieldOriginalPrice))
}
// OriginalPriceNotNil applies the NotNil predicate on the "original_price" field.
func OriginalPriceNotNil() predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNotNull(FieldOriginalPrice))
}
// ValidityDaysEQ applies the EQ predicate on the "validity_days" field.
func ValidityDaysEQ(v int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldValidityDays, v))
}
// ValidityDaysNEQ applies the NEQ predicate on the "validity_days" field.
func ValidityDaysNEQ(v int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNEQ(FieldValidityDays, v))
}
// ValidityDaysIn applies the In predicate on the "validity_days" field.
func ValidityDaysIn(vs ...int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldIn(FieldValidityDays, vs...))
}
// ValidityDaysNotIn applies the NotIn predicate on the "validity_days" field.
func ValidityDaysNotIn(vs ...int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNotIn(FieldValidityDays, vs...))
}
// ValidityDaysGT applies the GT predicate on the "validity_days" field.
func ValidityDaysGT(v int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGT(FieldValidityDays, v))
}
// ValidityDaysGTE applies the GTE predicate on the "validity_days" field.
func ValidityDaysGTE(v int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGTE(FieldValidityDays, v))
}
// ValidityDaysLT applies the LT predicate on the "validity_days" field.
func ValidityDaysLT(v int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLT(FieldValidityDays, v))
}
// ValidityDaysLTE applies the LTE predicate on the "validity_days" field.
func ValidityDaysLTE(v int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLTE(FieldValidityDays, v))
}
// ValidityUnitEQ applies the EQ predicate on the "validity_unit" field.
func ValidityUnitEQ(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldValidityUnit, v))
}
// ValidityUnitNEQ applies the NEQ predicate on the "validity_unit" field.
func ValidityUnitNEQ(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNEQ(FieldValidityUnit, v))
}
// ValidityUnitIn applies the In predicate on the "validity_unit" field.
func ValidityUnitIn(vs ...string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldIn(FieldValidityUnit, vs...))
}
// ValidityUnitNotIn applies the NotIn predicate on the "validity_unit" field.
func ValidityUnitNotIn(vs ...string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNotIn(FieldValidityUnit, vs...))
}
// ValidityUnitGT applies the GT predicate on the "validity_unit" field.
func ValidityUnitGT(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGT(FieldValidityUnit, v))
}
// ValidityUnitGTE applies the GTE predicate on the "validity_unit" field.
func ValidityUnitGTE(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGTE(FieldValidityUnit, v))
}
// ValidityUnitLT applies the LT predicate on the "validity_unit" field.
func ValidityUnitLT(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLT(FieldValidityUnit, v))
}
// ValidityUnitLTE applies the LTE predicate on the "validity_unit" field.
func ValidityUnitLTE(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLTE(FieldValidityUnit, v))
}
// ValidityUnitContains applies the Contains predicate on the "validity_unit" field.
func ValidityUnitContains(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldContains(FieldValidityUnit, v))
}
// ValidityUnitHasPrefix applies the HasPrefix predicate on the "validity_unit" field.
func ValidityUnitHasPrefix(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldHasPrefix(FieldValidityUnit, v))
}
// ValidityUnitHasSuffix applies the HasSuffix predicate on the "validity_unit" field.
func ValidityUnitHasSuffix(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldHasSuffix(FieldValidityUnit, v))
}
// ValidityUnitEqualFold applies the EqualFold predicate on the "validity_unit" field.
func ValidityUnitEqualFold(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEqualFold(FieldValidityUnit, v))
}
// ValidityUnitContainsFold applies the ContainsFold predicate on the "validity_unit" field.
func ValidityUnitContainsFold(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldContainsFold(FieldValidityUnit, v))
}
// FeaturesEQ applies the EQ predicate on the "features" field.
func FeaturesEQ(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldFeatures, v))
}
// FeaturesNEQ applies the NEQ predicate on the "features" field.
func FeaturesNEQ(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNEQ(FieldFeatures, v))
}
// FeaturesIn applies the In predicate on the "features" field.
func FeaturesIn(vs ...string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldIn(FieldFeatures, vs...))
}
// FeaturesNotIn applies the NotIn predicate on the "features" field.
func FeaturesNotIn(vs ...string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNotIn(FieldFeatures, vs...))
}
// FeaturesGT applies the GT predicate on the "features" field.
func FeaturesGT(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGT(FieldFeatures, v))
}
// FeaturesGTE applies the GTE predicate on the "features" field.
func FeaturesGTE(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGTE(FieldFeatures, v))
}
// FeaturesLT applies the LT predicate on the "features" field.
func FeaturesLT(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLT(FieldFeatures, v))
}
// FeaturesLTE applies the LTE predicate on the "features" field.
func FeaturesLTE(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLTE(FieldFeatures, v))
}
// FeaturesContains applies the Contains predicate on the "features" field.
func FeaturesContains(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldContains(FieldFeatures, v))
}
// FeaturesHasPrefix applies the HasPrefix predicate on the "features" field.
func FeaturesHasPrefix(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldHasPrefix(FieldFeatures, v))
}
// FeaturesHasSuffix applies the HasSuffix predicate on the "features" field.
func FeaturesHasSuffix(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldHasSuffix(FieldFeatures, v))
}
// FeaturesEqualFold applies the EqualFold predicate on the "features" field.
func FeaturesEqualFold(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEqualFold(FieldFeatures, v))
}
// FeaturesContainsFold applies the ContainsFold predicate on the "features" field.
func FeaturesContainsFold(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldContainsFold(FieldFeatures, v))
}
// ProductNameEQ applies the EQ predicate on the "product_name" field.
func ProductNameEQ(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldProductName, v))
}
// ProductNameNEQ applies the NEQ predicate on the "product_name" field.
func ProductNameNEQ(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNEQ(FieldProductName, v))
}
// ProductNameIn applies the In predicate on the "product_name" field.
func ProductNameIn(vs ...string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldIn(FieldProductName, vs...))
}
// ProductNameNotIn applies the NotIn predicate on the "product_name" field.
func ProductNameNotIn(vs ...string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNotIn(FieldProductName, vs...))
}
// ProductNameGT applies the GT predicate on the "product_name" field.
func ProductNameGT(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGT(FieldProductName, v))
}
// ProductNameGTE applies the GTE predicate on the "product_name" field.
func ProductNameGTE(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGTE(FieldProductName, v))
}
// ProductNameLT applies the LT predicate on the "product_name" field.
func ProductNameLT(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLT(FieldProductName, v))
}
// ProductNameLTE applies the LTE predicate on the "product_name" field.
func ProductNameLTE(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLTE(FieldProductName, v))
}
// ProductNameContains applies the Contains predicate on the "product_name" field.
func ProductNameContains(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldContains(FieldProductName, v))
}
// ProductNameHasPrefix applies the HasPrefix predicate on the "product_name" field.
func ProductNameHasPrefix(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldHasPrefix(FieldProductName, v))
}
// ProductNameHasSuffix applies the HasSuffix predicate on the "product_name" field.
func ProductNameHasSuffix(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldHasSuffix(FieldProductName, v))
}
// ProductNameEqualFold applies the EqualFold predicate on the "product_name" field.
func ProductNameEqualFold(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEqualFold(FieldProductName, v))
}
// ProductNameContainsFold applies the ContainsFold predicate on the "product_name" field.
func ProductNameContainsFold(v string) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldContainsFold(FieldProductName, v))
}
// ForSaleEQ applies the EQ predicate on the "for_sale" field.
func ForSaleEQ(v bool) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldForSale, v))
}
// ForSaleNEQ applies the NEQ predicate on the "for_sale" field.
func ForSaleNEQ(v bool) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNEQ(FieldForSale, v))
}
// SortOrderEQ applies the EQ predicate on the "sort_order" field.
func SortOrderEQ(v int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldSortOrder, v))
}
// SortOrderNEQ applies the NEQ predicate on the "sort_order" field.
func SortOrderNEQ(v int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNEQ(FieldSortOrder, v))
}
// SortOrderIn applies the In predicate on the "sort_order" field.
func SortOrderIn(vs ...int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldIn(FieldSortOrder, vs...))
}
// SortOrderNotIn applies the NotIn predicate on the "sort_order" field.
func SortOrderNotIn(vs ...int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNotIn(FieldSortOrder, vs...))
}
// SortOrderGT applies the GT predicate on the "sort_order" field.
func SortOrderGT(v int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGT(FieldSortOrder, v))
}
// SortOrderGTE applies the GTE predicate on the "sort_order" field.
func SortOrderGTE(v int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGTE(FieldSortOrder, v))
}
// SortOrderLT applies the LT predicate on the "sort_order" field.
func SortOrderLT(v int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLT(FieldSortOrder, v))
}
// SortOrderLTE applies the LTE predicate on the "sort_order" field.
func SortOrderLTE(v int) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLTE(FieldSortOrder, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLTE(FieldCreatedAt, v))
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldEQ(FieldUpdatedAt, v))
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNEQ(FieldUpdatedAt, v))
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldIn(FieldUpdatedAt, vs...))
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldNotIn(FieldUpdatedAt, vs...))
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGT(FieldUpdatedAt, v))
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldGTE(FieldUpdatedAt, v))
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLT(FieldUpdatedAt, v))
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.FieldLTE(FieldUpdatedAt, v))
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.SubscriptionPlan) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.SubscriptionPlan) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.SubscriptionPlan) predicate.SubscriptionPlan {
return predicate.SubscriptionPlan(sql.NotPredicates(p))
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,88 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/subscriptionplan"
)
// SubscriptionPlanDelete is the builder for deleting a SubscriptionPlan entity.
type SubscriptionPlanDelete struct {
config
hooks []Hook
mutation *SubscriptionPlanMutation
}
// Where appends a list predicates to the SubscriptionPlanDelete builder.
func (_d *SubscriptionPlanDelete) Where(ps ...predicate.SubscriptionPlan) *SubscriptionPlanDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *SubscriptionPlanDelete) Exec(ctx context.Context) (int, error) {
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *SubscriptionPlanDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *SubscriptionPlanDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(subscriptionplan.Table, sqlgraph.NewFieldSpec(subscriptionplan.FieldID, field.TypeInt64))
if ps := _d.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
if err != nil && sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
_d.mutation.done = true
return affected, err
}
// SubscriptionPlanDeleteOne is the builder for deleting a single SubscriptionPlan entity.
type SubscriptionPlanDeleteOne struct {
_d *SubscriptionPlanDelete
}
// Where appends a list predicates to the SubscriptionPlanDelete builder.
func (_d *SubscriptionPlanDeleteOne) Where(ps ...predicate.SubscriptionPlan) *SubscriptionPlanDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *SubscriptionPlanDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{subscriptionplan.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *SubscriptionPlanDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}

View File

@ -0,0 +1,564 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"fmt"
"math"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/subscriptionplan"
)
// SubscriptionPlanQuery is the builder for querying SubscriptionPlan entities.
type SubscriptionPlanQuery struct {
config
ctx *QueryContext
order []subscriptionplan.OrderOption
inters []Interceptor
predicates []predicate.SubscriptionPlan
modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
sql *sql.Selector
path func(context.Context) (*sql.Selector, error)
}
// Where adds a new predicate for the SubscriptionPlanQuery builder.
func (_q *SubscriptionPlanQuery) Where(ps ...predicate.SubscriptionPlan) *SubscriptionPlanQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *SubscriptionPlanQuery) Limit(limit int) *SubscriptionPlanQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *SubscriptionPlanQuery) Offset(offset int) *SubscriptionPlanQuery {
_q.ctx.Offset = &offset
return _q
}
// Unique configures the query builder to filter duplicate records on query.
// By default, unique is set to true, and can be disabled using this method.
func (_q *SubscriptionPlanQuery) Unique(unique bool) *SubscriptionPlanQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *SubscriptionPlanQuery) Order(o ...subscriptionplan.OrderOption) *SubscriptionPlanQuery {
_q.order = append(_q.order, o...)
return _q
}
// First returns the first SubscriptionPlan entity from the query.
// Returns a *NotFoundError when no SubscriptionPlan was found.
func (_q *SubscriptionPlanQuery) First(ctx context.Context) (*SubscriptionPlan, error) {
nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
if err != nil {
return nil, err
}
if len(nodes) == 0 {
return nil, &NotFoundError{subscriptionplan.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *SubscriptionPlanQuery) FirstX(ctx context.Context) *SubscriptionPlan {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first SubscriptionPlan ID from the query.
// Returns a *NotFoundError when no SubscriptionPlan ID was found.
func (_q *SubscriptionPlanQuery) FirstID(ctx context.Context) (id int64, err error) {
var ids []int64
if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
return
}
if len(ids) == 0 {
err = &NotFoundError{subscriptionplan.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *SubscriptionPlanQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single SubscriptionPlan entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one SubscriptionPlan entity is found.
// Returns a *NotFoundError when no SubscriptionPlan entities are found.
func (_q *SubscriptionPlanQuery) Only(ctx context.Context) (*SubscriptionPlan, error) {
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
if err != nil {
return nil, err
}
switch len(nodes) {
case 1:
return nodes[0], nil
case 0:
return nil, &NotFoundError{subscriptionplan.Label}
default:
return nil, &NotSingularError{subscriptionplan.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *SubscriptionPlanQuery) OnlyX(ctx context.Context) *SubscriptionPlan {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only SubscriptionPlan ID in the query.
// Returns a *NotSingularError when more than one SubscriptionPlan ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *SubscriptionPlanQuery) OnlyID(ctx context.Context) (id int64, err error) {
var ids []int64
if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
return
}
switch len(ids) {
case 1:
id = ids[0]
case 0:
err = &NotFoundError{subscriptionplan.Label}
default:
err = &NotSingularError{subscriptionplan.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *SubscriptionPlanQuery) OnlyIDX(ctx context.Context) int64 {
id, err := _q.OnlyID(ctx)
if err != nil {
panic(err)
}
return id
}
// All executes the query and returns a list of SubscriptionPlans.
func (_q *SubscriptionPlanQuery) All(ctx context.Context) ([]*SubscriptionPlan, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*SubscriptionPlan, *SubscriptionPlanQuery]()
return withInterceptors[[]*SubscriptionPlan](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *SubscriptionPlanQuery) AllX(ctx context.Context) []*SubscriptionPlan {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of SubscriptionPlan IDs.
func (_q *SubscriptionPlanQuery) IDs(ctx context.Context) (ids []int64, err error) {
if _q.ctx.Unique == nil && _q.path != nil {
_q.Unique(true)
}
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
if err = _q.Select(subscriptionplan.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *SubscriptionPlanQuery) IDsX(ctx context.Context) []int64 {
ids, err := _q.IDs(ctx)
if err != nil {
panic(err)
}
return ids
}
// Count returns the count of the given query.
func (_q *SubscriptionPlanQuery) Count(ctx context.Context) (int, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
if err := _q.prepareQuery(ctx); err != nil {
return 0, err
}
return withInterceptors[int](ctx, _q, querierCount[*SubscriptionPlanQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *SubscriptionPlanQuery) CountX(ctx context.Context) int {
count, err := _q.Count(ctx)
if err != nil {
panic(err)
}
return count
}
// Exist returns true if the query has elements in the graph.
func (_q *SubscriptionPlanQuery) Exist(ctx context.Context) (bool, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
switch _, err := _q.FirstID(ctx); {
case IsNotFound(err):
return false, nil
case err != nil:
return false, fmt.Errorf("ent: check existence: %w", err)
default:
return true, nil
}
}
// ExistX is like Exist, but panics if an error occurs.
func (_q *SubscriptionPlanQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the SubscriptionPlanQuery builder, including all associated steps. It can be
// used to prepare common query builders and use them differently after the clone is made.
func (_q *SubscriptionPlanQuery) Clone() *SubscriptionPlanQuery {
if _q == nil {
return nil
}
return &SubscriptionPlanQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]subscriptionplan.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.SubscriptionPlan{}, _q.predicates...),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
// Example:
//
// var v []struct {
// GroupID int64 `json:"group_id,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.SubscriptionPlan.Query().
// GroupBy(subscriptionplan.FieldGroupID).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *SubscriptionPlanQuery) GroupBy(field string, fields ...string) *SubscriptionPlanGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &SubscriptionPlanGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = subscriptionplan.Label
grbuild.scan = grbuild.Scan
return grbuild
}
// Select allows the selection one or more fields/columns for the given query,
// instead of selecting all fields in the entity.
//
// Example:
//
// var v []struct {
// GroupID int64 `json:"group_id,omitempty"`
// }
//
// client.SubscriptionPlan.Query().
// Select(subscriptionplan.FieldGroupID).
// Scan(ctx, &v)
func (_q *SubscriptionPlanQuery) Select(fields ...string) *SubscriptionPlanSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &SubscriptionPlanSelect{SubscriptionPlanQuery: _q}
sbuild.label = subscriptionplan.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a SubscriptionPlanSelect configured with the given aggregations.
func (_q *SubscriptionPlanQuery) Aggregate(fns ...AggregateFunc) *SubscriptionPlanSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *SubscriptionPlanQuery) prepareQuery(ctx context.Context) error {
for _, inter := range _q.inters {
if inter == nil {
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
}
if trv, ok := inter.(Traverser); ok {
if err := trv.Traverse(ctx, _q); err != nil {
return err
}
}
}
for _, f := range _q.ctx.Fields {
if !subscriptionplan.ValidColumn(f) {
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
}
if _q.path != nil {
prev, err := _q.path(ctx)
if err != nil {
return err
}
_q.sql = prev
}
return nil
}
func (_q *SubscriptionPlanQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*SubscriptionPlan, error) {
var (
nodes = []*SubscriptionPlan{}
_spec = _q.querySpec()
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*SubscriptionPlan).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &SubscriptionPlan{config: _q.config}
nodes = append(nodes, node)
return node.assignValues(columns, values)
}
if len(_q.modifiers) > 0 {
_spec.Modifiers = _q.modifiers
}
for i := range hooks {
hooks[i](ctx, _spec)
}
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
return nodes, nil
}
func (_q *SubscriptionPlanQuery) sqlCount(ctx context.Context) (int, error) {
_spec := _q.querySpec()
if len(_q.modifiers) > 0 {
_spec.Modifiers = _q.modifiers
}
_spec.Node.Columns = _q.ctx.Fields
if len(_q.ctx.Fields) > 0 {
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
}
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
}
func (_q *SubscriptionPlanQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(subscriptionplan.Table, subscriptionplan.Columns, sqlgraph.NewFieldSpec(subscriptionplan.FieldID, field.TypeInt64))
_spec.From = _q.sql
if unique := _q.ctx.Unique; unique != nil {
_spec.Unique = *unique
} else if _q.path != nil {
_spec.Unique = true
}
if fields := _q.ctx.Fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, subscriptionplan.FieldID)
for i := range fields {
if fields[i] != subscriptionplan.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
}
if ps := _q.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if limit := _q.ctx.Limit; limit != nil {
_spec.Limit = *limit
}
if offset := _q.ctx.Offset; offset != nil {
_spec.Offset = *offset
}
if ps := _q.order; len(ps) > 0 {
_spec.Order = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
return _spec
}
func (_q *SubscriptionPlanQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(subscriptionplan.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = subscriptionplan.Columns
}
selector := builder.Select(t1.Columns(columns...)...).From(t1)
if _q.sql != nil {
selector = _q.sql
selector.Select(selector.Columns(columns...)...)
}
if _q.ctx.Unique != nil && *_q.ctx.Unique {
selector.Distinct()
}
for _, m := range _q.modifiers {
m(selector)
}
for _, p := range _q.predicates {
p(selector)
}
for _, p := range _q.order {
p(selector)
}
if offset := _q.ctx.Offset; offset != nil {
// limit is mandatory for offset clause. We start
// with default value, and override it below if needed.
selector.Offset(*offset).Limit(math.MaxInt32)
}
if limit := _q.ctx.Limit; limit != nil {
selector.Limit(*limit)
}
return selector
}
// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
// updated, deleted or "selected ... for update" by other sessions, until the transaction is
// either committed or rolled-back.
func (_q *SubscriptionPlanQuery) ForUpdate(opts ...sql.LockOption) *SubscriptionPlanQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForUpdate(opts...)
})
return _q
}
// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
// on any rows that are read. Other sessions can read the rows, but cannot modify them
// until your transaction commits.
func (_q *SubscriptionPlanQuery) ForShare(opts ...sql.LockOption) *SubscriptionPlanQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// SubscriptionPlanGroupBy is the group-by builder for SubscriptionPlan entities.
type SubscriptionPlanGroupBy struct {
selector
build *SubscriptionPlanQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *SubscriptionPlanGroupBy) Aggregate(fns ...AggregateFunc) *SubscriptionPlanGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *SubscriptionPlanGroupBy) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
if err := _g.build.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*SubscriptionPlanQuery, *SubscriptionPlanGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *SubscriptionPlanGroupBy) sqlScan(ctx context.Context, root *SubscriptionPlanQuery, v any) error {
selector := root.sqlQuery(ctx).Select()
aggregation := make([]string, 0, len(_g.fns))
for _, fn := range _g.fns {
aggregation = append(aggregation, fn(selector))
}
if len(selector.SelectedColumns()) == 0 {
columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
for _, f := range *_g.flds {
columns = append(columns, selector.C(f))
}
columns = append(columns, aggregation...)
selector.Select(columns...)
}
selector.GroupBy(selector.Columns(*_g.flds...)...)
if err := selector.Err(); err != nil {
return err
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}
// SubscriptionPlanSelect is the builder for selecting fields of SubscriptionPlan entities.
type SubscriptionPlanSelect struct {
*SubscriptionPlanQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *SubscriptionPlanSelect) Aggregate(fns ...AggregateFunc) *SubscriptionPlanSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *SubscriptionPlanSelect) Scan(ctx context.Context, v any) error {
ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
if err := _s.prepareQuery(ctx); err != nil {
return err
}
return scanWithInterceptors[*SubscriptionPlanQuery, *SubscriptionPlanSelect](ctx, _s.SubscriptionPlanQuery, _s, _s.inters, v)
}
func (_s *SubscriptionPlanSelect) sqlScan(ctx context.Context, root *SubscriptionPlanQuery, v any) error {
selector := root.sqlQuery(ctx)
aggregation := make([]string, 0, len(_s.fns))
for _, fn := range _s.fns {
aggregation = append(aggregation, fn(selector))
}
switch n := len(*_s.selector.flds); {
case n == 0 && len(aggregation) > 0:
selector.Select(aggregation...)
case n != 0 && len(aggregation) > 0:
selector.AppendSelect(aggregation...)
}
rows := &sql.Rows{}
query, args := selector.Query()
if err := _s.driver.Query(ctx, query, args, rows); err != nil {
return err
}
defer rows.Close()
return sql.ScanSlice(rows, v)
}

View File

@ -0,0 +1,750 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"errors"
"fmt"
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/subscriptionplan"
)
// SubscriptionPlanUpdate is the builder for updating SubscriptionPlan entities.
type SubscriptionPlanUpdate struct {
config
hooks []Hook
mutation *SubscriptionPlanMutation
}
// Where appends a list predicates to the SubscriptionPlanUpdate builder.
func (_u *SubscriptionPlanUpdate) Where(ps ...predicate.SubscriptionPlan) *SubscriptionPlanUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetGroupID sets the "group_id" field.
func (_u *SubscriptionPlanUpdate) SetGroupID(v int64) *SubscriptionPlanUpdate {
_u.mutation.ResetGroupID()
_u.mutation.SetGroupID(v)
return _u
}
// SetNillableGroupID sets the "group_id" field if the given value is not nil.
func (_u *SubscriptionPlanUpdate) SetNillableGroupID(v *int64) *SubscriptionPlanUpdate {
if v != nil {
_u.SetGroupID(*v)
}
return _u
}
// AddGroupID adds value to the "group_id" field.
func (_u *SubscriptionPlanUpdate) AddGroupID(v int64) *SubscriptionPlanUpdate {
_u.mutation.AddGroupID(v)
return _u
}
// SetName sets the "name" field.
func (_u *SubscriptionPlanUpdate) SetName(v string) *SubscriptionPlanUpdate {
_u.mutation.SetName(v)
return _u
}
// SetNillableName sets the "name" field if the given value is not nil.
func (_u *SubscriptionPlanUpdate) SetNillableName(v *string) *SubscriptionPlanUpdate {
if v != nil {
_u.SetName(*v)
}
return _u
}
// SetDescription sets the "description" field.
func (_u *SubscriptionPlanUpdate) SetDescription(v string) *SubscriptionPlanUpdate {
_u.mutation.SetDescription(v)
return _u
}
// SetNillableDescription sets the "description" field if the given value is not nil.
func (_u *SubscriptionPlanUpdate) SetNillableDescription(v *string) *SubscriptionPlanUpdate {
if v != nil {
_u.SetDescription(*v)
}
return _u
}
// SetPrice sets the "price" field.
func (_u *SubscriptionPlanUpdate) SetPrice(v float64) *SubscriptionPlanUpdate {
_u.mutation.ResetPrice()
_u.mutation.SetPrice(v)
return _u
}
// SetNillablePrice sets the "price" field if the given value is not nil.
func (_u *SubscriptionPlanUpdate) SetNillablePrice(v *float64) *SubscriptionPlanUpdate {
if v != nil {
_u.SetPrice(*v)
}
return _u
}
// AddPrice adds value to the "price" field.
func (_u *SubscriptionPlanUpdate) AddPrice(v float64) *SubscriptionPlanUpdate {
_u.mutation.AddPrice(v)
return _u
}
// SetOriginalPrice sets the "original_price" field.
func (_u *SubscriptionPlanUpdate) SetOriginalPrice(v float64) *SubscriptionPlanUpdate {
_u.mutation.ResetOriginalPrice()
_u.mutation.SetOriginalPrice(v)
return _u
}
// SetNillableOriginalPrice sets the "original_price" field if the given value is not nil.
func (_u *SubscriptionPlanUpdate) SetNillableOriginalPrice(v *float64) *SubscriptionPlanUpdate {
if v != nil {
_u.SetOriginalPrice(*v)
}
return _u
}
// AddOriginalPrice adds value to the "original_price" field.
func (_u *SubscriptionPlanUpdate) AddOriginalPrice(v float64) *SubscriptionPlanUpdate {
_u.mutation.AddOriginalPrice(v)
return _u
}
// ClearOriginalPrice clears the value of the "original_price" field.
func (_u *SubscriptionPlanUpdate) ClearOriginalPrice() *SubscriptionPlanUpdate {
_u.mutation.ClearOriginalPrice()
return _u
}
// SetValidityDays sets the "validity_days" field.
func (_u *SubscriptionPlanUpdate) SetValidityDays(v int) *SubscriptionPlanUpdate {
_u.mutation.ResetValidityDays()
_u.mutation.SetValidityDays(v)
return _u
}
// SetNillableValidityDays sets the "validity_days" field if the given value is not nil.
func (_u *SubscriptionPlanUpdate) SetNillableValidityDays(v *int) *SubscriptionPlanUpdate {
if v != nil {
_u.SetValidityDays(*v)
}
return _u
}
// AddValidityDays adds value to the "validity_days" field.
func (_u *SubscriptionPlanUpdate) AddValidityDays(v int) *SubscriptionPlanUpdate {
_u.mutation.AddValidityDays(v)
return _u
}
// SetValidityUnit sets the "validity_unit" field.
func (_u *SubscriptionPlanUpdate) SetValidityUnit(v string) *SubscriptionPlanUpdate {
_u.mutation.SetValidityUnit(v)
return _u
}
// SetNillableValidityUnit sets the "validity_unit" field if the given value is not nil.
func (_u *SubscriptionPlanUpdate) SetNillableValidityUnit(v *string) *SubscriptionPlanUpdate {
if v != nil {
_u.SetValidityUnit(*v)
}
return _u
}
// SetFeatures sets the "features" field.
func (_u *SubscriptionPlanUpdate) SetFeatures(v string) *SubscriptionPlanUpdate {
_u.mutation.SetFeatures(v)
return _u
}
// SetNillableFeatures sets the "features" field if the given value is not nil.
func (_u *SubscriptionPlanUpdate) SetNillableFeatures(v *string) *SubscriptionPlanUpdate {
if v != nil {
_u.SetFeatures(*v)
}
return _u
}
// SetProductName sets the "product_name" field.
func (_u *SubscriptionPlanUpdate) SetProductName(v string) *SubscriptionPlanUpdate {
_u.mutation.SetProductName(v)
return _u
}
// SetNillableProductName sets the "product_name" field if the given value is not nil.
func (_u *SubscriptionPlanUpdate) SetNillableProductName(v *string) *SubscriptionPlanUpdate {
if v != nil {
_u.SetProductName(*v)
}
return _u
}
// SetForSale sets the "for_sale" field.
func (_u *SubscriptionPlanUpdate) SetForSale(v bool) *SubscriptionPlanUpdate {
_u.mutation.SetForSale(v)
return _u
}
// SetNillableForSale sets the "for_sale" field if the given value is not nil.
func (_u *SubscriptionPlanUpdate) SetNillableForSale(v *bool) *SubscriptionPlanUpdate {
if v != nil {
_u.SetForSale(*v)
}
return _u
}
// SetSortOrder sets the "sort_order" field.
func (_u *SubscriptionPlanUpdate) SetSortOrder(v int) *SubscriptionPlanUpdate {
_u.mutation.ResetSortOrder()
_u.mutation.SetSortOrder(v)
return _u
}
// SetNillableSortOrder sets the "sort_order" field if the given value is not nil.
func (_u *SubscriptionPlanUpdate) SetNillableSortOrder(v *int) *SubscriptionPlanUpdate {
if v != nil {
_u.SetSortOrder(*v)
}
return _u
}
// AddSortOrder adds value to the "sort_order" field.
func (_u *SubscriptionPlanUpdate) AddSortOrder(v int) *SubscriptionPlanUpdate {
_u.mutation.AddSortOrder(v)
return _u
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *SubscriptionPlanUpdate) SetUpdatedAt(v time.Time) *SubscriptionPlanUpdate {
_u.mutation.SetUpdatedAt(v)
return _u
}
// Mutation returns the SubscriptionPlanMutation object of the builder.
func (_u *SubscriptionPlanUpdate) Mutation() *SubscriptionPlanMutation {
return _u.mutation
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *SubscriptionPlanUpdate) Save(ctx context.Context) (int, error) {
_u.defaults()
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *SubscriptionPlanUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *SubscriptionPlanUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *SubscriptionPlanUpdate) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (_u *SubscriptionPlanUpdate) defaults() {
if _, ok := _u.mutation.UpdatedAt(); !ok {
v := subscriptionplan.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *SubscriptionPlanUpdate) check() error {
if v, ok := _u.mutation.Name(); ok {
if err := subscriptionplan.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.name": %w`, err)}
}
}
if v, ok := _u.mutation.ValidityUnit(); ok {
if err := subscriptionplan.ValidityUnitValidator(v); err != nil {
return &ValidationError{Name: "validity_unit", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.validity_unit": %w`, err)}
}
}
if v, ok := _u.mutation.ProductName(); ok {
if err := subscriptionplan.ProductNameValidator(v); err != nil {
return &ValidationError{Name: "product_name", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.product_name": %w`, err)}
}
}
return nil
}
func (_u *SubscriptionPlanUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(subscriptionplan.Table, subscriptionplan.Columns, sqlgraph.NewFieldSpec(subscriptionplan.FieldID, field.TypeInt64))
if ps := _u.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := _u.mutation.GroupID(); ok {
_spec.SetField(subscriptionplan.FieldGroupID, field.TypeInt64, value)
}
if value, ok := _u.mutation.AddedGroupID(); ok {
_spec.AddField(subscriptionplan.FieldGroupID, field.TypeInt64, value)
}
if value, ok := _u.mutation.Name(); ok {
_spec.SetField(subscriptionplan.FieldName, field.TypeString, value)
}
if value, ok := _u.mutation.Description(); ok {
_spec.SetField(subscriptionplan.FieldDescription, field.TypeString, value)
}
if value, ok := _u.mutation.Price(); ok {
_spec.SetField(subscriptionplan.FieldPrice, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedPrice(); ok {
_spec.AddField(subscriptionplan.FieldPrice, field.TypeFloat64, value)
}
if value, ok := _u.mutation.OriginalPrice(); ok {
_spec.SetField(subscriptionplan.FieldOriginalPrice, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedOriginalPrice(); ok {
_spec.AddField(subscriptionplan.FieldOriginalPrice, field.TypeFloat64, value)
}
if _u.mutation.OriginalPriceCleared() {
_spec.ClearField(subscriptionplan.FieldOriginalPrice, field.TypeFloat64)
}
if value, ok := _u.mutation.ValidityDays(); ok {
_spec.SetField(subscriptionplan.FieldValidityDays, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedValidityDays(); ok {
_spec.AddField(subscriptionplan.FieldValidityDays, field.TypeInt, value)
}
if value, ok := _u.mutation.ValidityUnit(); ok {
_spec.SetField(subscriptionplan.FieldValidityUnit, field.TypeString, value)
}
if value, ok := _u.mutation.Features(); ok {
_spec.SetField(subscriptionplan.FieldFeatures, field.TypeString, value)
}
if value, ok := _u.mutation.ProductName(); ok {
_spec.SetField(subscriptionplan.FieldProductName, field.TypeString, value)
}
if value, ok := _u.mutation.ForSale(); ok {
_spec.SetField(subscriptionplan.FieldForSale, field.TypeBool, value)
}
if value, ok := _u.mutation.SortOrder(); ok {
_spec.SetField(subscriptionplan.FieldSortOrder, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedSortOrder(); ok {
_spec.AddField(subscriptionplan.FieldSortOrder, field.TypeInt, value)
}
if value, ok := _u.mutation.UpdatedAt(); ok {
_spec.SetField(subscriptionplan.FieldUpdatedAt, field.TypeTime, value)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{subscriptionplan.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// SubscriptionPlanUpdateOne is the builder for updating a single SubscriptionPlan entity.
type SubscriptionPlanUpdateOne struct {
config
fields []string
hooks []Hook
mutation *SubscriptionPlanMutation
}
// SetGroupID sets the "group_id" field.
func (_u *SubscriptionPlanUpdateOne) SetGroupID(v int64) *SubscriptionPlanUpdateOne {
_u.mutation.ResetGroupID()
_u.mutation.SetGroupID(v)
return _u
}
// SetNillableGroupID sets the "group_id" field if the given value is not nil.
func (_u *SubscriptionPlanUpdateOne) SetNillableGroupID(v *int64) *SubscriptionPlanUpdateOne {
if v != nil {
_u.SetGroupID(*v)
}
return _u
}
// AddGroupID adds value to the "group_id" field.
func (_u *SubscriptionPlanUpdateOne) AddGroupID(v int64) *SubscriptionPlanUpdateOne {
_u.mutation.AddGroupID(v)
return _u
}
// SetName sets the "name" field.
func (_u *SubscriptionPlanUpdateOne) SetName(v string) *SubscriptionPlanUpdateOne {
_u.mutation.SetName(v)
return _u
}
// SetNillableName sets the "name" field if the given value is not nil.
func (_u *SubscriptionPlanUpdateOne) SetNillableName(v *string) *SubscriptionPlanUpdateOne {
if v != nil {
_u.SetName(*v)
}
return _u
}
// SetDescription sets the "description" field.
func (_u *SubscriptionPlanUpdateOne) SetDescription(v string) *SubscriptionPlanUpdateOne {
_u.mutation.SetDescription(v)
return _u
}
// SetNillableDescription sets the "description" field if the given value is not nil.
func (_u *SubscriptionPlanUpdateOne) SetNillableDescription(v *string) *SubscriptionPlanUpdateOne {
if v != nil {
_u.SetDescription(*v)
}
return _u
}
// SetPrice sets the "price" field.
func (_u *SubscriptionPlanUpdateOne) SetPrice(v float64) *SubscriptionPlanUpdateOne {
_u.mutation.ResetPrice()
_u.mutation.SetPrice(v)
return _u
}
// SetNillablePrice sets the "price" field if the given value is not nil.
func (_u *SubscriptionPlanUpdateOne) SetNillablePrice(v *float64) *SubscriptionPlanUpdateOne {
if v != nil {
_u.SetPrice(*v)
}
return _u
}
// AddPrice adds value to the "price" field.
func (_u *SubscriptionPlanUpdateOne) AddPrice(v float64) *SubscriptionPlanUpdateOne {
_u.mutation.AddPrice(v)
return _u
}
// SetOriginalPrice sets the "original_price" field.
func (_u *SubscriptionPlanUpdateOne) SetOriginalPrice(v float64) *SubscriptionPlanUpdateOne {
_u.mutation.ResetOriginalPrice()
_u.mutation.SetOriginalPrice(v)
return _u
}
// SetNillableOriginalPrice sets the "original_price" field if the given value is not nil.
func (_u *SubscriptionPlanUpdateOne) SetNillableOriginalPrice(v *float64) *SubscriptionPlanUpdateOne {
if v != nil {
_u.SetOriginalPrice(*v)
}
return _u
}
// AddOriginalPrice adds value to the "original_price" field.
func (_u *SubscriptionPlanUpdateOne) AddOriginalPrice(v float64) *SubscriptionPlanUpdateOne {
_u.mutation.AddOriginalPrice(v)
return _u
}
// ClearOriginalPrice clears the value of the "original_price" field.
func (_u *SubscriptionPlanUpdateOne) ClearOriginalPrice() *SubscriptionPlanUpdateOne {
_u.mutation.ClearOriginalPrice()
return _u
}
// SetValidityDays sets the "validity_days" field.
func (_u *SubscriptionPlanUpdateOne) SetValidityDays(v int) *SubscriptionPlanUpdateOne {
_u.mutation.ResetValidityDays()
_u.mutation.SetValidityDays(v)
return _u
}
// SetNillableValidityDays sets the "validity_days" field if the given value is not nil.
func (_u *SubscriptionPlanUpdateOne) SetNillableValidityDays(v *int) *SubscriptionPlanUpdateOne {
if v != nil {
_u.SetValidityDays(*v)
}
return _u
}
// AddValidityDays adds value to the "validity_days" field.
func (_u *SubscriptionPlanUpdateOne) AddValidityDays(v int) *SubscriptionPlanUpdateOne {
_u.mutation.AddValidityDays(v)
return _u
}
// SetValidityUnit sets the "validity_unit" field.
func (_u *SubscriptionPlanUpdateOne) SetValidityUnit(v string) *SubscriptionPlanUpdateOne {
_u.mutation.SetValidityUnit(v)
return _u
}
// SetNillableValidityUnit sets the "validity_unit" field if the given value is not nil.
func (_u *SubscriptionPlanUpdateOne) SetNillableValidityUnit(v *string) *SubscriptionPlanUpdateOne {
if v != nil {
_u.SetValidityUnit(*v)
}
return _u
}
// SetFeatures sets the "features" field.
func (_u *SubscriptionPlanUpdateOne) SetFeatures(v string) *SubscriptionPlanUpdateOne {
_u.mutation.SetFeatures(v)
return _u
}
// SetNillableFeatures sets the "features" field if the given value is not nil.
func (_u *SubscriptionPlanUpdateOne) SetNillableFeatures(v *string) *SubscriptionPlanUpdateOne {
if v != nil {
_u.SetFeatures(*v)
}
return _u
}
// SetProductName sets the "product_name" field.
func (_u *SubscriptionPlanUpdateOne) SetProductName(v string) *SubscriptionPlanUpdateOne {
_u.mutation.SetProductName(v)
return _u
}
// SetNillableProductName sets the "product_name" field if the given value is not nil.
func (_u *SubscriptionPlanUpdateOne) SetNillableProductName(v *string) *SubscriptionPlanUpdateOne {
if v != nil {
_u.SetProductName(*v)
}
return _u
}
// SetForSale sets the "for_sale" field.
func (_u *SubscriptionPlanUpdateOne) SetForSale(v bool) *SubscriptionPlanUpdateOne {
_u.mutation.SetForSale(v)
return _u
}
// SetNillableForSale sets the "for_sale" field if the given value is not nil.
func (_u *SubscriptionPlanUpdateOne) SetNillableForSale(v *bool) *SubscriptionPlanUpdateOne {
if v != nil {
_u.SetForSale(*v)
}
return _u
}
// SetSortOrder sets the "sort_order" field.
func (_u *SubscriptionPlanUpdateOne) SetSortOrder(v int) *SubscriptionPlanUpdateOne {
_u.mutation.ResetSortOrder()
_u.mutation.SetSortOrder(v)
return _u
}
// SetNillableSortOrder sets the "sort_order" field if the given value is not nil.
func (_u *SubscriptionPlanUpdateOne) SetNillableSortOrder(v *int) *SubscriptionPlanUpdateOne {
if v != nil {
_u.SetSortOrder(*v)
}
return _u
}
// AddSortOrder adds value to the "sort_order" field.
func (_u *SubscriptionPlanUpdateOne) AddSortOrder(v int) *SubscriptionPlanUpdateOne {
_u.mutation.AddSortOrder(v)
return _u
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *SubscriptionPlanUpdateOne) SetUpdatedAt(v time.Time) *SubscriptionPlanUpdateOne {
_u.mutation.SetUpdatedAt(v)
return _u
}
// Mutation returns the SubscriptionPlanMutation object of the builder.
func (_u *SubscriptionPlanUpdateOne) Mutation() *SubscriptionPlanMutation {
return _u.mutation
}
// Where appends a list predicates to the SubscriptionPlanUpdate builder.
func (_u *SubscriptionPlanUpdateOne) Where(ps ...predicate.SubscriptionPlan) *SubscriptionPlanUpdateOne {
_u.mutation.Where(ps...)
return _u
}
// Select allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
func (_u *SubscriptionPlanUpdateOne) Select(field string, fields ...string) *SubscriptionPlanUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated SubscriptionPlan entity.
func (_u *SubscriptionPlanUpdateOne) Save(ctx context.Context) (*SubscriptionPlan, error) {
_u.defaults()
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *SubscriptionPlanUpdateOne) SaveX(ctx context.Context) *SubscriptionPlan {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *SubscriptionPlanUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *SubscriptionPlanUpdateOne) ExecX(ctx context.Context) {
if err := _u.Exec(ctx); err != nil {
panic(err)
}
}
// defaults sets the default values of the builder before save.
func (_u *SubscriptionPlanUpdateOne) defaults() {
if _, ok := _u.mutation.UpdatedAt(); !ok {
v := subscriptionplan.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
}
// check runs all checks and user-defined validators on the builder.
func (_u *SubscriptionPlanUpdateOne) check() error {
if v, ok := _u.mutation.Name(); ok {
if err := subscriptionplan.NameValidator(v); err != nil {
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.name": %w`, err)}
}
}
if v, ok := _u.mutation.ValidityUnit(); ok {
if err := subscriptionplan.ValidityUnitValidator(v); err != nil {
return &ValidationError{Name: "validity_unit", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.validity_unit": %w`, err)}
}
}
if v, ok := _u.mutation.ProductName(); ok {
if err := subscriptionplan.ProductNameValidator(v); err != nil {
return &ValidationError{Name: "product_name", err: fmt.Errorf(`ent: validator failed for field "SubscriptionPlan.product_name": %w`, err)}
}
}
return nil
}
func (_u *SubscriptionPlanUpdateOne) sqlSave(ctx context.Context) (_node *SubscriptionPlan, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(subscriptionplan.Table, subscriptionplan.Columns, sqlgraph.NewFieldSpec(subscriptionplan.FieldID, field.TypeInt64))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "SubscriptionPlan.id" for update`)}
}
_spec.Node.ID.Value = id
if fields := _u.fields; len(fields) > 0 {
_spec.Node.Columns = make([]string, 0, len(fields))
_spec.Node.Columns = append(_spec.Node.Columns, subscriptionplan.FieldID)
for _, f := range fields {
if !subscriptionplan.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != subscriptionplan.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, f)
}
}
}
if ps := _u.mutation.predicates; len(ps) > 0 {
_spec.Predicate = func(selector *sql.Selector) {
for i := range ps {
ps[i](selector)
}
}
}
if value, ok := _u.mutation.GroupID(); ok {
_spec.SetField(subscriptionplan.FieldGroupID, field.TypeInt64, value)
}
if value, ok := _u.mutation.AddedGroupID(); ok {
_spec.AddField(subscriptionplan.FieldGroupID, field.TypeInt64, value)
}
if value, ok := _u.mutation.Name(); ok {
_spec.SetField(subscriptionplan.FieldName, field.TypeString, value)
}
if value, ok := _u.mutation.Description(); ok {
_spec.SetField(subscriptionplan.FieldDescription, field.TypeString, value)
}
if value, ok := _u.mutation.Price(); ok {
_spec.SetField(subscriptionplan.FieldPrice, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedPrice(); ok {
_spec.AddField(subscriptionplan.FieldPrice, field.TypeFloat64, value)
}
if value, ok := _u.mutation.OriginalPrice(); ok {
_spec.SetField(subscriptionplan.FieldOriginalPrice, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedOriginalPrice(); ok {
_spec.AddField(subscriptionplan.FieldOriginalPrice, field.TypeFloat64, value)
}
if _u.mutation.OriginalPriceCleared() {
_spec.ClearField(subscriptionplan.FieldOriginalPrice, field.TypeFloat64)
}
if value, ok := _u.mutation.ValidityDays(); ok {
_spec.SetField(subscriptionplan.FieldValidityDays, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedValidityDays(); ok {
_spec.AddField(subscriptionplan.FieldValidityDays, field.TypeInt, value)
}
if value, ok := _u.mutation.ValidityUnit(); ok {
_spec.SetField(subscriptionplan.FieldValidityUnit, field.TypeString, value)
}
if value, ok := _u.mutation.Features(); ok {
_spec.SetField(subscriptionplan.FieldFeatures, field.TypeString, value)
}
if value, ok := _u.mutation.ProductName(); ok {
_spec.SetField(subscriptionplan.FieldProductName, field.TypeString, value)
}
if value, ok := _u.mutation.ForSale(); ok {
_spec.SetField(subscriptionplan.FieldForSale, field.TypeBool, value)
}
if value, ok := _u.mutation.SortOrder(); ok {
_spec.SetField(subscriptionplan.FieldSortOrder, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedSortOrder(); ok {
_spec.AddField(subscriptionplan.FieldSortOrder, field.TypeInt, value)
}
if value, ok := _u.mutation.UpdatedAt(); ok {
_spec.SetField(subscriptionplan.FieldUpdatedAt, field.TypeTime, value)
}
_node = &SubscriptionPlan{config: _u.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues
if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{subscriptionplan.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
_u.mutation.done = true
return _node, nil
}

View File

@ -30,6 +30,12 @@ type Tx struct {
Group *GroupClient
// IdempotencyRecord is the client for interacting with the IdempotencyRecord builders.
IdempotencyRecord *IdempotencyRecordClient
// PaymentAuditLog is the client for interacting with the PaymentAuditLog builders.
PaymentAuditLog *PaymentAuditLogClient
// PaymentOrder is the client for interacting with the PaymentOrder builders.
PaymentOrder *PaymentOrderClient
// PaymentProviderInstance is the client for interacting with the PaymentProviderInstance builders.
PaymentProviderInstance *PaymentProviderInstanceClient
// PromoCode is the client for interacting with the PromoCode builders.
PromoCode *PromoCodeClient
// PromoCodeUsage is the client for interacting with the PromoCodeUsage builders.
@ -42,6 +48,8 @@ type Tx struct {
SecuritySecret *SecuritySecretClient
// Setting is the client for interacting with the Setting builders.
Setting *SettingClient
// SubscriptionPlan is the client for interacting with the SubscriptionPlan builders.
SubscriptionPlan *SubscriptionPlanClient
// TLSFingerprintProfile is the client for interacting with the TLSFingerprintProfile builders.
TLSFingerprintProfile *TLSFingerprintProfileClient
// UsageCleanupTask is the client for interacting with the UsageCleanupTask builders.
@ -197,12 +205,16 @@ func (tx *Tx) init() {
tx.ErrorPassthroughRule = NewErrorPassthroughRuleClient(tx.config)
tx.Group = NewGroupClient(tx.config)
tx.IdempotencyRecord = NewIdempotencyRecordClient(tx.config)
tx.PaymentAuditLog = NewPaymentAuditLogClient(tx.config)
tx.PaymentOrder = NewPaymentOrderClient(tx.config)
tx.PaymentProviderInstance = NewPaymentProviderInstanceClient(tx.config)
tx.PromoCode = NewPromoCodeClient(tx.config)
tx.PromoCodeUsage = NewPromoCodeUsageClient(tx.config)
tx.Proxy = NewProxyClient(tx.config)
tx.RedeemCode = NewRedeemCodeClient(tx.config)
tx.SecuritySecret = NewSecuritySecretClient(tx.config)
tx.Setting = NewSettingClient(tx.config)
tx.SubscriptionPlan = NewSubscriptionPlanClient(tx.config)
tx.TLSFingerprintProfile = NewTLSFingerprintProfileClient(tx.config)
tx.UsageCleanupTask = NewUsageCleanupTaskClient(tx.config)
tx.UsageLog = NewUsageLogClient(tx.config)

View File

@ -71,11 +71,13 @@ type UserEdges struct {
AttributeValues []*UserAttributeValue `json:"attribute_values,omitempty"`
// PromoCodeUsages holds the value of the promo_code_usages edge.
PromoCodeUsages []*PromoCodeUsage `json:"promo_code_usages,omitempty"`
// PaymentOrders holds the value of the payment_orders edge.
PaymentOrders []*PaymentOrder `json:"payment_orders,omitempty"`
// UserAllowedGroups holds the value of the user_allowed_groups edge.
UserAllowedGroups []*UserAllowedGroup `json:"user_allowed_groups,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [10]bool
loadedTypes [11]bool
}
// APIKeysOrErr returns the APIKeys value or an error if the edge
@ -159,10 +161,19 @@ func (e UserEdges) PromoCodeUsagesOrErr() ([]*PromoCodeUsage, error) {
return nil, &NotLoadedError{edge: "promo_code_usages"}
}
// PaymentOrdersOrErr returns the PaymentOrders value or an error if the edge
// was not loaded in eager-loading.
func (e UserEdges) PaymentOrdersOrErr() ([]*PaymentOrder, error) {
if e.loadedTypes[9] {
return e.PaymentOrders, nil
}
return nil, &NotLoadedError{edge: "payment_orders"}
}
// UserAllowedGroupsOrErr returns the UserAllowedGroups value or an error if the edge
// was not loaded in eager-loading.
func (e UserEdges) UserAllowedGroupsOrErr() ([]*UserAllowedGroup, error) {
if e.loadedTypes[9] {
if e.loadedTypes[10] {
return e.UserAllowedGroups, nil
}
return nil, &NotLoadedError{edge: "user_allowed_groups"}
@ -349,6 +360,11 @@ func (_m *User) QueryPromoCodeUsages() *PromoCodeUsageQuery {
return NewUserClient(_m.config).QueryPromoCodeUsages(_m)
}
// QueryPaymentOrders queries the "payment_orders" edge of the User entity.
func (_m *User) QueryPaymentOrders() *PaymentOrderQuery {
return NewUserClient(_m.config).QueryPaymentOrders(_m)
}
// QueryUserAllowedGroups queries the "user_allowed_groups" edge of the User entity.
func (_m *User) QueryUserAllowedGroups() *UserAllowedGroupQuery {
return NewUserClient(_m.config).QueryUserAllowedGroups(_m)

View File

@ -61,6 +61,8 @@ const (
EdgeAttributeValues = "attribute_values"
// EdgePromoCodeUsages holds the string denoting the promo_code_usages edge name in mutations.
EdgePromoCodeUsages = "promo_code_usages"
// EdgePaymentOrders holds the string denoting the payment_orders edge name in mutations.
EdgePaymentOrders = "payment_orders"
// EdgeUserAllowedGroups holds the string denoting the user_allowed_groups edge name in mutations.
EdgeUserAllowedGroups = "user_allowed_groups"
// Table holds the table name of the user in the database.
@ -126,6 +128,13 @@ const (
PromoCodeUsagesInverseTable = "promo_code_usages"
// PromoCodeUsagesColumn is the table column denoting the promo_code_usages relation/edge.
PromoCodeUsagesColumn = "user_id"
// PaymentOrdersTable is the table that holds the payment_orders relation/edge.
PaymentOrdersTable = "payment_orders"
// PaymentOrdersInverseTable is the table name for the PaymentOrder entity.
// It exists in this package in order to avoid circular dependency with the "paymentorder" package.
PaymentOrdersInverseTable = "payment_orders"
// PaymentOrdersColumn is the table column denoting the payment_orders relation/edge.
PaymentOrdersColumn = "user_id"
// UserAllowedGroupsTable is the table that holds the user_allowed_groups relation/edge.
UserAllowedGroupsTable = "user_allowed_groups"
// UserAllowedGroupsInverseTable is the table name for the UserAllowedGroup entity.
@ -414,6 +423,20 @@ func ByPromoCodeUsages(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
}
}
// ByPaymentOrdersCount orders the results by payment_orders count.
func ByPaymentOrdersCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborsCount(s, newPaymentOrdersStep(), opts...)
}
}
// ByPaymentOrders orders the results by payment_orders terms.
func ByPaymentOrders(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newPaymentOrdersStep(), append([]sql.OrderTerm{term}, terms...)...)
}
}
// ByUserAllowedGroupsCount orders the results by user_allowed_groups count.
func ByUserAllowedGroupsCount(opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
@ -490,6 +513,13 @@ func newPromoCodeUsagesStep() *sqlgraph.Step {
sqlgraph.Edge(sqlgraph.O2M, false, PromoCodeUsagesTable, PromoCodeUsagesColumn),
)
}
func newPaymentOrdersStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(PaymentOrdersInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, PaymentOrdersTable, PaymentOrdersColumn),
)
}
func newUserAllowedGroupsStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),

View File

@ -1067,6 +1067,29 @@ func HasPromoCodeUsagesWith(preds ...predicate.PromoCodeUsage) predicate.User {
})
}
// HasPaymentOrders applies the HasEdge predicate on the "payment_orders" edge.
func HasPaymentOrders() predicate.User {
return predicate.User(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, PaymentOrdersTable, PaymentOrdersColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasPaymentOrdersWith applies the HasEdge predicate on the "payment_orders" edge with a given conditions (other predicates).
func HasPaymentOrdersWith(preds ...predicate.PaymentOrder) predicate.User {
return predicate.User(func(s *sql.Selector) {
step := newPaymentOrdersStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// HasUserAllowedGroups applies the HasEdge predicate on the "user_allowed_groups" edge.
func HasUserAllowedGroups() predicate.User {
return predicate.User(func(s *sql.Selector) {

View File

@ -14,6 +14,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/announcementread"
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
"github.com/Wei-Shaw/sub2api/ent/usagelog"
@ -345,6 +346,21 @@ func (_c *UserCreate) AddPromoCodeUsages(v ...*PromoCodeUsage) *UserCreate {
return _c.AddPromoCodeUsageIDs(ids...)
}
// AddPaymentOrderIDs adds the "payment_orders" edge to the PaymentOrder entity by IDs.
func (_c *UserCreate) AddPaymentOrderIDs(ids ...int64) *UserCreate {
_c.mutation.AddPaymentOrderIDs(ids...)
return _c
}
// AddPaymentOrders adds the "payment_orders" edges to the PaymentOrder entity.
func (_c *UserCreate) AddPaymentOrders(v ...*PaymentOrder) *UserCreate {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _c.AddPaymentOrderIDs(ids...)
}
// Mutation returns the UserMutation object of the builder.
func (_c *UserCreate) Mutation() *UserMutation {
return _c.mutation
@ -718,6 +734,22 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) {
}
_spec.Edges = append(_spec.Edges, edge)
}
if nodes := _c.mutation.PaymentOrdersIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.PaymentOrdersTable,
Columns: []string{user.PaymentOrdersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(paymentorder.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges = append(_spec.Edges, edge)
}
return _node, _spec
}

View File

@ -16,6 +16,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/announcementread"
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
@ -42,6 +43,7 @@ type UserQuery struct {
withUsageLogs *UsageLogQuery
withAttributeValues *UserAttributeValueQuery
withPromoCodeUsages *PromoCodeUsageQuery
withPaymentOrders *PaymentOrderQuery
withUserAllowedGroups *UserAllowedGroupQuery
modifiers []func(*sql.Selector)
// intermediate query (i.e. traversal path).
@ -278,6 +280,28 @@ func (_q *UserQuery) QueryPromoCodeUsages() *PromoCodeUsageQuery {
return query
}
// QueryPaymentOrders chains the current query on the "payment_orders" edge.
func (_q *UserQuery) QueryPaymentOrders() *PaymentOrderQuery {
query := (&PaymentOrderClient{config: _q.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
selector := _q.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(user.Table, user.FieldID, selector),
sqlgraph.To(paymentorder.Table, paymentorder.FieldID),
sqlgraph.Edge(sqlgraph.O2M, false, user.PaymentOrdersTable, user.PaymentOrdersColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// QueryUserAllowedGroups chains the current query on the "user_allowed_groups" edge.
func (_q *UserQuery) QueryUserAllowedGroups() *UserAllowedGroupQuery {
query := (&UserAllowedGroupClient{config: _q.config}).Query()
@ -501,6 +525,7 @@ func (_q *UserQuery) Clone() *UserQuery {
withUsageLogs: _q.withUsageLogs.Clone(),
withAttributeValues: _q.withAttributeValues.Clone(),
withPromoCodeUsages: _q.withPromoCodeUsages.Clone(),
withPaymentOrders: _q.withPaymentOrders.Clone(),
withUserAllowedGroups: _q.withUserAllowedGroups.Clone(),
// clone intermediate query.
sql: _q.sql.Clone(),
@ -607,6 +632,17 @@ func (_q *UserQuery) WithPromoCodeUsages(opts ...func(*PromoCodeUsageQuery)) *Us
return _q
}
// WithPaymentOrders tells the query-builder to eager-load the nodes that are connected to
// the "payment_orders" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *UserQuery) WithPaymentOrders(opts ...func(*PaymentOrderQuery)) *UserQuery {
query := (&PaymentOrderClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withPaymentOrders = query
return _q
}
// WithUserAllowedGroups tells the query-builder to eager-load the nodes that are connected to
// the "user_allowed_groups" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *UserQuery) WithUserAllowedGroups(opts ...func(*UserAllowedGroupQuery)) *UserQuery {
@ -696,7 +732,7 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
var (
nodes = []*User{}
_spec = _q.querySpec()
loadedTypes = [10]bool{
loadedTypes = [11]bool{
_q.withAPIKeys != nil,
_q.withRedeemCodes != nil,
_q.withSubscriptions != nil,
@ -706,6 +742,7 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
_q.withUsageLogs != nil,
_q.withAttributeValues != nil,
_q.withPromoCodeUsages != nil,
_q.withPaymentOrders != nil,
_q.withUserAllowedGroups != nil,
}
)
@ -795,6 +832,13 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
return nil, err
}
}
if query := _q.withPaymentOrders; query != nil {
if err := _q.loadPaymentOrders(ctx, query, nodes,
func(n *User) { n.Edges.PaymentOrders = []*PaymentOrder{} },
func(n *User, e *PaymentOrder) { n.Edges.PaymentOrders = append(n.Edges.PaymentOrders, e) }); err != nil {
return nil, err
}
}
if query := _q.withUserAllowedGroups; query != nil {
if err := _q.loadUserAllowedGroups(ctx, query, nodes,
func(n *User) { n.Edges.UserAllowedGroups = []*UserAllowedGroup{} },
@ -1112,6 +1156,36 @@ func (_q *UserQuery) loadPromoCodeUsages(ctx context.Context, query *PromoCodeUs
}
return nil
}
func (_q *UserQuery) loadPaymentOrders(ctx context.Context, query *PaymentOrderQuery, nodes []*User, init func(*User), assign func(*User, *PaymentOrder)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int64]*User)
for i := range nodes {
fks = append(fks, nodes[i].ID)
nodeids[nodes[i].ID] = nodes[i]
if init != nil {
init(nodes[i])
}
}
if len(query.ctx.Fields) > 0 {
query.ctx.AppendFieldOnce(paymentorder.FieldUserID)
}
query.Where(predicate.PaymentOrder(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(user.PaymentOrdersColumn), fks...))
}))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
fk := n.UserID
node, ok := nodeids[fk]
if !ok {
return fmt.Errorf(`unexpected referenced foreign-key "user_id" returned %v for node %v`, fk, n.ID)
}
assign(node, n)
}
return nil
}
func (_q *UserQuery) loadUserAllowedGroups(ctx context.Context, query *UserAllowedGroupQuery, nodes []*User, init func(*User), assign func(*User, *UserAllowedGroup)) error {
fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int64]*User)

View File

@ -14,6 +14,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/announcementread"
"github.com/Wei-Shaw/sub2api/ent/apikey"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/predicate"
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
@ -377,6 +378,21 @@ func (_u *UserUpdate) AddPromoCodeUsages(v ...*PromoCodeUsage) *UserUpdate {
return _u.AddPromoCodeUsageIDs(ids...)
}
// AddPaymentOrderIDs adds the "payment_orders" edge to the PaymentOrder entity by IDs.
func (_u *UserUpdate) AddPaymentOrderIDs(ids ...int64) *UserUpdate {
_u.mutation.AddPaymentOrderIDs(ids...)
return _u
}
// AddPaymentOrders adds the "payment_orders" edges to the PaymentOrder entity.
func (_u *UserUpdate) AddPaymentOrders(v ...*PaymentOrder) *UserUpdate {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddPaymentOrderIDs(ids...)
}
// Mutation returns the UserMutation object of the builder.
func (_u *UserUpdate) Mutation() *UserMutation {
return _u.mutation
@ -571,6 +587,27 @@ func (_u *UserUpdate) RemovePromoCodeUsages(v ...*PromoCodeUsage) *UserUpdate {
return _u.RemovePromoCodeUsageIDs(ids...)
}
// ClearPaymentOrders clears all "payment_orders" edges to the PaymentOrder entity.
func (_u *UserUpdate) ClearPaymentOrders() *UserUpdate {
_u.mutation.ClearPaymentOrders()
return _u
}
// RemovePaymentOrderIDs removes the "payment_orders" edge to PaymentOrder entities by IDs.
func (_u *UserUpdate) RemovePaymentOrderIDs(ids ...int64) *UserUpdate {
_u.mutation.RemovePaymentOrderIDs(ids...)
return _u
}
// RemovePaymentOrders removes "payment_orders" edges to PaymentOrder entities.
func (_u *UserUpdate) RemovePaymentOrders(v ...*PaymentOrder) *UserUpdate {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemovePaymentOrderIDs(ids...)
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *UserUpdate) Save(ctx context.Context) (int, error) {
if err := _u.defaults(); err != nil {
@ -1126,6 +1163,51 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) {
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.PaymentOrdersCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.PaymentOrdersTable,
Columns: []string{user.PaymentOrdersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(paymentorder.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedPaymentOrdersIDs(); len(nodes) > 0 && !_u.mutation.PaymentOrdersCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.PaymentOrdersTable,
Columns: []string{user.PaymentOrdersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(paymentorder.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.PaymentOrdersIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.PaymentOrdersTable,
Columns: []string{user.PaymentOrdersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(paymentorder.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{user.Label}
@ -1487,6 +1569,21 @@ func (_u *UserUpdateOne) AddPromoCodeUsages(v ...*PromoCodeUsage) *UserUpdateOne
return _u.AddPromoCodeUsageIDs(ids...)
}
// AddPaymentOrderIDs adds the "payment_orders" edge to the PaymentOrder entity by IDs.
func (_u *UserUpdateOne) AddPaymentOrderIDs(ids ...int64) *UserUpdateOne {
_u.mutation.AddPaymentOrderIDs(ids...)
return _u
}
// AddPaymentOrders adds the "payment_orders" edges to the PaymentOrder entity.
func (_u *UserUpdateOne) AddPaymentOrders(v ...*PaymentOrder) *UserUpdateOne {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.AddPaymentOrderIDs(ids...)
}
// Mutation returns the UserMutation object of the builder.
func (_u *UserUpdateOne) Mutation() *UserMutation {
return _u.mutation
@ -1681,6 +1778,27 @@ func (_u *UserUpdateOne) RemovePromoCodeUsages(v ...*PromoCodeUsage) *UserUpdate
return _u.RemovePromoCodeUsageIDs(ids...)
}
// ClearPaymentOrders clears all "payment_orders" edges to the PaymentOrder entity.
func (_u *UserUpdateOne) ClearPaymentOrders() *UserUpdateOne {
_u.mutation.ClearPaymentOrders()
return _u
}
// RemovePaymentOrderIDs removes the "payment_orders" edge to PaymentOrder entities by IDs.
func (_u *UserUpdateOne) RemovePaymentOrderIDs(ids ...int64) *UserUpdateOne {
_u.mutation.RemovePaymentOrderIDs(ids...)
return _u
}
// RemovePaymentOrders removes "payment_orders" edges to PaymentOrder entities.
func (_u *UserUpdateOne) RemovePaymentOrders(v ...*PaymentOrder) *UserUpdateOne {
ids := make([]int64, len(v))
for i := range v {
ids[i] = v[i].ID
}
return _u.RemovePaymentOrderIDs(ids...)
}
// Where appends a list predicates to the UserUpdate builder.
func (_u *UserUpdateOne) Where(ps ...predicate.User) *UserUpdateOne {
_u.mutation.Where(ps...)
@ -2266,6 +2384,51 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) {
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _u.mutation.PaymentOrdersCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.PaymentOrdersTable,
Columns: []string{user.PaymentOrdersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(paymentorder.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.RemovedPaymentOrdersIDs(); len(nodes) > 0 && !_u.mutation.PaymentOrdersCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.PaymentOrdersTable,
Columns: []string{user.PaymentOrdersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(paymentorder.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.PaymentOrdersIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
Inverse: false,
Table: user.PaymentOrdersTable,
Columns: []string{user.PaymentOrdersColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(paymentorder.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &User{config: _u.config}
_spec.Assign = _node.assignValues
_spec.ScanValues = _node.scanValues

View File

@ -27,12 +27,16 @@ require (
github.com/refraction-networking/utls v1.8.2
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v4 v4.25.6
github.com/shopspring/decimal v1.4.0
github.com/smartwalle/alipay/v3 v3.2.29
github.com/spf13/viper v1.18.2
github.com/stretchr/testify v1.11.1
github.com/stripe/stripe-go/v85 v85.0.0
github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0
github.com/testcontainers/testcontainers-go/modules/redis v0.40.0
github.com/tidwall/gjson v1.18.0
github.com/tidwall/sjson v1.2.5
github.com/wechatpay-apiv3/wechatpay-go v0.2.21
github.com/zeromicro/go-zero v1.9.4
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.48.0
@ -99,6 +103,7 @@ require (
github.com/goccy/go-json v0.10.2 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/subcommands v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/hcl/v2 v2.18.1 // indirect
@ -137,6 +142,9 @@ require (
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/smartwalle/ncrypto v1.0.4 // indirect
github.com/smartwalle/ngx v1.1.0 // indirect
github.com/smartwalle/nsign v1.0.9 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
@ -167,6 +175,7 @@ require (
golang.org/x/mod v0.32.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/tools v0.41.0 // indirect
google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect

View File

@ -14,6 +14,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw=
github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw=
github.com/alitto/pond/v2 v2.6.2 h1:Sphe40g0ILeM1pA2c2K+Th0DGU+pt0A/Kprr+WB24Pw=
github.com/alitto/pond/v2 v2.6.2/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
@ -160,6 +162,8 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
@ -288,8 +292,18 @@ github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartwalle/alipay/v3 v3.2.29 h1:roGFqlml8hDa//0TpFmlyxZhndTYs7rbYLu/HlNFNJo=
github.com/smartwalle/alipay/v3 v3.2.29/go.mod h1:XarBLuAkwK3ah7mYjVtghRu+ysxzlex9sRkgqNMzMRU=
github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8=
github.com/smartwalle/ncrypto v1.0.4/go.mod h1:Dwlp6sfeNaPMnOxMNayMTacvC5JGEVln3CVdiVDgbBk=
github.com/smartwalle/ngx v1.1.0 h1:q8nANgWSPRGeI/u+ixBoA4mf68DrUq6vZ+n9L5UKv9I=
github.com/smartwalle/ngx v1.1.0/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0=
github.com/smartwalle/nsign v1.0.9 h1:8poAgG7zBd8HkZy9RQDwasC6XZvJpDGQWSjzL2FZL6E=
github.com/smartwalle/nsign v1.0.9/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
@ -317,6 +331,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/stripe/stripe-go/v85 v85.0.0 h1:HMlFJXW6I/9WvkeSAtj8V7dI5pzeDu4gS1TaqR1ccI4=
github.com/stripe/stripe-go/v85 v85.0.0/go.mod h1:5P+HGFenpWgak27T5Is6JMsmDfUC1yJnjhhmquz7kXw=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU=
@ -342,6 +358,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/wechatpay-apiv3/wechatpay-go v0.2.21 h1:uIyMpzvcaHA33W/QPtHstccw+X52HO1gFdvVL9O6Lfs=
github.com/wechatpay-apiv3/wechatpay-go v0.2.21/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=

View File

@ -0,0 +1,323 @@
package admin
import (
"strconv"
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
)
// PaymentHandler handles admin payment management.
type PaymentHandler struct {
paymentService *service.PaymentService
configService *service.PaymentConfigService
}
// NewPaymentHandler creates a new admin PaymentHandler.
func NewPaymentHandler(paymentService *service.PaymentService, configService *service.PaymentConfigService) *PaymentHandler {
return &PaymentHandler{
paymentService: paymentService,
configService: configService,
}
}
// --- Dashboard ---
// GetDashboard returns payment dashboard statistics.
// GET /api/v1/admin/payment/dashboard
func (h *PaymentHandler) GetDashboard(c *gin.Context) {
days := 30
if d := c.Query("days"); d != "" {
if v, err := strconv.Atoi(d); err == nil && v > 0 {
days = v
}
}
stats, err := h.paymentService.GetDashboardStats(c.Request.Context(), days)
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, stats)
}
// --- Orders ---
// ListOrders returns a paginated list of all payment orders.
// GET /api/v1/admin/payment/orders
func (h *PaymentHandler) ListOrders(c *gin.Context) {
page, pageSize := response.ParsePagination(c)
var userID int64
if uid := c.Query("user_id"); uid != "" {
if v, err := strconv.ParseInt(uid, 10, 64); err == nil {
userID = v
}
}
orders, total, err := h.paymentService.AdminListOrders(c.Request.Context(), userID, service.OrderListParams{
Page: page,
PageSize: pageSize,
Status: c.Query("status"),
OrderType: c.Query("order_type"),
PaymentType: c.Query("payment_type"),
Keyword: c.Query("keyword"),
})
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Paginated(c, orders, int64(total), page, pageSize)
}
// GetOrderDetail returns detailed information about a single order.
// GET /api/v1/admin/payment/orders/:id
func (h *PaymentHandler) GetOrderDetail(c *gin.Context) {
orderID, ok := parseIDParam(c, "id")
if !ok {
return
}
order, err := h.paymentService.GetOrderByID(c.Request.Context(), orderID)
if err != nil {
response.ErrorFrom(c, err)
return
}
auditLogs, _ := h.paymentService.GetOrderAuditLogs(c.Request.Context(), orderID)
response.Success(c, gin.H{"order": order, "auditLogs": auditLogs})
}
// CancelOrder cancels a pending order (admin).
// POST /api/v1/admin/payment/orders/:id/cancel
func (h *PaymentHandler) CancelOrder(c *gin.Context) {
orderID, ok := parseIDParam(c, "id")
if !ok {
return
}
msg, err := h.paymentService.AdminCancelOrder(c.Request.Context(), orderID)
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, gin.H{"message": msg})
}
// RetryFulfillment retries fulfillment for a paid order.
// POST /api/v1/admin/payment/orders/:id/retry
func (h *PaymentHandler) RetryFulfillment(c *gin.Context) {
orderID, ok := parseIDParam(c, "id")
if !ok {
return
}
if err := h.paymentService.RetryFulfillment(c.Request.Context(), orderID); err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, gin.H{"message": "fulfillment retried"})
}
// AdminProcessRefundRequest is the request body for admin refund processing.
type AdminProcessRefundRequest struct {
Amount float64 `json:"amount"`
Reason string `json:"reason"`
Force bool `json:"force"`
DeductBalance bool `json:"deduct_balance"`
}
// ProcessRefund processes a refund for an order (admin).
// POST /api/v1/admin/payment/orders/:id/refund
func (h *PaymentHandler) ProcessRefund(c *gin.Context) {
orderID, ok := parseIDParam(c, "id")
if !ok {
return
}
var req AdminProcessRefundRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
plan, earlyResult, err := h.paymentService.PrepareRefund(c.Request.Context(), orderID, req.Amount, req.Reason, req.Force, req.DeductBalance)
if err != nil {
response.ErrorFrom(c, err)
return
}
if earlyResult != nil {
response.Success(c, earlyResult)
return
}
result, err := h.paymentService.ExecuteRefund(c.Request.Context(), plan)
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, result)
}
// --- Subscription Plans ---
// ListPlans returns all subscription plans.
// GET /api/v1/admin/payment/plans
func (h *PaymentHandler) ListPlans(c *gin.Context) {
plans, err := h.configService.ListPlans(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, plans)
}
// CreatePlan creates a new subscription plan.
// POST /api/v1/admin/payment/plans
func (h *PaymentHandler) CreatePlan(c *gin.Context) {
var req service.CreatePlanRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
plan, err := h.configService.CreatePlan(c.Request.Context(), req)
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Created(c, plan)
}
// UpdatePlan updates an existing subscription plan.
// PUT /api/v1/admin/payment/plans/:id
func (h *PaymentHandler) UpdatePlan(c *gin.Context) {
id, ok := parseIDParam(c, "id")
if !ok {
return
}
var req service.UpdatePlanRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
plan, err := h.configService.UpdatePlan(c.Request.Context(), id, req)
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, plan)
}
// DeletePlan deletes a subscription plan.
// DELETE /api/v1/admin/payment/plans/:id
func (h *PaymentHandler) DeletePlan(c *gin.Context) {
id, ok := parseIDParam(c, "id")
if !ok {
return
}
if err := h.configService.DeletePlan(c.Request.Context(), id); err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, gin.H{"message": "deleted"})
}
// --- Provider Instances ---
// ListProviders returns all payment provider instances.
// GET /api/v1/admin/payment/providers
func (h *PaymentHandler) ListProviders(c *gin.Context) {
providers, err := h.configService.ListProviderInstancesWithConfig(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, providers)
}
// CreateProvider creates a new payment provider instance.
// POST /api/v1/admin/payment/providers
func (h *PaymentHandler) CreateProvider(c *gin.Context) {
var req service.CreateProviderInstanceRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
inst, err := h.configService.CreateProviderInstance(c.Request.Context(), req)
if err != nil {
response.ErrorFrom(c, err)
return
}
h.paymentService.RefreshProviders(c.Request.Context())
response.Created(c, inst)
}
// UpdateProvider updates an existing payment provider instance.
// PUT /api/v1/admin/payment/providers/:id
func (h *PaymentHandler) UpdateProvider(c *gin.Context) {
id, ok := parseIDParam(c, "id")
if !ok {
return
}
var req service.UpdateProviderInstanceRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
inst, err := h.configService.UpdateProviderInstance(c.Request.Context(), id, req)
if err != nil {
response.ErrorFrom(c, err)
return
}
h.paymentService.RefreshProviders(c.Request.Context())
response.Success(c, inst)
}
// DeleteProvider deletes a payment provider instance.
// DELETE /api/v1/admin/payment/providers/:id
func (h *PaymentHandler) DeleteProvider(c *gin.Context) {
id, ok := parseIDParam(c, "id")
if !ok {
return
}
if err := h.configService.DeleteProviderInstance(c.Request.Context(), id); err != nil {
response.ErrorFrom(c, err)
return
}
h.paymentService.RefreshProviders(c.Request.Context())
response.Success(c, gin.H{"message": "deleted"})
}
// parseIDParam parses an int64 path parameter.
// Returns the parsed ID and true on success; on failure it writes a BadRequest response and returns false.
func parseIDParam(c *gin.Context, paramName string) (int64, bool) {
id, err := strconv.ParseInt(c.Param(paramName), 10, 64)
if err != nil {
response.BadRequest(c, "Invalid "+paramName)
return 0, false
}
return id, true
}
// --- Config ---
// GetConfig returns the payment configuration (admin view).
// GET /api/v1/admin/payment/config
func (h *PaymentHandler) GetConfig(c *gin.Context) {
cfg, err := h.configService.GetPaymentConfig(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, cfg)
}
// UpdateConfig updates the payment configuration.
// PUT /api/v1/admin/payment/config
func (h *PaymentHandler) UpdateConfig(c *gin.Context) {
var req service.UpdatePaymentConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
if err := h.configService.UpdatePaymentConfig(c.Request.Context(), req); err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, gin.H{"message": "updated"})
}

View File

@ -46,19 +46,23 @@ func scopesContainOpenID(scopes string) bool {
// SettingHandler 系统设置处理器
type SettingHandler struct {
settingService *service.SettingService
emailService *service.EmailService
turnstileService *service.TurnstileService
opsService *service.OpsService
settingService *service.SettingService
emailService *service.EmailService
turnstileService *service.TurnstileService
opsService *service.OpsService
paymentConfigService *service.PaymentConfigService
paymentService *service.PaymentService
}
// NewSettingHandler 创建系统设置处理器
func NewSettingHandler(settingService *service.SettingService, emailService *service.EmailService, turnstileService *service.TurnstileService, opsService *service.OpsService) *SettingHandler {
func NewSettingHandler(settingService *service.SettingService, emailService *service.EmailService, turnstileService *service.TurnstileService, opsService *service.OpsService, paymentConfigService *service.PaymentConfigService, paymentService *service.PaymentService) *SettingHandler {
return &SettingHandler{
settingService: settingService,
emailService: emailService,
turnstileService: turnstileService,
opsService: opsService,
settingService: settingService,
emailService: emailService,
turnstileService: turnstileService,
opsService: opsService,
paymentConfigService: paymentConfigService,
paymentService: paymentService,
}
}
@ -81,6 +85,15 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
})
}
// Load payment config
var paymentCfg *service.PaymentConfig
if h.paymentConfigService != nil {
paymentCfg, _ = h.paymentConfigService.GetPaymentConfig(c.Request.Context())
}
if paymentCfg == nil {
paymentCfg = &service.PaymentConfig{}
}
response.Success(c, dto.SystemSettings{
RegistrationEnabled: settings.RegistrationEnabled,
EmailVerifyEnabled: settings.EmailVerifyEnabled,
@ -162,6 +175,24 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
EnableFingerprintUnification: settings.EnableFingerprintUnification,
EnableMetadataPassthrough: settings.EnableMetadataPassthrough,
EnableCCHSigning: settings.EnableCCHSigning,
PaymentEnabled: paymentCfg.Enabled,
PaymentMinAmount: paymentCfg.MinAmount,
PaymentMaxAmount: paymentCfg.MaxAmount,
PaymentDailyLimit: paymentCfg.DailyLimit,
PaymentOrderTimeoutMin: paymentCfg.OrderTimeoutMin,
PaymentMaxPendingOrders: paymentCfg.MaxPendingOrders,
PaymentEnabledTypes: paymentCfg.EnabledTypes,
PaymentBalanceDisabled: paymentCfg.BalanceDisabled,
PaymentLoadBalanceStrat: paymentCfg.LoadBalanceStrategy,
PaymentProductNamePrefix: paymentCfg.ProductNamePrefix,
PaymentProductNameSuffix: paymentCfg.ProductNameSuffix,
PaymentHelpImageURL: paymentCfg.HelpImageURL,
PaymentHelpText: paymentCfg.HelpText,
PaymentCancelRateLimitEnabled: paymentCfg.CancelRateLimitEnabled,
PaymentCancelRateLimitMax: paymentCfg.CancelRateLimitMax,
PaymentCancelRateLimitWindow: paymentCfg.CancelRateLimitWindow,
PaymentCancelRateLimitUnit: paymentCfg.CancelRateLimitUnit,
PaymentCancelRateLimitMode: paymentCfg.CancelRateLimitMode,
})
}
@ -272,6 +303,28 @@ type UpdateSettingsRequest struct {
EnableFingerprintUnification *bool `json:"enable_fingerprint_unification"`
EnableMetadataPassthrough *bool `json:"enable_metadata_passthrough"`
EnableCCHSigning *bool `json:"enable_cch_signing"`
// Payment configuration (integrated into settings, full replace)
PaymentEnabled *bool `json:"payment_enabled"`
PaymentMinAmount *float64 `json:"payment_min_amount"`
PaymentMaxAmount *float64 `json:"payment_max_amount"`
PaymentDailyLimit *float64 `json:"payment_daily_limit"`
PaymentOrderTimeoutMin *int `json:"payment_order_timeout_minutes"`
PaymentMaxPendingOrders *int `json:"payment_max_pending_orders"`
PaymentEnabledTypes []string `json:"payment_enabled_types"`
PaymentBalanceDisabled *bool `json:"payment_balance_disabled"`
PaymentLoadBalanceStrat *string `json:"payment_load_balance_strategy"`
PaymentProductNamePrefix *string `json:"payment_product_name_prefix"`
PaymentProductNameSuffix *string `json:"payment_product_name_suffix"`
PaymentHelpImageURL *string `json:"payment_help_image_url"`
PaymentHelpText *string `json:"payment_help_text"`
// Cancel rate limit
PaymentCancelRateLimitEnabled *bool `json:"payment_cancel_rate_limit_enabled"`
PaymentCancelRateLimitMax *int `json:"payment_cancel_rate_limit_max"`
PaymentCancelRateLimitWindow *int `json:"payment_cancel_rate_limit_window"`
PaymentCancelRateLimitUnit *string `json:"payment_cancel_rate_limit_unit"`
PaymentCancelRateLimitMode *string `json:"payment_cancel_rate_limit_window_mode"`
}
// UpdateSettings 更新系统设置
@ -835,6 +888,39 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
return
}
// Update payment configuration (integrated into system settings).
// Skip if no payment fields were provided (prevents accidental wipe).
if h.paymentConfigService != nil && hasPaymentFields(req) {
paymentReq := service.UpdatePaymentConfigRequest{
Enabled: req.PaymentEnabled,
MinAmount: req.PaymentMinAmount,
MaxAmount: req.PaymentMaxAmount,
DailyLimit: req.PaymentDailyLimit,
OrderTimeoutMin: req.PaymentOrderTimeoutMin,
MaxPendingOrders: req.PaymentMaxPendingOrders,
EnabledTypes: req.PaymentEnabledTypes,
BalanceDisabled: req.PaymentBalanceDisabled,
LoadBalanceStrategy: req.PaymentLoadBalanceStrat,
ProductNamePrefix: req.PaymentProductNamePrefix,
ProductNameSuffix: req.PaymentProductNameSuffix,
HelpImageURL: req.PaymentHelpImageURL,
HelpText: req.PaymentHelpText,
CancelRateLimitEnabled: req.PaymentCancelRateLimitEnabled,
CancelRateLimitMax: req.PaymentCancelRateLimitMax,
CancelRateLimitWindow: req.PaymentCancelRateLimitWindow,
CancelRateLimitUnit: req.PaymentCancelRateLimitUnit,
CancelRateLimitMode: req.PaymentCancelRateLimitMode,
}
if err := h.paymentConfigService.UpdatePaymentConfig(c.Request.Context(), paymentReq); err != nil {
response.ErrorFrom(c, err)
return
}
// Refresh in-memory provider registry so config changes take effect immediately
if h.paymentService != nil {
h.paymentService.RefreshProviders(c.Request.Context())
}
}
h.auditSettingsUpdate(c, previousSettings, settings, req)
// 重新获取设置返回
@ -851,6 +937,15 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
})
}
// Reload payment config for response
var updatedPaymentCfg *service.PaymentConfig
if h.paymentConfigService != nil {
updatedPaymentCfg, _ = h.paymentConfigService.GetPaymentConfig(c.Request.Context())
}
if updatedPaymentCfg == nil {
updatedPaymentCfg = &service.PaymentConfig{}
}
response.Success(c, dto.SystemSettings{
RegistrationEnabled: updatedSettings.RegistrationEnabled,
EmailVerifyEnabled: updatedSettings.EmailVerifyEnabled,
@ -932,9 +1027,40 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
EnableFingerprintUnification: updatedSettings.EnableFingerprintUnification,
EnableMetadataPassthrough: updatedSettings.EnableMetadataPassthrough,
EnableCCHSigning: updatedSettings.EnableCCHSigning,
PaymentEnabled: updatedPaymentCfg.Enabled,
PaymentMinAmount: updatedPaymentCfg.MinAmount,
PaymentMaxAmount: updatedPaymentCfg.MaxAmount,
PaymentDailyLimit: updatedPaymentCfg.DailyLimit,
PaymentOrderTimeoutMin: updatedPaymentCfg.OrderTimeoutMin,
PaymentMaxPendingOrders: updatedPaymentCfg.MaxPendingOrders,
PaymentEnabledTypes: updatedPaymentCfg.EnabledTypes,
PaymentBalanceDisabled: updatedPaymentCfg.BalanceDisabled,
PaymentLoadBalanceStrat: updatedPaymentCfg.LoadBalanceStrategy,
PaymentProductNamePrefix: updatedPaymentCfg.ProductNamePrefix,
PaymentProductNameSuffix: updatedPaymentCfg.ProductNameSuffix,
PaymentHelpImageURL: updatedPaymentCfg.HelpImageURL,
PaymentHelpText: updatedPaymentCfg.HelpText,
PaymentCancelRateLimitEnabled: updatedPaymentCfg.CancelRateLimitEnabled,
PaymentCancelRateLimitMax: updatedPaymentCfg.CancelRateLimitMax,
PaymentCancelRateLimitWindow: updatedPaymentCfg.CancelRateLimitWindow,
PaymentCancelRateLimitUnit: updatedPaymentCfg.CancelRateLimitUnit,
PaymentCancelRateLimitMode: updatedPaymentCfg.CancelRateLimitMode,
})
}
// hasPaymentFields returns true if any payment-related field was explicitly provided.
func hasPaymentFields(req UpdateSettingsRequest) bool {
return req.PaymentEnabled != nil || req.PaymentMinAmount != nil ||
req.PaymentMaxAmount != nil || req.PaymentDailyLimit != nil ||
req.PaymentOrderTimeoutMin != nil || req.PaymentMaxPendingOrders != nil ||
req.PaymentEnabledTypes != nil || req.PaymentBalanceDisabled != nil ||
req.PaymentLoadBalanceStrat != nil || req.PaymentProductNamePrefix != nil ||
req.PaymentProductNameSuffix != nil || req.PaymentHelpImageURL != nil ||
req.PaymentHelpText != nil || req.PaymentCancelRateLimitEnabled != nil ||
req.PaymentCancelRateLimitMax != nil || req.PaymentCancelRateLimitWindow != nil ||
req.PaymentCancelRateLimitUnit != nil || req.PaymentCancelRateLimitMode != nil
}
func (h *SettingHandler) auditSettingsUpdate(c *gin.Context, before *service.SystemSettings, after *service.SystemSettings, req UpdateSettingsRequest) {
if before == nil || after == nil {
return

View File

@ -123,6 +123,28 @@ type SystemSettings struct {
EnableFingerprintUnification bool `json:"enable_fingerprint_unification"`
EnableMetadataPassthrough bool `json:"enable_metadata_passthrough"`
EnableCCHSigning bool `json:"enable_cch_signing"`
// Payment configuration
PaymentEnabled bool `json:"payment_enabled"`
PaymentMinAmount float64 `json:"payment_min_amount"`
PaymentMaxAmount float64 `json:"payment_max_amount"`
PaymentDailyLimit float64 `json:"payment_daily_limit"`
PaymentOrderTimeoutMin int `json:"payment_order_timeout_minutes"`
PaymentMaxPendingOrders int `json:"payment_max_pending_orders"`
PaymentEnabledTypes []string `json:"payment_enabled_types"`
PaymentBalanceDisabled bool `json:"payment_balance_disabled"`
PaymentLoadBalanceStrat string `json:"payment_load_balance_strategy"`
PaymentProductNamePrefix string `json:"payment_product_name_prefix"`
PaymentProductNameSuffix string `json:"payment_product_name_suffix"`
PaymentHelpImageURL string `json:"payment_help_image_url"`
PaymentHelpText string `json:"payment_help_text"`
// Cancel rate limit
PaymentCancelRateLimitEnabled bool `json:"payment_cancel_rate_limit_enabled"`
PaymentCancelRateLimitMax int `json:"payment_cancel_rate_limit_max"`
PaymentCancelRateLimitWindow int `json:"payment_cancel_rate_limit_window"`
PaymentCancelRateLimitUnit string `json:"payment_cancel_rate_limit_unit"`
PaymentCancelRateLimitMode string `json:"payment_cancel_rate_limit_window_mode"`
}
type DefaultSubscriptionSetting struct {
@ -159,6 +181,7 @@ type PublicSettings struct {
OIDCOAuthProviderName string `json:"oidc_oauth_provider_name"`
SoraClientEnabled bool `json:"sora_client_enabled"`
BackendModeEnabled bool `json:"backend_mode_enabled"`
PaymentEnabled bool `json:"payment_enabled"`
Version string `json:"version"`
}

View File

@ -31,22 +31,25 @@ type AdminHandlers struct {
APIKey *admin.AdminAPIKeyHandler
ScheduledTest *admin.ScheduledTestHandler
Channel *admin.ChannelHandler
Payment *admin.PaymentHandler
}
// Handlers contains all HTTP handlers
type Handlers struct {
Auth *AuthHandler
User *UserHandler
APIKey *APIKeyHandler
Usage *UsageHandler
Redeem *RedeemHandler
Subscription *SubscriptionHandler
Announcement *AnnouncementHandler
Admin *AdminHandlers
Gateway *GatewayHandler
OpenAIGateway *OpenAIGatewayHandler
Setting *SettingHandler
Totp *TotpHandler
Auth *AuthHandler
User *UserHandler
APIKey *APIKeyHandler
Usage *UsageHandler
Redeem *RedeemHandler
Subscription *SubscriptionHandler
Announcement *AnnouncementHandler
Admin *AdminHandlers
Gateway *GatewayHandler
OpenAIGateway *OpenAIGatewayHandler
Setting *SettingHandler
Totp *TotpHandler
Payment *PaymentHandler
PaymentWebhook *PaymentWebhookHandler
}
// BuildInfo contains build-time information

View File

@ -0,0 +1,421 @@
package handler
import (
"strconv"
"strings"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
)
// PaymentHandler handles user-facing payment requests.
type PaymentHandler struct {
channelService *service.ChannelService
paymentService *service.PaymentService
configService *service.PaymentConfigService
}
// NewPaymentHandler creates a new PaymentHandler.
func NewPaymentHandler(paymentService *service.PaymentService, configService *service.PaymentConfigService, channelService *service.ChannelService) *PaymentHandler {
return &PaymentHandler{
channelService: channelService,
paymentService: paymentService,
configService: configService,
}
}
// GetPaymentConfig returns the payment system configuration.
// GET /api/v1/payment/config
func (h *PaymentHandler) GetPaymentConfig(c *gin.Context) {
cfg, err := h.configService.GetPaymentConfig(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, cfg)
}
// GetPlans returns subscription plans available for sale.
// GET /api/v1/payment/plans
func (h *PaymentHandler) GetPlans(c *gin.Context) {
plans, err := h.configService.ListPlansForSale(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
// Enrich plans with group platform for frontend color coding
type planWithPlatform struct {
ID int64 `json:"id"`
GroupID int64 `json:"group_id"`
GroupPlatform string `json:"group_platform"`
Name string `json:"name"`
Description string `json:"description"`
Price float64 `json:"price"`
OriginalPrice *float64 `json:"original_price,omitempty"`
ValidityDays int `json:"validity_days"`
ValidityUnit string `json:"validity_unit"`
Features string `json:"features"`
ProductName string `json:"product_name"`
ForSale bool `json:"for_sale"`
SortOrder int `json:"sort_order"`
}
platformMap := h.configService.GetGroupPlatformMap(c.Request.Context(), plans)
result := make([]planWithPlatform, 0, len(plans))
for _, p := range plans {
result = append(result, planWithPlatform{
ID: int64(p.ID), GroupID: p.GroupID, GroupPlatform: platformMap[p.GroupID],
Name: p.Name, Description: p.Description, Price: p.Price, OriginalPrice: p.OriginalPrice,
ValidityDays: p.ValidityDays, ValidityUnit: p.ValidityUnit, Features: p.Features,
ProductName: p.ProductName, ForSale: p.ForSale, SortOrder: p.SortOrder,
})
}
response.Success(c, result)
}
// GetChannels returns enabled payment channels.
// GET /api/v1/payment/channels
func (h *PaymentHandler) GetChannels(c *gin.Context) {
channels, _, err := h.channelService.List(c.Request.Context(), pagination.PaginationParams{Page: 1, PageSize: 1000}, "active", "")
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, channels)
}
// GetCheckoutInfo returns all data the payment page needs in a single call:
// payment methods with limits, subscription plans, and configuration.
// GET /api/v1/payment/checkout-info
func (h *PaymentHandler) GetCheckoutInfo(c *gin.Context) {
ctx := c.Request.Context()
// Fetch limits (methods + global range)
limitsResp, err := h.configService.GetAvailableMethodLimits(ctx)
if err != nil {
response.ErrorFrom(c, err)
return
}
// Fetch payment config
cfg, err := h.configService.GetPaymentConfig(ctx)
if err != nil {
response.ErrorFrom(c, err)
return
}
// Fetch plans with group info
plans, _ := h.configService.ListPlansForSale(ctx)
groupInfo := h.configService.GetGroupInfoMap(ctx, plans)
planList := make([]checkoutPlan, 0, len(plans))
for _, p := range plans {
gi := groupInfo[p.GroupID]
planList = append(planList, checkoutPlan{
ID: int64(p.ID), GroupID: p.GroupID,
GroupPlatform: gi.Platform, GroupName: gi.Name,
RateMultiplier: gi.RateMultiplier, DailyLimitUSD: gi.DailyLimitUSD,
WeeklyLimitUSD: gi.WeeklyLimitUSD, MonthlyLimitUSD: gi.MonthlyLimitUSD,
ModelScopes: gi.ModelScopes,
Name: p.Name, Description: p.Description, Price: p.Price, OriginalPrice: p.OriginalPrice,
ValidityDays: p.ValidityDays, ValidityUnit: p.ValidityUnit, Features: parseFeatures(p.Features),
ProductName: p.ProductName,
})
}
response.Success(c, checkoutInfoResponse{
Methods: limitsResp.Methods,
GlobalMin: limitsResp.GlobalMin,
GlobalMax: limitsResp.GlobalMax,
Plans: planList,
BalanceDisabled: cfg.BalanceDisabled,
HelpText: cfg.HelpText,
HelpImageURL: cfg.HelpImageURL,
StripePublishableKey: cfg.StripePublishableKey,
})
}
type checkoutInfoResponse struct {
Methods map[string]service.MethodLimits `json:"methods"`
GlobalMin float64 `json:"global_min"`
GlobalMax float64 `json:"global_max"`
Plans []checkoutPlan `json:"plans"`
BalanceDisabled bool `json:"balance_disabled"`
HelpText string `json:"help_text"`
HelpImageURL string `json:"help_image_url"`
StripePublishableKey string `json:"stripe_publishable_key"`
}
type checkoutPlan struct {
ID int64 `json:"id"`
GroupID int64 `json:"group_id"`
GroupPlatform string `json:"group_platform"`
GroupName string `json:"group_name"`
RateMultiplier float64 `json:"rate_multiplier"`
DailyLimitUSD *float64 `json:"daily_limit_usd"`
WeeklyLimitUSD *float64 `json:"weekly_limit_usd"`
MonthlyLimitUSD *float64 `json:"monthly_limit_usd"`
ModelScopes []string `json:"supported_model_scopes"`
Name string `json:"name"`
Description string `json:"description"`
Price float64 `json:"price"`
OriginalPrice *float64 `json:"original_price,omitempty"`
ValidityDays int `json:"validity_days"`
ValidityUnit string `json:"validity_unit"`
Features []string `json:"features"`
ProductName string `json:"product_name"`
}
// parseFeatures splits a newline-separated features string into a string slice.
func parseFeatures(raw string) []string {
if raw == "" {
return []string{}
}
var out []string
for _, line := range strings.Split(raw, "\n") {
if s := strings.TrimSpace(line); s != "" {
out = append(out, s)
}
}
if out == nil {
return []string{}
}
return out
}
// GetLimits returns per-payment-type limits derived from enabled provider instances.
// GET /api/v1/payment/limits
func (h *PaymentHandler) GetLimits(c *gin.Context) {
resp, err := h.configService.GetAvailableMethodLimits(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, resp)
}
// CreateOrderRequest is the request body for creating a payment order.
type CreateOrderRequest struct {
Amount float64 `json:"amount"`
PaymentType string `json:"payment_type" binding:"required"`
OrderType string `json:"order_type"`
PlanID int64 `json:"plan_id"`
}
// CreateOrder creates a new payment order.
// POST /api/v1/payment/orders
func (h *PaymentHandler) CreateOrder(c *gin.Context) {
subject, ok := requireAuth(c)
if !ok {
return
}
var req CreateOrderRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
result, err := h.paymentService.CreateOrder(c.Request.Context(), service.CreateOrderRequest{
UserID: subject.UserID,
Amount: req.Amount,
PaymentType: req.PaymentType,
ClientIP: c.ClientIP(),
IsMobile: isMobile(c),
SrcHost: c.Request.Host,
SrcURL: c.Request.Referer(),
OrderType: req.OrderType,
PlanID: req.PlanID,
})
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, result)
}
// GetMyOrders returns the authenticated user's orders.
// GET /api/v1/payment/orders/my
func (h *PaymentHandler) GetMyOrders(c *gin.Context) {
subject, ok := requireAuth(c)
if !ok {
return
}
page, pageSize := response.ParsePagination(c)
orders, total, err := h.paymentService.GetUserOrders(c.Request.Context(), subject.UserID, service.OrderListParams{
Page: page,
PageSize: pageSize,
Status: c.Query("status"),
OrderType: c.Query("order_type"),
PaymentType: c.Query("payment_type"),
})
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Paginated(c, orders, int64(total), page, pageSize)
}
// GetOrder returns a single order for the authenticated user.
// GET /api/v1/payment/orders/:id
func (h *PaymentHandler) GetOrder(c *gin.Context) {
subject, ok := requireAuth(c)
if !ok {
return
}
orderID, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "Invalid order ID")
return
}
order, err := h.paymentService.GetOrder(c.Request.Context(), orderID, subject.UserID)
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, order)
}
// CancelOrder cancels a pending order for the authenticated user.
// POST /api/v1/payment/orders/:id/cancel
func (h *PaymentHandler) CancelOrder(c *gin.Context) {
subject, ok := requireAuth(c)
if !ok {
return
}
orderID, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "Invalid order ID")
return
}
msg, err := h.paymentService.CancelOrder(c.Request.Context(), orderID, subject.UserID)
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, gin.H{"message": msg})
}
// RefundRequestBody is the request body for requesting a refund.
type RefundRequestBody struct {
Reason string `json:"reason"`
}
// RequestRefund submits a refund request for a completed order.
// POST /api/v1/payment/orders/:id/refund-request
func (h *PaymentHandler) RequestRefund(c *gin.Context) {
subject, ok := requireAuth(c)
if !ok {
return
}
orderID, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
response.BadRequest(c, "Invalid order ID")
return
}
var req RefundRequestBody
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
if err := h.paymentService.RequestRefund(c.Request.Context(), orderID, subject.UserID, req.Reason); err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, gin.H{"message": "refund requested"})
}
// VerifyOrderRequest is the request body for verifying a payment order.
type VerifyOrderRequest struct {
OutTradeNo string `json:"out_trade_no" binding:"required"`
}
// VerifyOrder actively queries the upstream payment provider to check
// if payment was made, and processes it if so.
// POST /api/v1/payment/orders/verify
func (h *PaymentHandler) VerifyOrder(c *gin.Context) {
subject, ok := requireAuth(c)
if !ok {
return
}
var req VerifyOrderRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
order, err := h.paymentService.VerifyOrderByOutTradeNo(c.Request.Context(), req.OutTradeNo, subject.UserID)
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, order)
}
// PublicOrderResult is the limited order info returned by the public verify endpoint.
// No user details are exposed — only payment status information.
type PublicOrderResult struct {
ID int64 `json:"id"`
OutTradeNo string `json:"out_trade_no"`
Amount float64 `json:"amount"`
PayAmount float64 `json:"pay_amount"`
PaymentType string `json:"payment_type"`
Status string `json:"status"`
}
// VerifyOrderPublic verifies payment status without requiring authentication.
// Returns limited order info (no user details) to prevent information leakage.
// POST /api/v1/payment/public/orders/verify
func (h *PaymentHandler) VerifyOrderPublic(c *gin.Context) {
var req VerifyOrderRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
order, err := h.paymentService.VerifyOrderPublic(c.Request.Context(), req.OutTradeNo)
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, PublicOrderResult{
ID: order.ID,
OutTradeNo: order.OutTradeNo,
Amount: order.Amount,
PayAmount: order.PayAmount,
PaymentType: order.PaymentType,
Status: order.Status,
})
}
// requireAuth extracts the authenticated subject from the context.
// Returns the subject and true on success; on failure it writes an Unauthorized response and returns false.
func requireAuth(c *gin.Context) (middleware2.AuthSubject, bool) {
subject, ok := middleware2.GetAuthSubjectFromContext(c)
if !ok {
response.Unauthorized(c, "User not authenticated")
return middleware2.AuthSubject{}, false
}
return subject, true
}
// isMobile detects mobile user agents.
func isMobile(c *gin.Context) bool {
ua := strings.ToLower(c.GetHeader("User-Agent"))
for _, kw := range []string{"mobile", "android", "iphone", "ipad", "ipod"} {
if strings.Contains(ua, kw) {
return true
}
}
return false
}

View File

@ -0,0 +1,158 @@
package handler
import (
"io"
"log/slog"
"net/http"
"net/url"
"strings"
"github.com/Wei-Shaw/sub2api/internal/payment"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
)
// PaymentWebhookHandler handles payment provider webhook callbacks.
type PaymentWebhookHandler struct {
paymentService *service.PaymentService
registry *payment.Registry
}
// maxWebhookBodySize is the maximum allowed webhook request body size (1 MB).
const maxWebhookBodySize = 1 << 20
// webhookLogTruncateLen is the maximum length of raw body logged on verify failure.
const webhookLogTruncateLen = 200
// NewPaymentWebhookHandler creates a new PaymentWebhookHandler.
func NewPaymentWebhookHandler(paymentService *service.PaymentService, registry *payment.Registry) *PaymentWebhookHandler {
return &PaymentWebhookHandler{
paymentService: paymentService,
registry: registry,
}
}
// EasyPayNotify handles EasyPay payment notifications.
// POST /api/v1/payment/webhook/easypay
func (h *PaymentWebhookHandler) EasyPayNotify(c *gin.Context) {
h.handleNotify(c, payment.TypeEasyPay)
}
// AlipayNotify handles Alipay payment notifications.
// POST /api/v1/payment/webhook/alipay
func (h *PaymentWebhookHandler) AlipayNotify(c *gin.Context) {
h.handleNotify(c, payment.TypeAlipay)
}
// WxpayNotify handles WeChat Pay payment notifications.
// POST /api/v1/payment/webhook/wxpay
func (h *PaymentWebhookHandler) WxpayNotify(c *gin.Context) {
h.handleNotify(c, payment.TypeWxpay)
}
// StripeWebhook handles Stripe webhook events.
// POST /api/v1/payment/webhook/stripe
func (h *PaymentWebhookHandler) StripeWebhook(c *gin.Context) {
h.handleNotify(c, payment.TypeStripe)
}
// handleNotify is the shared logic for all provider webhook handlers.
func (h *PaymentWebhookHandler) handleNotify(c *gin.Context, providerKey string) {
var rawBody string
if c.Request.Method == http.MethodGet {
// GET callbacks (e.g. EasyPay) pass params as URL query string
rawBody = c.Request.URL.RawQuery
} else {
body, err := io.ReadAll(io.LimitReader(c.Request.Body, maxWebhookBodySize))
if err != nil {
slog.Error("[Payment Webhook] failed to read body", "provider", providerKey, "error", err)
c.String(http.StatusBadRequest, "failed to read body")
return
}
rawBody = string(body)
}
// Extract out_trade_no to look up the order's specific provider instance.
// This is needed when multiple instances of the same provider exist (e.g. multiple EasyPay accounts).
outTradeNo := extractOutTradeNo(rawBody, providerKey)
provider, err := h.paymentService.GetWebhookProvider(c.Request.Context(), providerKey, outTradeNo)
if err != nil {
slog.Warn("[Payment Webhook] provider not found", "provider", providerKey, "outTradeNo", outTradeNo, "error", err)
writeSuccessResponse(c, providerKey)
return
}
headers := make(map[string]string)
for k := range c.Request.Header {
headers[strings.ToLower(k)] = c.GetHeader(k)
}
notification, err := provider.VerifyNotification(c.Request.Context(), rawBody, headers)
if err != nil {
truncatedBody := rawBody
if len(truncatedBody) > webhookLogTruncateLen {
truncatedBody = truncatedBody[:webhookLogTruncateLen] + "...(truncated)"
}
slog.Error("[Payment Webhook] verify failed", "provider", providerKey, "error", err, "method", c.Request.Method, "bodyLen", len(rawBody))
slog.Debug("[Payment Webhook] verify failed body", "provider", providerKey, "rawBody", truncatedBody)
c.String(http.StatusBadRequest, "verify failed")
return
}
// nil notification means irrelevant event (e.g. Stripe non-payment event); return success.
if notification == nil {
writeSuccessResponse(c, providerKey)
return
}
if err := h.paymentService.HandlePaymentNotification(c.Request.Context(), notification, providerKey); err != nil {
slog.Error("[Payment Webhook] handle notification failed", "provider", providerKey, "error", err)
c.String(http.StatusInternalServerError, "handle failed")
return
}
writeSuccessResponse(c, providerKey)
}
// extractOutTradeNo parses the webhook body to find the out_trade_no.
// This allows looking up the correct provider instance before verification.
func extractOutTradeNo(rawBody, providerKey string) string {
switch providerKey {
case payment.TypeEasyPay:
values, err := url.ParseQuery(rawBody)
if err == nil {
return values.Get("out_trade_no")
}
}
// For other providers (Stripe, Alipay direct, WxPay direct), the registry
// typically has only one instance, so no instance lookup is needed.
return ""
}
// wxpaySuccessResponse is the JSON response expected by WeChat Pay webhook.
type wxpaySuccessResponse struct {
Code string `json:"code"`
Message string `json:"message"`
}
// WeChat Pay webhook success response constants.
const (
wxpaySuccessCode = "SUCCESS"
wxpaySuccessMessage = "成功"
)
// writeSuccessResponse sends the provider-specific success response.
// WeChat Pay requires JSON {"code":"SUCCESS","message":"成功"};
// Stripe expects an empty 200; others accept plain text "success".
func writeSuccessResponse(c *gin.Context, providerKey string) {
switch providerKey {
case payment.TypeWxpay:
c.JSON(http.StatusOK, wxpaySuccessResponse{Code: wxpaySuccessCode, Message: wxpaySuccessMessage})
case payment.TypeStripe:
c.String(http.StatusOK, "")
default:
c.String(http.StatusOK, "success")
}
}

View File

@ -0,0 +1,99 @@
//go:build unit
package handler
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestWriteSuccessResponse(t *testing.T) {
gin.SetMode(gin.TestMode)
tests := []struct {
name string
providerKey string
wantCode int
wantContentType string
wantBody string
checkJSON bool
wantJSONCode string
wantJSONMessage string
}{
{
name: "wxpay returns JSON with code SUCCESS",
providerKey: "wxpay",
wantCode: http.StatusOK,
wantContentType: "application/json",
checkJSON: true,
wantJSONCode: "SUCCESS",
wantJSONMessage: "成功",
},
{
name: "stripe returns empty 200",
providerKey: "stripe",
wantCode: http.StatusOK,
wantContentType: "text/plain",
wantBody: "",
},
{
name: "easypay returns plain text success",
providerKey: "easypay",
wantCode: http.StatusOK,
wantContentType: "text/plain",
wantBody: "success",
},
{
name: "alipay returns plain text success",
providerKey: "alipay",
wantCode: http.StatusOK,
wantContentType: "text/plain",
wantBody: "success",
},
{
name: "unknown provider returns plain text success",
providerKey: "unknown_provider",
wantCode: http.StatusOK,
wantContentType: "text/plain",
wantBody: "success",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
writeSuccessResponse(c, tt.providerKey)
assert.Equal(t, tt.wantCode, w.Code)
assert.Contains(t, w.Header().Get("Content-Type"), tt.wantContentType)
if tt.checkJSON {
var resp wxpaySuccessResponse
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err, "response body should be valid JSON")
assert.Equal(t, tt.wantJSONCode, resp.Code)
assert.Equal(t, tt.wantJSONMessage, resp.Message)
} else {
assert.Equal(t, tt.wantBody, w.Body.String())
}
})
}
}
func TestWebhookConstants(t *testing.T) {
t.Run("maxWebhookBodySize is 1MB", func(t *testing.T) {
assert.Equal(t, int64(1<<20), int64(maxWebhookBodySize))
})
t.Run("webhookLogTruncateLen is 200", func(t *testing.T) {
assert.Equal(t, 200, webhookLogTruncateLen)
})
}

View File

@ -59,6 +59,7 @@ func (h *SettingHandler) GetPublicSettings(c *gin.Context) {
OIDCOAuthEnabled: settings.OIDCOAuthEnabled,
OIDCOAuthProviderName: settings.OIDCOAuthProviderName,
BackendModeEnabled: settings.BackendModeEnabled,
PaymentEnabled: settings.PaymentEnabled,
Version: h.version,
})
}

View File

@ -34,6 +34,7 @@ func ProvideAdminHandlers(
apiKeyHandler *admin.AdminAPIKeyHandler,
scheduledTestHandler *admin.ScheduledTestHandler,
channelHandler *admin.ChannelHandler,
paymentHandler *admin.PaymentHandler,
) *AdminHandlers {
return &AdminHandlers{
Dashboard: dashboardHandler,
@ -61,6 +62,7 @@ func ProvideAdminHandlers(
APIKey: apiKeyHandler,
ScheduledTest: scheduledTestHandler,
Channel: channelHandler,
Payment: paymentHandler,
}
}
@ -88,22 +90,26 @@ func ProvideHandlers(
openaiGatewayHandler *OpenAIGatewayHandler,
settingHandler *SettingHandler,
totpHandler *TotpHandler,
paymentHandler *PaymentHandler,
paymentWebhookHandler *PaymentWebhookHandler,
_ *service.IdempotencyCoordinator,
_ *service.IdempotencyCleanupService,
) *Handlers {
return &Handlers{
Auth: authHandler,
User: userHandler,
APIKey: apiKeyHandler,
Usage: usageHandler,
Redeem: redeemHandler,
Subscription: subscriptionHandler,
Announcement: announcementHandler,
Admin: adminHandlers,
Gateway: gatewayHandler,
OpenAIGateway: openaiGatewayHandler,
Setting: settingHandler,
Totp: totpHandler,
Auth: authHandler,
User: userHandler,
APIKey: apiKeyHandler,
Usage: usageHandler,
Redeem: redeemHandler,
Subscription: subscriptionHandler,
Announcement: announcementHandler,
Admin: adminHandlers,
Gateway: gatewayHandler,
OpenAIGateway: openaiGatewayHandler,
Setting: settingHandler,
Totp: totpHandler,
Payment: paymentHandler,
PaymentWebhook: paymentWebhookHandler,
}
}
@ -121,6 +127,8 @@ var ProviderSet = wire.NewSet(
NewOpenAIGatewayHandler,
NewTotpHandler,
ProvideSettingHandler,
NewPaymentHandler,
NewPaymentWebhookHandler,
// Admin handlers
admin.NewDashboardHandler,
@ -148,6 +156,7 @@ var ProviderSet = wire.NewSet(
admin.NewAdminAPIKeyHandler,
admin.NewScheduledTestHandler,
admin.NewChannelHandler,
admin.NewPaymentHandler,
// AdminHandlers and Handlers constructors
ProvideAdminHandlers,

View File

@ -0,0 +1,24 @@
package payment
import (
"fmt"
"github.com/shopspring/decimal"
)
const centsPerYuan = 100
// YuanToFen converts a CNY yuan string (e.g. "10.50") to fen (int64).
// Uses shopspring/decimal for precision.
func YuanToFen(yuanStr string) (int64, error) {
d, err := decimal.NewFromString(yuanStr)
if err != nil {
return 0, fmt.Errorf("invalid amount: %s", yuanStr)
}
return d.Mul(decimal.NewFromInt(centsPerYuan)).IntPart(), nil
}
// FenToYuan converts fen (int64) to yuan as a float64 for interface compatibility.
func FenToYuan(fen int64) float64 {
return decimal.NewFromInt(fen).Div(decimal.NewFromInt(centsPerYuan)).InexactFloat64()
}

View File

@ -0,0 +1,128 @@
//go:build unit
package payment
import (
"math"
"testing"
)
func TestYuanToFen(t *testing.T) {
tests := []struct {
name string
input string
want int64
wantErr bool
}{
// Normal values
{name: "one yuan", input: "1.00", want: 100},
{name: "ten yuan fifty fen", input: "10.50", want: 1050},
{name: "one fen", input: "0.01", want: 1},
{name: "large amount", input: "99999.99", want: 9999999},
// Edge: zero
{name: "zero no decimal", input: "0", want: 0},
{name: "zero with decimal", input: "0.00", want: 0},
// IEEE 754 precision edge case: 1.15 * 100 = 114.99999... in float64
{name: "ieee754 precision 1.15", input: "1.15", want: 115},
// More precision edge cases
{name: "ieee754 precision 0.1", input: "0.1", want: 10},
{name: "ieee754 precision 0.2", input: "0.2", want: 20},
{name: "ieee754 precision 33.33", input: "33.33", want: 3333},
// Large value
{name: "hundred thousand", input: "100000.00", want: 10000000},
// Integer without decimal
{name: "integer 5", input: "5", want: 500},
{name: "integer 100", input: "100", want: 10000},
// Single decimal place
{name: "single decimal 1.5", input: "1.5", want: 150},
// Negative values
{name: "negative one yuan", input: "-1.00", want: -100},
{name: "negative with fen", input: "-10.50", want: -1050},
// Invalid inputs
{name: "empty string", input: "", wantErr: true},
{name: "alphabetic", input: "abc", wantErr: true},
{name: "double dot", input: "1.2.3", wantErr: true},
{name: "spaces", input: " ", wantErr: true},
{name: "special chars", input: "$10.00", wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := YuanToFen(tt.input)
if tt.wantErr {
if err == nil {
t.Errorf("YuanToFen(%q) expected error, got %d", tt.input, got)
}
return
}
if err != nil {
t.Fatalf("YuanToFen(%q) unexpected error: %v", tt.input, err)
}
if got != tt.want {
t.Errorf("YuanToFen(%q) = %d, want %d", tt.input, got, tt.want)
}
})
}
}
func TestFenToYuan(t *testing.T) {
tests := []struct {
name string
fen int64
want float64
}{
{name: "one yuan", fen: 100, want: 1.0},
{name: "ten yuan fifty fen", fen: 1050, want: 10.5},
{name: "one fen", fen: 1, want: 0.01},
{name: "zero", fen: 0, want: 0.0},
{name: "large amount", fen: 9999999, want: 99999.99},
{name: "negative", fen: -100, want: -1.0},
{name: "negative with fen", fen: -1050, want: -10.5},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := FenToYuan(tt.fen)
if math.Abs(got-tt.want) > 1e-9 {
t.Errorf("FenToYuan(%d) = %f, want %f", tt.fen, got, tt.want)
}
})
}
}
func TestYuanToFenRoundTrip(t *testing.T) {
// Verify that converting yuan->fen->yuan preserves the value.
cases := []struct {
yuan string
fen int64
}{
{"0.01", 1},
{"1.00", 100},
{"10.50", 1050},
{"99999.99", 9999999},
}
for _, tc := range cases {
fen, err := YuanToFen(tc.yuan)
if err != nil {
t.Fatalf("YuanToFen(%q) unexpected error: %v", tc.yuan, err)
}
if fen != tc.fen {
t.Errorf("YuanToFen(%q) = %d, want %d", tc.yuan, fen, tc.fen)
}
yuan := FenToYuan(fen)
// Parse expected yuan back for comparison
expectedYuan := FenToYuan(tc.fen)
if math.Abs(yuan-expectedYuan) > 1e-9 {
t.Errorf("round-trip: FenToYuan(%d) = %f, want %f", fen, yuan, expectedYuan)
}
}
}

View File

@ -0,0 +1,98 @@
package payment
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
"strings"
)
// Encrypt encrypts plaintext using AES-256-GCM with the given 32-byte key.
// The output format is "iv:authTag:ciphertext" where each component is base64-encoded,
// matching the Node.js crypto.ts format for cross-compatibility.
func Encrypt(plaintext string, key []byte) (string, error) {
if len(key) != 32 {
return "", fmt.Errorf("encryption key must be 32 bytes, got %d", len(key))
}
block, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("create AES cipher: %w", err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", fmt.Errorf("create GCM: %w", err)
}
nonce := make([]byte, gcm.NonceSize()) // 12 bytes for GCM
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", fmt.Errorf("generate nonce: %w", err)
}
// Seal appends the ciphertext + auth tag
sealed := gcm.Seal(nil, nonce, []byte(plaintext), nil)
// Split sealed into ciphertext and auth tag (last 16 bytes)
tagSize := gcm.Overhead()
ciphertext := sealed[:len(sealed)-tagSize]
authTag := sealed[len(sealed)-tagSize:]
// Format: iv:authTag:ciphertext (all base64)
return fmt.Sprintf("%s:%s:%s",
base64.StdEncoding.EncodeToString(nonce),
base64.StdEncoding.EncodeToString(authTag),
base64.StdEncoding.EncodeToString(ciphertext),
), nil
}
// Decrypt decrypts a ciphertext string produced by Encrypt.
// The input format is "iv:authTag:ciphertext" where each component is base64-encoded.
func Decrypt(ciphertext string, key []byte) (string, error) {
if len(key) != 32 {
return "", fmt.Errorf("encryption key must be 32 bytes, got %d", len(key))
}
parts := strings.SplitN(ciphertext, ":", 3)
if len(parts) != 3 {
return "", fmt.Errorf("invalid ciphertext format: expected iv:authTag:ciphertext")
}
nonce, err := base64.StdEncoding.DecodeString(parts[0])
if err != nil {
return "", fmt.Errorf("decode IV: %w", err)
}
authTag, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil {
return "", fmt.Errorf("decode auth tag: %w", err)
}
encrypted, err := base64.StdEncoding.DecodeString(parts[2])
if err != nil {
return "", fmt.Errorf("decode ciphertext: %w", err)
}
block, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("create AES cipher: %w", err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", fmt.Errorf("create GCM: %w", err)
}
// Reconstruct the sealed data: ciphertext + authTag
sealed := append(encrypted, authTag...)
plaintext, err := gcm.Open(nil, nonce, sealed, nil)
if err != nil {
return "", fmt.Errorf("decrypt: %w", err)
}
return string(plaintext), nil
}

View File

@ -0,0 +1,183 @@
package payment
import (
"crypto/rand"
"strings"
"testing"
)
func makeKey(t *testing.T) []byte {
t.Helper()
key := make([]byte, 32)
if _, err := rand.Read(key); err != nil {
t.Fatalf("generate random key: %v", err)
}
return key
}
func TestEncryptDecryptRoundTrip(t *testing.T) {
t.Parallel()
key := makeKey(t)
plaintexts := []string{
"hello world",
"short",
"a longer string with special chars: !@#$%^&*()",
`{"key":"value","num":42}`,
"你好世界 unicode test 🎉",
strings.Repeat("x", 10000),
}
for _, pt := range plaintexts {
encrypted, err := Encrypt(pt, key)
if err != nil {
t.Fatalf("Encrypt(%q) error: %v", pt[:min(len(pt), 30)], err)
}
decrypted, err := Decrypt(encrypted, key)
if err != nil {
t.Fatalf("Decrypt error for plaintext %q: %v", pt[:min(len(pt), 30)], err)
}
if decrypted != pt {
t.Fatalf("round-trip failed: got %q, want %q", decrypted[:min(len(decrypted), 30)], pt[:min(len(pt), 30)])
}
}
}
func TestEncryptProducesDifferentCiphertexts(t *testing.T) {
t.Parallel()
key := makeKey(t)
ct1, err := Encrypt("same plaintext", key)
if err != nil {
t.Fatalf("first Encrypt error: %v", err)
}
ct2, err := Encrypt("same plaintext", key)
if err != nil {
t.Fatalf("second Encrypt error: %v", err)
}
if ct1 == ct2 {
t.Fatal("two encryptions of the same plaintext should produce different ciphertexts (random nonce)")
}
}
func TestDecryptWithWrongKeyFails(t *testing.T) {
t.Parallel()
key1 := makeKey(t)
key2 := makeKey(t)
encrypted, err := Encrypt("secret data", key1)
if err != nil {
t.Fatalf("Encrypt error: %v", err)
}
_, err = Decrypt(encrypted, key2)
if err == nil {
t.Fatal("Decrypt with wrong key should fail, but got nil error")
}
}
func TestEncryptRejectsInvalidKeyLength(t *testing.T) {
t.Parallel()
badKeys := [][]byte{
nil,
make([]byte, 0),
make([]byte, 16),
make([]byte, 31),
make([]byte, 33),
make([]byte, 64),
}
for _, key := range badKeys {
_, err := Encrypt("test", key)
if err == nil {
t.Fatalf("Encrypt should reject key of length %d", len(key))
}
}
}
func TestDecryptRejectsInvalidKeyLength(t *testing.T) {
t.Parallel()
badKeys := [][]byte{
nil,
make([]byte, 16),
make([]byte, 33),
}
for _, key := range badKeys {
_, err := Decrypt("dummydata:dummydata:dummydata", key)
if err == nil {
t.Fatalf("Decrypt should reject key of length %d", len(key))
}
}
}
func TestEncryptEmptyPlaintext(t *testing.T) {
t.Parallel()
key := makeKey(t)
encrypted, err := Encrypt("", key)
if err != nil {
t.Fatalf("Encrypt empty plaintext error: %v", err)
}
decrypted, err := Decrypt(encrypted, key)
if err != nil {
t.Fatalf("Decrypt empty plaintext error: %v", err)
}
if decrypted != "" {
t.Fatalf("expected empty string, got %q", decrypted)
}
}
func TestEncryptDecryptUnicodeJSON(t *testing.T) {
t.Parallel()
key := makeKey(t)
jsonContent := `{"name":"测试用户","email":"test@example.com","balance":100.50}`
encrypted, err := Encrypt(jsonContent, key)
if err != nil {
t.Fatalf("Encrypt JSON error: %v", err)
}
decrypted, err := Decrypt(encrypted, key)
if err != nil {
t.Fatalf("Decrypt JSON error: %v", err)
}
if decrypted != jsonContent {
t.Fatalf("JSON round-trip failed: got %q, want %q", decrypted, jsonContent)
}
}
func TestDecryptInvalidFormat(t *testing.T) {
t.Parallel()
key := makeKey(t)
invalidInputs := []string{
"",
"nodelimiter",
"only:two",
"invalid:base64:!!!",
}
for _, input := range invalidInputs {
_, err := Decrypt(input, key)
if err == nil {
t.Fatalf("Decrypt(%q) should fail but got nil error", input)
}
}
}
func TestCiphertextFormat(t *testing.T) {
t.Parallel()
key := makeKey(t)
encrypted, err := Encrypt("test", key)
if err != nil {
t.Fatalf("Encrypt error: %v", err)
}
parts := strings.SplitN(encrypted, ":", 3)
if len(parts) != 3 {
t.Fatalf("ciphertext should have format iv:authTag:ciphertext, got %d parts", len(parts))
}
for i, part := range parts {
if part == "" {
t.Fatalf("ciphertext part %d is empty", i)
}
}
}

View File

@ -0,0 +1,19 @@
package payment
import (
"github.com/shopspring/decimal"
)
// CalculatePayAmount computes the total pay amount given a recharge amount and
// fee rate (percentage). Fee = amount * feeRate / 100, rounded UP (away from zero)
// to 2 decimal places. The returned string is formatted to exactly 2 decimal places.
// If feeRate <= 0, the amount is returned as-is (formatted to 2 decimal places).
func CalculatePayAmount(rechargeAmount float64, feeRate float64) string {
amount := decimal.NewFromFloat(rechargeAmount)
if feeRate <= 0 {
return amount.StringFixed(2)
}
rate := decimal.NewFromFloat(feeRate)
fee := amount.Mul(rate).Div(decimal.NewFromInt(100)).RoundUp(2)
return amount.Add(fee).StringFixed(2)
}

View File

@ -0,0 +1,111 @@
package payment
import (
"testing"
)
func TestCalculatePayAmount(t *testing.T) {
t.Parallel()
tests := []struct {
name string
amount float64
feeRate float64
expected string
}{
{
name: "zero fee rate returns same amount",
amount: 100.00,
feeRate: 0,
expected: "100.00",
},
{
name: "negative fee rate returns same amount",
amount: 50.00,
feeRate: -5,
expected: "50.00",
},
{
name: "1 percent fee rate",
amount: 100.00,
feeRate: 1,
expected: "101.00",
},
{
name: "5 percent fee on 200",
amount: 200.00,
feeRate: 5,
expected: "210.00",
},
{
name: "fee rounds UP to 2 decimal places",
amount: 100.00,
feeRate: 3,
expected: "103.00",
},
{
name: "fee rounds UP small remainder",
amount: 10.00,
feeRate: 3.33,
expected: "10.34", // 10 * 3.33 / 100 = 0.333 -> round up -> 0.34
},
{
name: "very small amount",
amount: 0.01,
feeRate: 1,
expected: "0.02", // 0.01 * 1/100 = 0.0001 -> round up -> 0.01 -> total 0.02
},
{
name: "large amount",
amount: 99999.99,
feeRate: 10,
expected: "109999.99", // 99999.99 * 10/100 = 9999.999 -> round up -> 10000.00 -> total 109999.99
},
{
name: "100 percent fee rate doubles amount",
amount: 50.00,
feeRate: 100,
expected: "100.00",
},
{
name: "precision 0.01 fee difference",
amount: 100.00,
feeRate: 1.01,
expected: "101.01", // 100 * 1.01/100 = 1.01
},
{
name: "precision 0.02 fee",
amount: 100.00,
feeRate: 1.02,
expected: "101.02",
},
{
name: "zero amount with positive fee",
amount: 0,
feeRate: 5,
expected: "0.00",
},
{
name: "fractional amount no fee",
amount: 19.99,
feeRate: 0,
expected: "19.99",
},
{
name: "fractional fee that causes rounding up",
amount: 33.33,
feeRate: 7.77,
expected: "35.92", // 33.33 * 7.77 / 100 = 2.589741 -> round up -> 2.59 -> total 35.92
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := CalculatePayAmount(tt.amount, tt.feeRate)
if got != tt.expected {
t.Fatalf("CalculatePayAmount(%v, %v) = %q, want %q", tt.amount, tt.feeRate, got, tt.expected)
}
})
}
}

View File

@ -0,0 +1,328 @@
package payment
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"strings"
"sync/atomic"
"time"
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
)
// Strategy represents a load balancing strategy for provider instance selection.
type Strategy string
const (
StrategyRoundRobin Strategy = "round-robin"
StrategyLeastAmount Strategy = "least-amount"
)
// ChannelLimits holds limits for a single payment channel within a provider instance.
type ChannelLimits struct {
DailyLimit float64 `json:"dailyLimit,omitempty"`
SingleMin float64 `json:"singleMin,omitempty"`
SingleMax float64 `json:"singleMax,omitempty"`
}
// InstanceLimits holds per-channel limits for a provider instance (JSON).
type InstanceLimits map[string]ChannelLimits
// LoadBalancer selects a provider instance for a given payment type.
type LoadBalancer interface {
GetInstanceConfig(ctx context.Context, instanceID int64) (map[string]string, error)
SelectInstance(ctx context.Context, providerKey string, paymentType PaymentType, strategy Strategy, orderAmount float64) (*InstanceSelection, error)
}
// DefaultLoadBalancer implements LoadBalancer using database queries.
type DefaultLoadBalancer struct {
db *dbent.Client
encryptionKey []byte
counter atomic.Uint64
}
// NewDefaultLoadBalancer creates a new load balancer.
func NewDefaultLoadBalancer(db *dbent.Client, encryptionKey []byte) *DefaultLoadBalancer {
return &DefaultLoadBalancer{db: db, encryptionKey: encryptionKey}
}
// instanceCandidate pairs an instance with its pre-fetched daily usage.
type instanceCandidate struct {
inst *dbent.PaymentProviderInstance
dailyUsed float64 // includes PENDING orders
}
// SelectInstance picks an enabled instance for the given provider key and payment type.
//
// Flow:
// 1. Query all enabled instances for providerKey, filter by supported paymentType
// 2. Batch-query daily usage (PENDING + PAID + COMPLETED + RECHARGING) for all candidates
// 3. Filter out instances where: single-min/max violated OR daily remaining < orderAmount
// 4. Pick from survivors using the configured strategy (round-robin / least-amount)
// 5. If all filtered out, fall back to full list (let the provider itself reject)
func (lb *DefaultLoadBalancer) SelectInstance(
ctx context.Context,
providerKey string,
paymentType PaymentType,
strategy Strategy,
orderAmount float64,
) (*InstanceSelection, error) {
// Step 1: query enabled instances matching payment type.
instances, err := lb.queryEnabledInstances(ctx, providerKey, paymentType)
if err != nil {
return nil, err
}
// Step 2: batch-fetch daily usage for all candidates.
candidates := lb.attachDailyUsage(ctx, instances)
// Step 3: filter by limits.
available := filterByLimits(candidates, paymentType, orderAmount)
if len(available) == 0 {
slog.Warn("all instances exceeded limits, using full candidate list",
"provider", providerKey, "payment_type", paymentType,
"order_amount", orderAmount, "count", len(candidates))
available = candidates
}
// Step 4: pick by strategy.
selected := lb.pickByStrategy(available, strategy)
return lb.buildSelection(selected.inst)
}
// queryEnabledInstances returns enabled instances for providerKey that support paymentType.
func (lb *DefaultLoadBalancer) queryEnabledInstances(
ctx context.Context,
providerKey string,
paymentType PaymentType,
) ([]*dbent.PaymentProviderInstance, error) {
instances, err := lb.db.PaymentProviderInstance.Query().
Where(
paymentproviderinstance.ProviderKey(providerKey),
paymentproviderinstance.Enabled(true),
).
Order(dbent.Asc(paymentproviderinstance.FieldSortOrder)).
All(ctx)
if err != nil {
return nil, fmt.Errorf("query provider instances: %w", err)
}
var matched []*dbent.PaymentProviderInstance
for _, inst := range instances {
if paymentType == providerKey || InstanceSupportsType(inst.SupportedTypes, paymentType) {
matched = append(matched, inst)
}
}
if len(matched) == 0 {
return nil, fmt.Errorf("no enabled instance for provider %s type %s", providerKey, paymentType)
}
return matched, nil
}
// attachDailyUsage queries daily usage for each instance in a single pass.
// Usage includes PENDING orders to avoid over-committing capacity.
func (lb *DefaultLoadBalancer) attachDailyUsage(
ctx context.Context,
instances []*dbent.PaymentProviderInstance,
) []instanceCandidate {
todayStart := startOfDay(time.Now())
// Collect instance IDs.
ids := make([]string, len(instances))
for i, inst := range instances {
ids[i] = fmt.Sprintf("%d", inst.ID)
}
// Batch query: sum pay_amount grouped by provider_instance_id.
type row struct {
InstanceID string `json:"provider_instance_id"`
Sum float64 `json:"sum"`
}
var rows []row
err := lb.db.PaymentOrder.Query().
Where(
paymentorder.ProviderInstanceIDIn(ids...),
paymentorder.StatusIn(
OrderStatusPending, OrderStatusPaid,
OrderStatusCompleted, OrderStatusRecharging,
),
paymentorder.CreatedAtGTE(todayStart),
).
GroupBy(paymentorder.FieldProviderInstanceID).
Aggregate(dbent.Sum(paymentorder.FieldPayAmount)).
Scan(ctx, &rows)
if err != nil {
slog.Warn("batch daily usage query failed, treating all as zero", "error", err)
}
usageMap := make(map[string]float64, len(rows))
for _, r := range rows {
usageMap[r.InstanceID] = r.Sum
}
candidates := make([]instanceCandidate, len(instances))
for i, inst := range instances {
candidates[i] = instanceCandidate{
inst: inst,
dailyUsed: usageMap[fmt.Sprintf("%d", inst.ID)],
}
}
return candidates
}
// filterByLimits removes instances that cannot accommodate the order:
// - orderAmount outside single-transaction [min, max]
// - daily remaining capacity (limit - used) < orderAmount
func filterByLimits(candidates []instanceCandidate, paymentType PaymentType, orderAmount float64) []instanceCandidate {
var result []instanceCandidate
for _, c := range candidates {
cl := getInstanceChannelLimits(c.inst, paymentType)
if cl.SingleMin > 0 && orderAmount < cl.SingleMin {
slog.Info("order below instance single min, skipping",
"instance_id", c.inst.ID, "order", orderAmount, "min", cl.SingleMin)
continue
}
if cl.SingleMax > 0 && orderAmount > cl.SingleMax {
slog.Info("order above instance single max, skipping",
"instance_id", c.inst.ID, "order", orderAmount, "max", cl.SingleMax)
continue
}
if cl.DailyLimit > 0 && c.dailyUsed+orderAmount > cl.DailyLimit {
slog.Info("instance daily remaining insufficient, skipping",
"instance_id", c.inst.ID, "used", c.dailyUsed,
"order", orderAmount, "limit", cl.DailyLimit)
continue
}
result = append(result, c)
}
return result
}
// getInstanceChannelLimits returns the channel limits for a specific payment type.
func getInstanceChannelLimits(inst *dbent.PaymentProviderInstance, paymentType PaymentType) ChannelLimits {
if inst.Limits == "" {
return ChannelLimits{}
}
var limits InstanceLimits
if err := json.Unmarshal([]byte(inst.Limits), &limits); err != nil {
return ChannelLimits{}
}
// For Stripe, limits are stored under the provider key "stripe".
lookupKey := paymentType
if inst.ProviderKey == "stripe" {
lookupKey = "stripe"
}
if cl, ok := limits[lookupKey]; ok {
return cl
}
return ChannelLimits{}
}
// pickByStrategy selects one instance from the available candidates.
func (lb *DefaultLoadBalancer) pickByStrategy(candidates []instanceCandidate, strategy Strategy) instanceCandidate {
if strategy == StrategyLeastAmount && len(candidates) > 1 {
return pickLeastAmount(candidates)
}
// Default: round-robin.
idx := lb.counter.Add(1) % uint64(len(candidates))
return candidates[idx]
}
// pickLeastAmount selects the instance with the lowest daily usage.
// No extra DB queries — usage was pre-fetched in attachDailyUsage.
func pickLeastAmount(candidates []instanceCandidate) instanceCandidate {
best := candidates[0]
for _, c := range candidates[1:] {
if c.dailyUsed < best.dailyUsed {
best = c
}
}
return best
}
func (lb *DefaultLoadBalancer) buildSelection(selected *dbent.PaymentProviderInstance) (*InstanceSelection, error) {
config, err := lb.decryptConfig(selected.Config)
if err != nil {
return nil, fmt.Errorf("decrypt instance %d config: %w", selected.ID, err)
}
if selected.PaymentMode != "" {
config["paymentMode"] = selected.PaymentMode
}
return &InstanceSelection{
InstanceID: fmt.Sprintf("%d", selected.ID),
Config: config,
SupportedTypes: selected.SupportedTypes,
PaymentMode: selected.PaymentMode,
}, nil
}
func (lb *DefaultLoadBalancer) decryptConfig(encrypted string) (map[string]string, error) {
plaintext, err := Decrypt(encrypted, lb.encryptionKey)
if err != nil {
return nil, err
}
var config map[string]string
if err := json.Unmarshal([]byte(plaintext), &config); err != nil {
return nil, fmt.Errorf("unmarshal config: %w", err)
}
return config, nil
}
// GetInstanceDailyAmount returns the total completed order amount for an instance today.
func (lb *DefaultLoadBalancer) GetInstanceDailyAmount(ctx context.Context, instanceID string) (float64, error) {
todayStart := startOfDay(time.Now())
var result []struct {
Sum float64 `json:"sum"`
}
err := lb.db.PaymentOrder.Query().
Where(
paymentorder.ProviderInstanceID(instanceID),
paymentorder.StatusIn(OrderStatusCompleted, OrderStatusPaid, OrderStatusRecharging),
paymentorder.PaidAtGTE(todayStart),
).
Aggregate(dbent.Sum(paymentorder.FieldPayAmount)).
Scan(ctx, &result)
if err != nil {
return 0, fmt.Errorf("query daily amount: %w", err)
}
if len(result) > 0 {
return result[0].Sum, nil
}
return 0, nil
}
func startOfDay(t time.Time) time.Time {
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
}
// InstanceSupportsType checks if the given supported types string includes the target type.
// An empty supportedTypes string means all types are supported.
func InstanceSupportsType(supportedTypes string, target PaymentType) bool {
if supportedTypes == "" {
return true
}
for _, t := range strings.Split(supportedTypes, ",") {
if strings.TrimSpace(t) == target {
return true
}
}
return false
}
// GetInstanceConfig decrypts and returns the configuration for a provider instance by ID.
func (lb *DefaultLoadBalancer) GetInstanceConfig(ctx context.Context, instanceID int64) (map[string]string, error) {
inst, err := lb.db.PaymentProviderInstance.Get(ctx, instanceID)
if err != nil {
return nil, fmt.Errorf("get instance %d: %w", instanceID, err)
}
return lb.decryptConfig(inst.Config)
}

View File

@ -0,0 +1,474 @@
//go:build unit
package payment
import (
"encoding/json"
"testing"
"time"
dbent "github.com/Wei-Shaw/sub2api/ent"
)
func TestInstanceSupportsType(t *testing.T) {
t.Parallel()
tests := []struct {
name string
supportedTypes string
target PaymentType
expected bool
}{
{
name: "exact match single type",
supportedTypes: "alipay",
target: "alipay",
expected: true,
},
{
name: "no match single type",
supportedTypes: "wxpay",
target: "alipay",
expected: false,
},
{
name: "match in comma-separated list",
supportedTypes: "alipay,wxpay,stripe",
target: "wxpay",
expected: true,
},
{
name: "first in comma-separated list",
supportedTypes: "alipay,wxpay",
target: "alipay",
expected: true,
},
{
name: "last in comma-separated list",
supportedTypes: "alipay,wxpay,stripe",
target: "stripe",
expected: true,
},
{
name: "no match in comma-separated list",
supportedTypes: "alipay,wxpay",
target: "stripe",
expected: false,
},
{
name: "empty target",
supportedTypes: "alipay,wxpay",
target: "",
expected: false,
},
{
name: "types with spaces are trimmed",
supportedTypes: " alipay , wxpay ",
target: "alipay",
expected: true,
},
{
name: "partial match should not succeed",
supportedTypes: "alipay_direct",
target: "alipay",
expected: false,
},
{
name: "empty supported types means all supported",
supportedTypes: "",
target: "alipay",
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := InstanceSupportsType(tt.supportedTypes, tt.target)
if got != tt.expected {
t.Fatalf("InstanceSupportsType(%q, %q) = %v, want %v", tt.supportedTypes, tt.target, got, tt.expected)
}
})
}
}
// ---------------------------------------------------------------------------
// Helper to build test PaymentProviderInstance values
// ---------------------------------------------------------------------------
func testInstance(id int64, providerKey, limits string) *dbent.PaymentProviderInstance {
return &dbent.PaymentProviderInstance{
ID: id,
ProviderKey: providerKey,
Limits: limits,
Enabled: true,
}
}
// makeLimitsJSON builds a limits JSON string for a single payment type.
func makeLimitsJSON(paymentType string, cl ChannelLimits) string {
m := map[string]ChannelLimits{paymentType: cl}
b, _ := json.Marshal(m)
return string(b)
}
// ---------------------------------------------------------------------------
// filterByLimits
// ---------------------------------------------------------------------------
func TestFilterByLimits(t *testing.T) {
t.Parallel()
tests := []struct {
name string
candidates []instanceCandidate
paymentType PaymentType
orderAmount float64
wantIDs []int64 // expected surviving instance IDs
}{
{
name: "order below SingleMin is filtered out",
candidates: []instanceCandidate{
{inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMin: 10})), dailyUsed: 0},
},
paymentType: "alipay",
orderAmount: 5,
wantIDs: nil,
},
{
name: "order at exact SingleMin boundary passes",
candidates: []instanceCandidate{
{inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMin: 10})), dailyUsed: 0},
},
paymentType: "alipay",
orderAmount: 10,
wantIDs: []int64{1},
},
{
name: "order above SingleMax is filtered out",
candidates: []instanceCandidate{
{inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMax: 100})), dailyUsed: 0},
},
paymentType: "alipay",
orderAmount: 150,
wantIDs: nil,
},
{
name: "order at exact SingleMax boundary passes",
candidates: []instanceCandidate{
{inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMax: 100})), dailyUsed: 0},
},
paymentType: "alipay",
orderAmount: 100,
wantIDs: []int64{1},
},
{
name: "daily used + orderAmount exceeding dailyLimit is filtered out",
candidates: []instanceCandidate{
{inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{DailyLimit: 500})), dailyUsed: 480},
},
paymentType: "alipay",
orderAmount: 30,
wantIDs: nil, // 480+30=510 > 500
},
{
name: "daily used + orderAmount equal to dailyLimit passes (strict greater-than)",
candidates: []instanceCandidate{
{inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{DailyLimit: 500})), dailyUsed: 480},
},
paymentType: "alipay",
orderAmount: 20,
wantIDs: []int64{1}, // 480+20=500, 500 > 500 is false → passes
},
{
name: "daily used + orderAmount below dailyLimit passes",
candidates: []instanceCandidate{
{inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{DailyLimit: 500})), dailyUsed: 400},
},
paymentType: "alipay",
orderAmount: 50,
wantIDs: []int64{1},
},
{
name: "no limits configured passes through",
candidates: []instanceCandidate{
{inst: testInstance(1, "easypay", ""), dailyUsed: 99999},
},
paymentType: "alipay",
orderAmount: 100,
wantIDs: []int64{1},
},
{
name: "multiple candidates with partial filtering",
candidates: []instanceCandidate{
// singleMax=50, order=80 → filtered out
{inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMax: 50})), dailyUsed: 0},
// no limits → passes
{inst: testInstance(2, "easypay", ""), dailyUsed: 0},
// singleMin=100, order=80 → filtered out
{inst: testInstance(3, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMin: 100})), dailyUsed: 0},
// daily limit ok → passes (500+80=580 < 1000)
{inst: testInstance(4, "easypay", makeLimitsJSON("alipay", ChannelLimits{DailyLimit: 1000})), dailyUsed: 500},
},
paymentType: "alipay",
orderAmount: 80,
wantIDs: []int64{2, 4},
},
{
name: "zero SingleMin and SingleMax means no single-transaction limit",
candidates: []instanceCandidate{
{inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMin: 0, SingleMax: 0, DailyLimit: 0})), dailyUsed: 0},
},
paymentType: "alipay",
orderAmount: 99999,
wantIDs: []int64{1},
},
{
name: "all limits combined - order passes all checks",
candidates: []instanceCandidate{
{inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMin: 10, SingleMax: 200, DailyLimit: 1000})), dailyUsed: 500},
},
paymentType: "alipay",
orderAmount: 50,
wantIDs: []int64{1},
},
{
name: "all limits combined - order fails SingleMin",
candidates: []instanceCandidate{
{inst: testInstance(1, "easypay", makeLimitsJSON("alipay", ChannelLimits{SingleMin: 10, SingleMax: 200, DailyLimit: 1000})), dailyUsed: 500},
},
paymentType: "alipay",
orderAmount: 5,
wantIDs: nil,
},
{
name: "empty candidates returns empty",
candidates: nil,
paymentType: "alipay",
orderAmount: 10,
wantIDs: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := filterByLimits(tt.candidates, tt.paymentType, tt.orderAmount)
gotIDs := make([]int64, len(got))
for i, c := range got {
gotIDs[i] = c.inst.ID
}
if !int64SliceEqual(gotIDs, tt.wantIDs) {
t.Fatalf("filterByLimits() returned IDs %v, want %v", gotIDs, tt.wantIDs)
}
})
}
}
// ---------------------------------------------------------------------------
// pickLeastAmount
// ---------------------------------------------------------------------------
func TestPickLeastAmount(t *testing.T) {
t.Parallel()
t.Run("picks candidate with lowest dailyUsed", func(t *testing.T) {
t.Parallel()
candidates := []instanceCandidate{
{inst: testInstance(1, "easypay", ""), dailyUsed: 300},
{inst: testInstance(2, "easypay", ""), dailyUsed: 100},
{inst: testInstance(3, "easypay", ""), dailyUsed: 200},
}
got := pickLeastAmount(candidates)
if got.inst.ID != 2 {
t.Fatalf("pickLeastAmount() picked instance %d, want 2", got.inst.ID)
}
})
t.Run("with equal dailyUsed picks the first one", func(t *testing.T) {
t.Parallel()
candidates := []instanceCandidate{
{inst: testInstance(1, "easypay", ""), dailyUsed: 100},
{inst: testInstance(2, "easypay", ""), dailyUsed: 100},
{inst: testInstance(3, "easypay", ""), dailyUsed: 200},
}
got := pickLeastAmount(candidates)
if got.inst.ID != 1 {
t.Fatalf("pickLeastAmount() picked instance %d, want 1 (first with lowest)", got.inst.ID)
}
})
t.Run("single candidate returns that candidate", func(t *testing.T) {
t.Parallel()
candidates := []instanceCandidate{
{inst: testInstance(42, "easypay", ""), dailyUsed: 999},
}
got := pickLeastAmount(candidates)
if got.inst.ID != 42 {
t.Fatalf("pickLeastAmount() picked instance %d, want 42", got.inst.ID)
}
})
t.Run("zero usage among non-zero picks zero", func(t *testing.T) {
t.Parallel()
candidates := []instanceCandidate{
{inst: testInstance(1, "easypay", ""), dailyUsed: 500},
{inst: testInstance(2, "easypay", ""), dailyUsed: 0},
{inst: testInstance(3, "easypay", ""), dailyUsed: 300},
}
got := pickLeastAmount(candidates)
if got.inst.ID != 2 {
t.Fatalf("pickLeastAmount() picked instance %d, want 2", got.inst.ID)
}
})
}
// ---------------------------------------------------------------------------
// getInstanceChannelLimits
// ---------------------------------------------------------------------------
func TestGetInstanceChannelLimits(t *testing.T) {
t.Parallel()
tests := []struct {
name string
inst *dbent.PaymentProviderInstance
paymentType PaymentType
want ChannelLimits
}{
{
name: "empty limits string returns zero ChannelLimits",
inst: testInstance(1, "easypay", ""),
paymentType: "alipay",
want: ChannelLimits{},
},
{
name: "invalid JSON returns zero ChannelLimits",
inst: testInstance(1, "easypay", "not-json{"),
paymentType: "alipay",
want: ChannelLimits{},
},
{
name: "valid JSON with matching payment type",
inst: testInstance(1, "easypay",
`{"alipay":{"singleMin":5,"singleMax":200,"dailyLimit":1000}}`),
paymentType: "alipay",
want: ChannelLimits{SingleMin: 5, SingleMax: 200, DailyLimit: 1000},
},
{
name: "payment type not in limits returns zero ChannelLimits",
inst: testInstance(1, "easypay",
`{"alipay":{"singleMin":5,"singleMax":200}}`),
paymentType: "wxpay",
want: ChannelLimits{},
},
{
name: "stripe provider uses stripe lookup key regardless of payment type",
inst: testInstance(1, "stripe",
`{"stripe":{"singleMin":10,"singleMax":500,"dailyLimit":5000}}`),
paymentType: "alipay",
want: ChannelLimits{SingleMin: 10, SingleMax: 500, DailyLimit: 5000},
},
{
name: "stripe provider ignores payment type key even if present",
inst: testInstance(1, "stripe",
`{"stripe":{"singleMin":10,"singleMax":500},"alipay":{"singleMin":1,"singleMax":100}}`),
paymentType: "alipay",
want: ChannelLimits{SingleMin: 10, SingleMax: 500},
},
{
name: "non-stripe provider uses payment type as lookup key",
inst: testInstance(1, "easypay",
`{"alipay":{"singleMin":5},"wxpay":{"singleMin":10}}`),
paymentType: "wxpay",
want: ChannelLimits{SingleMin: 10},
},
{
name: "valid JSON with partial limits (only dailyLimit)",
inst: testInstance(1, "easypay",
`{"alipay":{"dailyLimit":800}}`),
paymentType: "alipay",
want: ChannelLimits{DailyLimit: 800},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := getInstanceChannelLimits(tt.inst, tt.paymentType)
if got != tt.want {
t.Fatalf("getInstanceChannelLimits() = %+v, want %+v", got, tt.want)
}
})
}
}
// ---------------------------------------------------------------------------
// startOfDay
// ---------------------------------------------------------------------------
func TestStartOfDay(t *testing.T) {
t.Parallel()
tests := []struct {
name string
in time.Time
want time.Time
}{
{
name: "midday returns midnight of same day",
in: time.Date(2025, 6, 15, 14, 30, 45, 123456789, time.UTC),
want: time.Date(2025, 6, 15, 0, 0, 0, 0, time.UTC),
},
{
name: "midnight returns same time",
in: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
want: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
},
{
name: "last second of day returns midnight of same day",
in: time.Date(2025, 12, 31, 23, 59, 59, 999999999, time.UTC),
want: time.Date(2025, 12, 31, 0, 0, 0, 0, time.UTC),
},
{
name: "preserves timezone location",
in: time.Date(2025, 3, 10, 15, 0, 0, 0, time.FixedZone("CST", 8*3600)),
want: time.Date(2025, 3, 10, 0, 0, 0, 0, time.FixedZone("CST", 8*3600)),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := startOfDay(tt.in)
if !got.Equal(tt.want) {
t.Fatalf("startOfDay(%v) = %v, want %v", tt.in, got, tt.want)
}
// Also verify location is preserved.
if got.Location().String() != tt.want.Location().String() {
t.Fatalf("startOfDay() location = %v, want %v", got.Location(), tt.want.Location())
}
})
}
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
// int64SliceEqual compares two int64 slices for equality.
// Both nil and empty slices are treated as equal.
func int64SliceEqual(a, b []int64) bool {
if len(a) == 0 && len(b) == 0 {
return true
}
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}

View File

@ -0,0 +1,279 @@
package provider
import (
"context"
"fmt"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/Wei-Shaw/sub2api/internal/payment"
"github.com/smartwalle/alipay/v3"
)
// Alipay product codes.
const (
alipayProductCodePagePay = "FAST_INSTANT_TRADE_PAY"
alipayProductCodeWapPay = "QUICK_WAP_WAY"
)
// Alipay response constants.
const (
alipayFundChangeYes = "Y"
alipayErrTradeNotExist = "ACQ.TRADE_NOT_EXIST"
alipayRefundSuffix = "-refund"
)
// Alipay implements payment.Provider and payment.CancelableProvider using the smartwalle/alipay SDK.
type Alipay struct {
instanceID string
config map[string]string // appId, privateKey, publicKey (or alipayPublicKey), notifyUrl, returnUrl
mu sync.Mutex
client *alipay.Client
}
// NewAlipay creates a new Alipay provider instance.
func NewAlipay(instanceID string, config map[string]string) (*Alipay, error) {
required := []string{"appId", "privateKey"}
for _, k := range required {
if config[k] == "" {
return nil, fmt.Errorf("alipay config missing required key: %s", k)
}
}
return &Alipay{
instanceID: instanceID,
config: config,
}, nil
}
func (a *Alipay) getClient() (*alipay.Client, error) {
a.mu.Lock()
defer a.mu.Unlock()
if a.client != nil {
return a.client, nil
}
client, err := alipay.New(a.config["appId"], a.config["privateKey"], true)
if err != nil {
return nil, fmt.Errorf("alipay init client: %w", err)
}
pubKey := a.config["publicKey"]
if pubKey == "" {
pubKey = a.config["alipayPublicKey"]
}
if pubKey == "" {
return nil, fmt.Errorf("alipay config missing required key: publicKey (or alipayPublicKey)")
}
if err := client.LoadAliPayPublicKey(pubKey); err != nil {
return nil, fmt.Errorf("alipay load public key: %w", err)
}
a.client = client
return a.client, nil
}
func (a *Alipay) Name() string { return "Alipay" }
func (a *Alipay) ProviderKey() string { return payment.TypeAlipay }
func (a *Alipay) SupportedTypes() []payment.PaymentType {
return []payment.PaymentType{payment.TypeAlipayDirect}
}
// CreatePayment creates an Alipay payment page URL.
func (a *Alipay) CreatePayment(_ context.Context, req payment.CreatePaymentRequest) (*payment.CreatePaymentResponse, error) {
client, err := a.getClient()
if err != nil {
return nil, err
}
notifyURL := a.config["notifyUrl"]
if req.NotifyURL != "" {
notifyURL = req.NotifyURL
}
returnURL := a.config["returnUrl"]
if req.ReturnURL != "" {
returnURL = req.ReturnURL
}
if req.IsMobile {
return a.createTrade(client, req, notifyURL, returnURL, true)
}
return a.createTrade(client, req, notifyURL, returnURL, false)
}
func (a *Alipay) createTrade(client *alipay.Client, req payment.CreatePaymentRequest, notifyURL, returnURL string, isMobile bool) (*payment.CreatePaymentResponse, error) {
if isMobile {
param := alipay.TradeWapPay{}
param.OutTradeNo = req.OrderID
param.TotalAmount = req.Amount
param.Subject = req.Subject
param.ProductCode = alipayProductCodeWapPay
param.NotifyURL = notifyURL
param.ReturnURL = returnURL
payURL, err := client.TradeWapPay(param)
if err != nil {
return nil, fmt.Errorf("alipay TradeWapPay: %w", err)
}
return &payment.CreatePaymentResponse{
TradeNo: req.OrderID,
PayURL: payURL.String(),
}, nil
}
param := alipay.TradePagePay{}
param.OutTradeNo = req.OrderID
param.TotalAmount = req.Amount
param.Subject = req.Subject
param.ProductCode = alipayProductCodePagePay
param.NotifyURL = notifyURL
param.ReturnURL = returnURL
payURL, err := client.TradePagePay(param)
if err != nil {
return nil, fmt.Errorf("alipay TradePagePay: %w", err)
}
return &payment.CreatePaymentResponse{
TradeNo: req.OrderID,
PayURL: payURL.String(),
QRCode: payURL.String(),
}, nil
}
// QueryOrder queries the trade status via Alipay.
func (a *Alipay) QueryOrder(ctx context.Context, tradeNo string) (*payment.QueryOrderResponse, error) {
client, err := a.getClient()
if err != nil {
return nil, err
}
result, err := client.TradeQuery(ctx, alipay.TradeQuery{OutTradeNo: tradeNo})
if err != nil {
if isTradeNotExist(err) {
return &payment.QueryOrderResponse{
TradeNo: tradeNo,
Status: payment.ProviderStatusPending,
}, nil
}
return nil, fmt.Errorf("alipay TradeQuery: %w", err)
}
status := payment.ProviderStatusPending
switch result.TradeStatus {
case alipay.TradeStatusSuccess, alipay.TradeStatusFinished:
status = payment.ProviderStatusPaid
case alipay.TradeStatusClosed:
status = payment.ProviderStatusFailed
}
amount, err := strconv.ParseFloat(result.TotalAmount, 64)
if err != nil {
return nil, fmt.Errorf("alipay parse amount %q: %w", result.TotalAmount, err)
}
return &payment.QueryOrderResponse{
TradeNo: result.TradeNo,
Status: status,
Amount: amount,
PaidAt: result.SendPayDate,
}, nil
}
// VerifyNotification decodes and verifies an Alipay async notification.
func (a *Alipay) VerifyNotification(ctx context.Context, rawBody string, _ map[string]string) (*payment.PaymentNotification, error) {
client, err := a.getClient()
if err != nil {
return nil, err
}
values, err := url.ParseQuery(rawBody)
if err != nil {
return nil, fmt.Errorf("alipay parse notification: %w", err)
}
notification, err := client.DecodeNotification(ctx, values)
if err != nil {
return nil, fmt.Errorf("alipay verify notification: %w", err)
}
status := payment.ProviderStatusFailed
if notification.TradeStatus == alipay.TradeStatusSuccess || notification.TradeStatus == alipay.TradeStatusFinished {
status = payment.ProviderStatusSuccess
}
amount, err := strconv.ParseFloat(notification.TotalAmount, 64)
if err != nil {
return nil, fmt.Errorf("alipay parse notification amount %q: %w", notification.TotalAmount, err)
}
return &payment.PaymentNotification{
TradeNo: notification.TradeNo,
OrderID: notification.OutTradeNo,
Amount: amount,
Status: status,
RawData: rawBody,
}, nil
}
// Refund requests a refund through Alipay.
func (a *Alipay) Refund(ctx context.Context, req payment.RefundRequest) (*payment.RefundResponse, error) {
client, err := a.getClient()
if err != nil {
return nil, err
}
result, err := client.TradeRefund(ctx, alipay.TradeRefund{
OutTradeNo: req.OrderID,
RefundAmount: req.Amount,
RefundReason: req.Reason,
OutRequestNo: fmt.Sprintf("%s-refund-%d", req.OrderID, time.Now().UnixNano()),
})
if err != nil {
return nil, fmt.Errorf("alipay TradeRefund: %w", err)
}
refundStatus := payment.ProviderStatusPending
if result.FundChange == alipayFundChangeYes {
refundStatus = payment.ProviderStatusSuccess
}
refundID := result.TradeNo
if refundID == "" {
refundID = req.OrderID + alipayRefundSuffix
}
return &payment.RefundResponse{
RefundID: refundID,
Status: refundStatus,
}, nil
}
// CancelPayment closes a pending trade on Alipay.
func (a *Alipay) CancelPayment(ctx context.Context, tradeNo string) error {
client, err := a.getClient()
if err != nil {
return err
}
_, err = client.TradeClose(ctx, alipay.TradeClose{OutTradeNo: tradeNo})
if err != nil {
if isTradeNotExist(err) {
return nil
}
return fmt.Errorf("alipay TradeClose: %w", err)
}
return nil
}
func isTradeNotExist(err error) bool {
if err == nil {
return false
}
return strings.Contains(err.Error(), alipayErrTradeNotExist)
}
// Ensure interface compliance.
var (
_ payment.Provider = (*Alipay)(nil)
_ payment.CancelableProvider = (*Alipay)(nil)
)

View File

@ -0,0 +1,132 @@
//go:build unit
package provider
import (
"errors"
"strings"
"testing"
)
func TestIsTradeNotExist(t *testing.T) {
t.Parallel()
tests := []struct {
name string
err error
want bool
}{
{
name: "nil error returns false",
err: nil,
want: false,
},
{
name: "error containing ACQ.TRADE_NOT_EXIST returns true",
err: errors.New("alipay: sub_code=ACQ.TRADE_NOT_EXIST, sub_msg=交易不存在"),
want: true,
},
{
name: "error not containing the code returns false",
err: errors.New("alipay: sub_code=ACQ.SYSTEM_ERROR, sub_msg=系统错误"),
want: false,
},
{
name: "error with only partial match returns false",
err: errors.New("ACQ.TRADE_NOT"),
want: false,
},
{
name: "error with exact constant value returns true",
err: errors.New(alipayErrTradeNotExist),
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := isTradeNotExist(tt.err)
if got != tt.want {
t.Errorf("isTradeNotExist(%v) = %v, want %v", tt.err, got, tt.want)
}
})
}
}
func TestNewAlipay(t *testing.T) {
t.Parallel()
validConfig := map[string]string{
"appId": "2021001234567890",
"privateKey": "MIIEvQIBADANBgkqhkiG9w0BAQEFAASC...",
}
// helper to clone and override config fields
withOverride := func(overrides map[string]string) map[string]string {
cfg := make(map[string]string, len(validConfig))
for k, v := range validConfig {
cfg[k] = v
}
for k, v := range overrides {
cfg[k] = v
}
return cfg
}
tests := []struct {
name string
config map[string]string
wantErr bool
errSubstr string
}{
{
name: "valid config succeeds",
config: validConfig,
wantErr: false,
},
{
name: "missing appId",
config: withOverride(map[string]string{"appId": ""}),
wantErr: true,
errSubstr: "appId",
},
{
name: "missing privateKey",
config: withOverride(map[string]string{"privateKey": ""}),
wantErr: true,
errSubstr: "privateKey",
},
{
name: "nil config map returns error for appId",
config: map[string]string{},
wantErr: true,
errSubstr: "appId",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := NewAlipay("test-instance", tt.config)
if tt.wantErr {
if err == nil {
t.Fatal("expected error, got nil")
}
if tt.errSubstr != "" && !strings.Contains(err.Error(), tt.errSubstr) {
t.Errorf("error %q should contain %q", err.Error(), tt.errSubstr)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got == nil {
t.Fatal("expected non-nil Alipay instance")
}
if got.instanceID != "test-instance" {
t.Errorf("instanceID = %q, want %q", got.instanceID, "test-instance")
}
})
}
}

View File

@ -0,0 +1,288 @@
// Package provider contains concrete payment provider implementations.
package provider
import (
"context"
"crypto/hmac"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
"github.com/Wei-Shaw/sub2api/internal/payment"
)
// EasyPay constants.
const (
easypayCodeSuccess = 1
easypayStatusPaid = 1
easypayHTTPTimeout = 10 * time.Second
maxEasypayResponseSize = 1 << 20 // 1MB
tradeStatusSuccess = "TRADE_SUCCESS"
signTypeMD5 = "MD5"
paymentModePopup = "popup"
deviceMobile = "mobile"
)
// EasyPay implements payment.Provider for the EasyPay aggregation platform.
type EasyPay struct {
instanceID string
config map[string]string
httpClient *http.Client
}
// NewEasyPay creates a new EasyPay provider.
// config keys: pid, pkey, apiBase, notifyUrl, returnUrl, cid, cidAlipay, cidWxpay
func NewEasyPay(instanceID string, config map[string]string) (*EasyPay, error) {
for _, k := range []string{"pid", "pkey", "apiBase", "notifyUrl", "returnUrl"} {
if config[k] == "" {
return nil, fmt.Errorf("easypay config missing required key: %s", k)
}
}
return &EasyPay{
instanceID: instanceID,
config: config,
httpClient: &http.Client{Timeout: easypayHTTPTimeout},
}, nil
}
func (e *EasyPay) Name() string { return "EasyPay" }
func (e *EasyPay) ProviderKey() string { return payment.TypeEasyPay }
func (e *EasyPay) SupportedTypes() []payment.PaymentType {
return []payment.PaymentType{payment.TypeAlipay, payment.TypeWxpay}
}
func (e *EasyPay) CreatePayment(ctx context.Context, req payment.CreatePaymentRequest) (*payment.CreatePaymentResponse, error) {
// Payment mode determined by instance config, not payment type.
// "popup" → hosted page (submit.php); "qrcode"/default → API call (mapi.php).
mode := e.config["paymentMode"]
if mode == paymentModePopup {
return e.createRedirectPayment(req)
}
return e.createAPIPayment(ctx, req)
}
// createRedirectPayment builds a submit.php URL for browser redirect.
// No server-side API call — the user is redirected to EasyPay's hosted page.
// TradeNo is empty; it arrives via the notify callback after payment.
func (e *EasyPay) createRedirectPayment(req payment.CreatePaymentRequest) (*payment.CreatePaymentResponse, error) {
notifyURL, returnURL := e.resolveURLs(req)
params := map[string]string{
"pid": e.config["pid"], "type": req.PaymentType,
"out_trade_no": req.OrderID, "notify_url": notifyURL,
"return_url": returnURL, "name": req.Subject,
"money": req.Amount,
}
if cid := e.resolveCID(req.PaymentType); cid != "" {
params["cid"] = cid
}
if req.IsMobile {
params["device"] = deviceMobile
}
params["sign"] = easyPaySign(params, e.config["pkey"])
params["sign_type"] = signTypeMD5
q := url.Values{}
for k, v := range params {
q.Set(k, v)
}
base := strings.TrimRight(e.config["apiBase"], "/")
payURL := base + "/submit.php?" + q.Encode()
return &payment.CreatePaymentResponse{PayURL: payURL}, nil
}
// createAPIPayment calls mapi.php to get payurl/qrcode (existing behavior).
func (e *EasyPay) createAPIPayment(ctx context.Context, req payment.CreatePaymentRequest) (*payment.CreatePaymentResponse, error) {
notifyURL, returnURL := e.resolveURLs(req)
params := map[string]string{
"pid": e.config["pid"], "type": req.PaymentType,
"out_trade_no": req.OrderID, "notify_url": notifyURL,
"return_url": returnURL, "name": req.Subject,
"money": req.Amount, "clientip": req.ClientIP,
}
if cid := e.resolveCID(req.PaymentType); cid != "" {
params["cid"] = cid
}
if req.IsMobile {
params["device"] = deviceMobile
}
params["sign"] = easyPaySign(params, e.config["pkey"])
params["sign_type"] = signTypeMD5
body, err := e.post(ctx, strings.TrimRight(e.config["apiBase"], "/")+"/mapi.php", params)
if err != nil {
return nil, fmt.Errorf("easypay create: %w", err)
}
var resp struct {
Code int `json:"code"`
Msg string `json:"msg"`
TradeNo string `json:"trade_no"`
PayURL string `json:"payurl"`
PayURL2 string `json:"payurl2"` // H5 mobile payment URL
QRCode string `json:"qrcode"`
}
if err := json.Unmarshal(body, &resp); err != nil {
return nil, fmt.Errorf("easypay parse: %w", err)
}
if resp.Code != easypayCodeSuccess {
return nil, fmt.Errorf("easypay error: %s", resp.Msg)
}
payURL := resp.PayURL
if req.IsMobile && resp.PayURL2 != "" {
payURL = resp.PayURL2
}
return &payment.CreatePaymentResponse{TradeNo: resp.TradeNo, PayURL: payURL, QRCode: resp.QRCode}, nil
}
// resolveURLs returns (notifyURL, returnURL) preferring request values,
// falling back to instance config.
func (e *EasyPay) resolveURLs(req payment.CreatePaymentRequest) (string, string) {
notifyURL := req.NotifyURL
if notifyURL == "" {
notifyURL = e.config["notifyUrl"]
}
returnURL := req.ReturnURL
if returnURL == "" {
returnURL = e.config["returnUrl"]
}
return notifyURL, returnURL
}
func (e *EasyPay) QueryOrder(ctx context.Context, tradeNo string) (*payment.QueryOrderResponse, error) {
params := map[string]string{
"act": "order", "pid": e.config["pid"],
"key": e.config["pkey"], "out_trade_no": tradeNo,
}
body, err := e.post(ctx, e.config["apiBase"]+"/api.php", params)
if err != nil {
return nil, fmt.Errorf("easypay query: %w", err)
}
var resp struct {
Code int `json:"code"`
Msg string `json:"msg"`
Status int `json:"status"`
Money string `json:"money"`
}
if err := json.Unmarshal(body, &resp); err != nil {
return nil, fmt.Errorf("easypay parse query: %w", err)
}
status := payment.ProviderStatusPending
if resp.Status == easypayStatusPaid {
status = payment.ProviderStatusPaid
}
amount, _ := strconv.ParseFloat(resp.Money, 64)
return &payment.QueryOrderResponse{TradeNo: tradeNo, Status: status, Amount: amount}, nil
}
func (e *EasyPay) VerifyNotification(_ context.Context, rawBody string, _ map[string]string) (*payment.PaymentNotification, error) {
values, err := url.ParseQuery(rawBody)
if err != nil {
return nil, fmt.Errorf("parse notify: %w", err)
}
// url.ParseQuery already decodes values — no additional decode needed.
params := make(map[string]string)
for k := range values {
params[k] = values.Get(k)
}
sign := params["sign"]
if sign == "" {
return nil, fmt.Errorf("missing sign")
}
if !easyPayVerifySign(params, e.config["pkey"], sign) {
return nil, fmt.Errorf("invalid signature")
}
status := payment.ProviderStatusFailed
if params["trade_status"] == tradeStatusSuccess {
status = payment.ProviderStatusSuccess
}
amount, _ := strconv.ParseFloat(params["money"], 64)
return &payment.PaymentNotification{
TradeNo: params["trade_no"], OrderID: params["out_trade_no"],
Amount: amount, Status: status, RawData: rawBody,
}, nil
}
func (e *EasyPay) Refund(ctx context.Context, req payment.RefundRequest) (*payment.RefundResponse, error) {
params := map[string]string{
"pid": e.config["pid"], "key": e.config["pkey"],
"trade_no": req.TradeNo, "out_trade_no": req.OrderID, "money": req.Amount,
}
body, err := e.post(ctx, e.config["apiBase"]+"/api.php?act=refund", params)
if err != nil {
return nil, fmt.Errorf("easypay refund: %w", err)
}
var resp struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
if err := json.Unmarshal(body, &resp); err != nil {
return nil, fmt.Errorf("easypay parse refund: %w", err)
}
if resp.Code != easypayCodeSuccess {
return nil, fmt.Errorf("easypay refund failed: %s", resp.Msg)
}
return &payment.RefundResponse{RefundID: req.TradeNo, Status: payment.ProviderStatusSuccess}, nil
}
func (e *EasyPay) resolveCID(paymentType string) string {
if strings.HasPrefix(paymentType, "alipay") {
if v := e.config["cidAlipay"]; v != "" {
return v
}
return e.config["cid"]
}
if v := e.config["cidWxpay"]; v != "" {
return v
}
return e.config["cid"]
}
func (e *EasyPay) post(ctx context.Context, endpoint string, params map[string]string) ([]byte, error) {
form := url.Values{}
for k, v := range params {
form.Set(k, v)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, strings.NewReader(form.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := e.httpClient.Do(req)
if err != nil {
return nil, err
}
defer func() { _ = resp.Body.Close() }()
return io.ReadAll(io.LimitReader(resp.Body, maxEasypayResponseSize))
}
func easyPaySign(params map[string]string, pkey string) string {
keys := make([]string, 0, len(params))
for k, v := range params {
if k == "sign" || k == "sign_type" || v == "" {
continue
}
keys = append(keys, k)
}
sort.Strings(keys)
var buf strings.Builder
for i, k := range keys {
if i > 0 {
_ = buf.WriteByte('&')
}
_, _ = buf.WriteString(k + "=" + params[k])
}
_, _ = buf.WriteString(pkey)
hash := md5.Sum([]byte(buf.String()))
return hex.EncodeToString(hash[:])
}
func easyPayVerifySign(params map[string]string, pkey string, sign string) bool {
return hmac.Equal([]byte(easyPaySign(params, pkey)), []byte(sign))
}

View File

@ -0,0 +1,180 @@
package provider
import (
"testing"
)
func TestEasyPaySignConsistentOutput(t *testing.T) {
t.Parallel()
params := map[string]string{
"pid": "1001",
"type": "alipay",
"out_trade_no": "ORDER123",
"name": "Test Product",
"money": "10.00",
}
pkey := "test_secret_key"
sign1 := easyPaySign(params, pkey)
sign2 := easyPaySign(params, pkey)
if sign1 != sign2 {
t.Fatalf("easyPaySign should be deterministic: %q != %q", sign1, sign2)
}
if len(sign1) != 32 {
t.Fatalf("MD5 hex should be 32 chars, got %d", len(sign1))
}
}
func TestEasyPaySignExcludesSignAndSignType(t *testing.T) {
t.Parallel()
pkey := "my_key"
base := map[string]string{
"pid": "1001",
"type": "alipay",
}
withSign := map[string]string{
"pid": "1001",
"type": "alipay",
"sign": "should_be_ignored",
"sign_type": "MD5",
}
signBase := easyPaySign(base, pkey)
signWithExtra := easyPaySign(withSign, pkey)
if signBase != signWithExtra {
t.Fatalf("sign and sign_type should be excluded: base=%q, withExtra=%q", signBase, signWithExtra)
}
}
func TestEasyPaySignExcludesEmptyValues(t *testing.T) {
t.Parallel()
pkey := "key123"
base := map[string]string{
"pid": "1001",
"type": "alipay",
}
withEmpty := map[string]string{
"pid": "1001",
"type": "alipay",
"device": "",
"clientip": "",
}
signBase := easyPaySign(base, pkey)
signWithEmpty := easyPaySign(withEmpty, pkey)
if signBase != signWithEmpty {
t.Fatalf("empty values should be excluded: base=%q, withEmpty=%q", signBase, signWithEmpty)
}
}
func TestEasyPayVerifySignValid(t *testing.T) {
t.Parallel()
params := map[string]string{
"pid": "1001",
"type": "alipay",
"out_trade_no": "ORDER456",
"money": "25.00",
}
pkey := "secret"
sign := easyPaySign(params, pkey)
// Add sign to params (as would come in a real callback)
params["sign"] = sign
params["sign_type"] = "MD5"
if !easyPayVerifySign(params, pkey, sign) {
t.Fatal("easyPayVerifySign should return true for a valid signature")
}
}
func TestEasyPayVerifySignTampered(t *testing.T) {
t.Parallel()
params := map[string]string{
"pid": "1001",
"type": "alipay",
"out_trade_no": "ORDER789",
"money": "50.00",
}
pkey := "secret"
sign := easyPaySign(params, pkey)
// Tamper with the amount
params["money"] = "99.99"
if easyPayVerifySign(params, pkey, sign) {
t.Fatal("easyPayVerifySign should return false for tampered params")
}
}
func TestEasyPayVerifySignWrongKey(t *testing.T) {
t.Parallel()
params := map[string]string{
"pid": "1001",
"type": "wxpay",
}
sign := easyPaySign(params, "correct_key")
if easyPayVerifySign(params, "wrong_key", sign) {
t.Fatal("easyPayVerifySign should return false with wrong key")
}
}
func TestEasyPaySignEmptyParams(t *testing.T) {
t.Parallel()
sign := easyPaySign(map[string]string{}, "key123")
if sign == "" {
t.Fatal("easyPaySign with empty params should still produce a hash")
}
if len(sign) != 32 {
t.Fatalf("MD5 hex should be 32 chars, got %d", len(sign))
}
}
func TestEasyPaySignSortOrder(t *testing.T) {
t.Parallel()
pkey := "test_key"
params1 := map[string]string{
"a": "1",
"b": "2",
"c": "3",
}
params2 := map[string]string{
"c": "3",
"a": "1",
"b": "2",
}
sign1 := easyPaySign(params1, pkey)
sign2 := easyPaySign(params2, pkey)
if sign1 != sign2 {
t.Fatalf("easyPaySign should be order-independent: %q != %q", sign1, sign2)
}
}
func TestEasyPayVerifySignWrongSignValue(t *testing.T) {
t.Parallel()
params := map[string]string{
"pid": "1001",
"type": "alipay",
}
pkey := "key"
if easyPayVerifySign(params, pkey, "00000000000000000000000000000000") {
t.Fatal("easyPayVerifySign should return false for an incorrect sign value")
}
}

View File

@ -0,0 +1,23 @@
package provider
import (
"fmt"
"github.com/Wei-Shaw/sub2api/internal/payment"
)
// CreateProvider creates a Provider from a provider key, instance ID and decrypted config.
func CreateProvider(providerKey string, instanceID string, config map[string]string) (payment.Provider, error) {
switch providerKey {
case payment.TypeEasyPay:
return NewEasyPay(instanceID, config)
case payment.TypeAlipay:
return NewAlipay(instanceID, config)
case payment.TypeWxpay:
return NewWxpay(instanceID, config)
case payment.TypeStripe:
return NewStripe(instanceID, config)
default:
return nil, fmt.Errorf("unknown provider key: %s", providerKey)
}
}

View File

@ -0,0 +1,262 @@
package provider
import (
"context"
"encoding/json"
"fmt"
"strings"
"sync"
"github.com/Wei-Shaw/sub2api/internal/payment"
stripe "github.com/stripe/stripe-go/v85"
"github.com/stripe/stripe-go/v85/webhook"
)
// Stripe constants.
const (
stripeCurrency = "cny"
stripeEventPaymentSuccess = "payment_intent.succeeded"
stripeEventPaymentFailed = "payment_intent.payment_failed"
)
// Stripe implements the payment.CancelableProvider interface for Stripe payments.
type Stripe struct {
instanceID string
config map[string]string
mu sync.Mutex
initialized bool
sc *stripe.Client
}
// NewStripe creates a new Stripe provider instance.
func NewStripe(instanceID string, config map[string]string) (*Stripe, error) {
if config["secretKey"] == "" {
return nil, fmt.Errorf("stripe config missing required key: secretKey")
}
return &Stripe{
instanceID: instanceID,
config: config,
}, nil
}
func (s *Stripe) ensureInit() {
s.mu.Lock()
defer s.mu.Unlock()
if !s.initialized {
s.sc = stripe.NewClient(s.config["secretKey"])
s.initialized = true
}
}
// GetPublishableKey returns the publishable key for frontend use.
func (s *Stripe) GetPublishableKey() string {
return s.config["publishableKey"]
}
func (s *Stripe) Name() string { return "Stripe" }
func (s *Stripe) ProviderKey() string { return payment.TypeStripe }
func (s *Stripe) SupportedTypes() []payment.PaymentType {
return []payment.PaymentType{payment.TypeStripe}
}
// stripePaymentMethodTypes maps our PaymentType to Stripe payment_method_types.
var stripePaymentMethodTypes = map[string][]string{
payment.TypeCard: {"card"},
payment.TypeAlipay: {"alipay"},
payment.TypeWxpay: {"wechat_pay"},
payment.TypeLink: {"link"},
}
// CreatePayment creates a Stripe PaymentIntent.
func (s *Stripe) CreatePayment(ctx context.Context, req payment.CreatePaymentRequest) (*payment.CreatePaymentResponse, error) {
s.ensureInit()
amountInCents, err := payment.YuanToFen(req.Amount)
if err != nil {
return nil, fmt.Errorf("stripe create payment: %w", err)
}
// Collect all Stripe payment_method_types from the instance's configured sub-methods
methods := resolveStripeMethodTypes(req.InstanceSubMethods)
pmTypes := make([]*string, len(methods))
for i, m := range methods {
pmTypes[i] = stripe.String(m)
}
params := &stripe.PaymentIntentCreateParams{
Amount: stripe.Int64(amountInCents),
Currency: stripe.String(stripeCurrency),
PaymentMethodTypes: pmTypes,
Description: stripe.String(req.Subject),
Metadata: map[string]string{"orderId": req.OrderID},
}
// WeChat Pay requires payment_method_options with client type
if hasStripeMethod(methods, "wechat_pay") {
params.PaymentMethodOptions = &stripe.PaymentIntentCreatePaymentMethodOptionsParams{
WeChatPay: &stripe.PaymentIntentCreatePaymentMethodOptionsWeChatPayParams{
Client: stripe.String("web"),
},
}
}
params.SetIdempotencyKey(fmt.Sprintf("pi-%s", req.OrderID))
params.Context = ctx
pi, err := s.sc.V1PaymentIntents.Create(ctx, params)
if err != nil {
return nil, fmt.Errorf("stripe create payment: %w", err)
}
return &payment.CreatePaymentResponse{
TradeNo: pi.ID,
ClientSecret: pi.ClientSecret,
}, nil
}
// QueryOrder retrieves a PaymentIntent by ID.
func (s *Stripe) QueryOrder(ctx context.Context, tradeNo string) (*payment.QueryOrderResponse, error) {
s.ensureInit()
pi, err := s.sc.V1PaymentIntents.Retrieve(ctx, tradeNo, nil)
if err != nil {
return nil, fmt.Errorf("stripe query order: %w", err)
}
status := payment.ProviderStatusPending
switch pi.Status {
case stripe.PaymentIntentStatusSucceeded:
status = payment.ProviderStatusPaid
case stripe.PaymentIntentStatusCanceled:
status = payment.ProviderStatusFailed
}
return &payment.QueryOrderResponse{
TradeNo: pi.ID,
Status: status,
Amount: payment.FenToYuan(pi.Amount),
}, nil
}
// VerifyNotification verifies a Stripe webhook event.
func (s *Stripe) VerifyNotification(_ context.Context, rawBody string, headers map[string]string) (*payment.PaymentNotification, error) {
s.ensureInit()
webhookSecret := s.config["webhookSecret"]
if webhookSecret == "" {
return nil, fmt.Errorf("stripe webhookSecret not configured")
}
sig := headers["stripe-signature"]
if sig == "" {
return nil, fmt.Errorf("stripe notification missing stripe-signature header")
}
event, err := webhook.ConstructEvent([]byte(rawBody), sig, webhookSecret)
if err != nil {
return nil, fmt.Errorf("stripe verify notification: %w", err)
}
switch event.Type {
case stripeEventPaymentSuccess:
return parseStripePaymentIntent(&event, payment.ProviderStatusSuccess, rawBody)
case stripeEventPaymentFailed:
return parseStripePaymentIntent(&event, payment.ProviderStatusFailed, rawBody)
}
return nil, nil
}
func parseStripePaymentIntent(event *stripe.Event, status string, rawBody string) (*payment.PaymentNotification, error) {
var pi stripe.PaymentIntent
if err := json.Unmarshal(event.Data.Raw, &pi); err != nil {
return nil, fmt.Errorf("stripe parse payment_intent: %w", err)
}
return &payment.PaymentNotification{
TradeNo: pi.ID,
OrderID: pi.Metadata["orderId"],
Amount: payment.FenToYuan(pi.Amount),
Status: status,
RawData: rawBody,
}, nil
}
// Refund creates a Stripe refund.
func (s *Stripe) Refund(ctx context.Context, req payment.RefundRequest) (*payment.RefundResponse, error) {
s.ensureInit()
amountInCents, err := payment.YuanToFen(req.Amount)
if err != nil {
return nil, fmt.Errorf("stripe refund: %w", err)
}
params := &stripe.RefundCreateParams{
PaymentIntent: stripe.String(req.TradeNo),
Amount: stripe.Int64(amountInCents),
Reason: stripe.String(string(stripe.RefundReasonRequestedByCustomer)),
}
params.Context = ctx
r, err := s.sc.V1Refunds.Create(ctx, params)
if err != nil {
return nil, fmt.Errorf("stripe refund: %w", err)
}
refundStatus := payment.ProviderStatusPending
if r.Status == stripe.RefundStatusSucceeded {
refundStatus = payment.ProviderStatusSuccess
}
return &payment.RefundResponse{
RefundID: r.ID,
Status: refundStatus,
}, nil
}
// resolveStripeMethodTypes converts instance supported_types (comma-separated)
// into Stripe API payment_method_types. Falls back to ["card"] if empty.
func resolveStripeMethodTypes(instanceSubMethods string) []string {
if instanceSubMethods == "" {
return []string{"card"}
}
var methods []string
for _, t := range strings.Split(instanceSubMethods, ",") {
t = strings.TrimSpace(t)
if mapped, ok := stripePaymentMethodTypes[t]; ok {
methods = append(methods, mapped...)
}
}
if len(methods) == 0 {
return []string{"card"}
}
return methods
}
// hasStripeMethod checks if the given Stripe method list contains the target method.
func hasStripeMethod(methods []string, target string) bool {
for _, m := range methods {
if m == target {
return true
}
}
return false
}
// CancelPayment cancels a pending PaymentIntent.
func (s *Stripe) CancelPayment(ctx context.Context, tradeNo string) error {
s.ensureInit()
_, err := s.sc.V1PaymentIntents.Cancel(ctx, tradeNo, nil)
if err != nil {
return fmt.Errorf("stripe cancel payment: %w", err)
}
return nil
}
// Ensure interface compliance.
var (
_ payment.Provider = (*Stripe)(nil)
_ payment.CancelableProvider = (*Stripe)(nil)
)

View File

@ -0,0 +1,350 @@
package provider
import (
"bytes"
"context"
"crypto/rsa"
"fmt"
"io"
"log/slog"
"net/http"
"strings"
"sync"
"time"
"github.com/Wei-Shaw/sub2api/internal/payment"
"github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
"github.com/wechatpay-apiv3/wechatpay-go/core/notify"
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/h5"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/native"
"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
"github.com/wechatpay-apiv3/wechatpay-go/utils"
)
// WeChat Pay constants.
const (
wxpayCurrency = "CNY"
wxpayH5Type = "Wap"
)
// WeChat Pay trade states.
const (
wxpayTradeStateSuccess = "SUCCESS"
wxpayTradeStateRefund = "REFUND"
wxpayTradeStateClosed = "CLOSED"
wxpayTradeStatePayError = "PAYERROR"
)
// WeChat Pay notification event types.
const (
wxpayEventTransactionSuccess = "TRANSACTION.SUCCESS"
)
// WeChat Pay error codes.
const (
wxpayErrNoAuth = "NO_AUTH"
)
type Wxpay struct {
instanceID string
config map[string]string
mu sync.Mutex
coreClient *core.Client
notifyHandler *notify.Handler
}
func NewWxpay(instanceID string, config map[string]string) (*Wxpay, error) {
required := []string{"appId", "mchId", "privateKey", "apiV3Key", "publicKey", "publicKeyId", "certSerial"}
for _, k := range required {
if config[k] == "" {
return nil, fmt.Errorf("wxpay config missing required key: %s", k)
}
}
if len(config["apiV3Key"]) != 32 {
return nil, fmt.Errorf("wxpay apiV3Key must be exactly 32 bytes, got %d", len(config["apiV3Key"]))
}
return &Wxpay{instanceID: instanceID, config: config}, nil
}
func (w *Wxpay) Name() string { return "Wxpay" }
func (w *Wxpay) ProviderKey() string { return payment.TypeWxpay }
func (w *Wxpay) SupportedTypes() []payment.PaymentType {
return []payment.PaymentType{payment.TypeWxpayDirect}
}
func formatPEM(key, keyType string) string {
key = strings.TrimSpace(key)
if strings.HasPrefix(key, "-----BEGIN") {
return key
}
return fmt.Sprintf("-----BEGIN %s-----\n%s\n-----END %s-----", keyType, key, keyType)
}
func (w *Wxpay) ensureClient() (*core.Client, error) {
w.mu.Lock()
defer w.mu.Unlock()
if w.coreClient != nil {
return w.coreClient, nil
}
privateKey, publicKey, err := w.loadKeyPair()
if err != nil {
return nil, err
}
certSerial := w.config["certSerial"]
verifier := verifiers.NewSHA256WithRSAPubkeyVerifier(w.config["publicKeyId"], *publicKey)
client, err := core.NewClient(context.Background(),
option.WithMerchantCredential(w.config["mchId"], certSerial, privateKey),
option.WithVerifier(verifier))
if err != nil {
return nil, fmt.Errorf("wxpay init client: %w", err)
}
handler, err := notify.NewRSANotifyHandler(w.config["apiV3Key"], verifier)
if err != nil {
return nil, fmt.Errorf("wxpay init notify handler: %w", err)
}
w.notifyHandler = handler
w.coreClient = client
return w.coreClient, nil
}
func (w *Wxpay) loadKeyPair() (*rsa.PrivateKey, *rsa.PublicKey, error) {
privateKey, err := utils.LoadPrivateKey(formatPEM(w.config["privateKey"], "PRIVATE KEY"))
if err != nil {
return nil, nil, fmt.Errorf("wxpay load private key: %w", err)
}
publicKey, err := utils.LoadPublicKey(formatPEM(w.config["publicKey"], "PUBLIC KEY"))
if err != nil {
return nil, nil, fmt.Errorf("wxpay load public key: %w", err)
}
return privateKey, publicKey, nil
}
func (w *Wxpay) CreatePayment(ctx context.Context, req payment.CreatePaymentRequest) (*payment.CreatePaymentResponse, error) {
client, err := w.ensureClient()
if err != nil {
return nil, err
}
// Request-first, config-fallback (consistent with EasyPay/Alipay)
notifyURL := req.NotifyURL
if notifyURL == "" {
notifyURL = w.config["notifyUrl"]
}
if notifyURL == "" {
return nil, fmt.Errorf("wxpay notifyUrl is required")
}
totalFen, err := payment.YuanToFen(req.Amount)
if err != nil {
return nil, fmt.Errorf("wxpay create payment: %w", err)
}
if req.IsMobile && req.ClientIP != "" {
resp, err := w.createOrder(ctx, client, req, notifyURL, totalFen, true)
if err == nil {
return resp, nil
}
if !strings.Contains(err.Error(), wxpayErrNoAuth) {
return nil, err
}
slog.Warn("wxpay H5 payment not authorized, falling back to native", "order", req.OrderID)
}
return w.createOrder(ctx, client, req, notifyURL, totalFen, false)
}
func (w *Wxpay) createOrder(ctx context.Context, c *core.Client, req payment.CreatePaymentRequest, notifyURL string, totalFen int64, useH5 bool) (*payment.CreatePaymentResponse, error) {
if useH5 {
return w.prepayH5(ctx, c, req, notifyURL, totalFen)
}
return w.prepayNative(ctx, c, req, notifyURL, totalFen)
}
func (w *Wxpay) prepayNative(ctx context.Context, c *core.Client, req payment.CreatePaymentRequest, notifyURL string, totalFen int64) (*payment.CreatePaymentResponse, error) {
svc := native.NativeApiService{Client: c}
cur := wxpayCurrency
resp, _, err := svc.Prepay(ctx, native.PrepayRequest{
Appid: core.String(w.config["appId"]), Mchid: core.String(w.config["mchId"]),
Description: core.String(req.Subject), OutTradeNo: core.String(req.OrderID),
NotifyUrl: core.String(notifyURL),
Amount: &native.Amount{Total: core.Int64(totalFen), Currency: &cur},
})
if err != nil {
return nil, fmt.Errorf("wxpay native prepay: %w", err)
}
codeURL := ""
if resp.CodeUrl != nil {
codeURL = *resp.CodeUrl
}
return &payment.CreatePaymentResponse{TradeNo: req.OrderID, QRCode: codeURL}, nil
}
func (w *Wxpay) prepayH5(ctx context.Context, c *core.Client, req payment.CreatePaymentRequest, notifyURL string, totalFen int64) (*payment.CreatePaymentResponse, error) {
svc := h5.H5ApiService{Client: c}
cur := wxpayCurrency
tp := wxpayH5Type
resp, _, err := svc.Prepay(ctx, h5.PrepayRequest{
Appid: core.String(w.config["appId"]), Mchid: core.String(w.config["mchId"]),
Description: core.String(req.Subject), OutTradeNo: core.String(req.OrderID),
NotifyUrl: core.String(notifyURL),
Amount: &h5.Amount{Total: core.Int64(totalFen), Currency: &cur},
SceneInfo: &h5.SceneInfo{PayerClientIp: core.String(req.ClientIP), H5Info: &h5.H5Info{Type: &tp}},
})
if err != nil {
return nil, fmt.Errorf("wxpay h5 prepay: %w", err)
}
h5URL := ""
if resp.H5Url != nil {
h5URL = *resp.H5Url
}
return &payment.CreatePaymentResponse{TradeNo: req.OrderID, PayURL: h5URL}, nil
}
func wxSV(s *string) string {
if s == nil {
return ""
}
return *s
}
func mapWxState(s string) string {
switch s {
case wxpayTradeStateSuccess:
return payment.ProviderStatusPaid
case wxpayTradeStateRefund:
return payment.ProviderStatusRefunded
case wxpayTradeStateClosed, wxpayTradeStatePayError:
return payment.ProviderStatusFailed
default:
return payment.ProviderStatusPending
}
}
func (w *Wxpay) QueryOrder(ctx context.Context, tradeNo string) (*payment.QueryOrderResponse, error) {
c, err := w.ensureClient()
if err != nil {
return nil, err
}
svc := native.NativeApiService{Client: c}
tx, _, err := svc.QueryOrderByOutTradeNo(ctx, native.QueryOrderByOutTradeNoRequest{
OutTradeNo: core.String(tradeNo), Mchid: core.String(w.config["mchId"]),
})
if err != nil {
return nil, fmt.Errorf("wxpay query order: %w", err)
}
var amt float64
if tx.Amount != nil && tx.Amount.Total != nil {
amt = payment.FenToYuan(*tx.Amount.Total)
}
id := tradeNo
if tx.TransactionId != nil {
id = *tx.TransactionId
}
pa := ""
if tx.SuccessTime != nil {
pa = *tx.SuccessTime
}
return &payment.QueryOrderResponse{TradeNo: id, Status: mapWxState(wxSV(tx.TradeState)), Amount: amt, PaidAt: pa}, nil
}
func (w *Wxpay) VerifyNotification(ctx context.Context, rawBody string, headers map[string]string) (*payment.PaymentNotification, error) {
if _, err := w.ensureClient(); err != nil {
return nil, err
}
r, err := http.NewRequestWithContext(ctx, http.MethodPost, "/", io.NopCloser(bytes.NewBufferString(rawBody)))
if err != nil {
return nil, fmt.Errorf("wxpay construct request: %w", err)
}
for k, v := range headers {
r.Header.Set(k, v)
}
var tx payments.Transaction
nr, err := w.notifyHandler.ParseNotifyRequest(ctx, r, &tx)
if err != nil {
return nil, fmt.Errorf("wxpay verify notification: %w", err)
}
if nr.EventType != wxpayEventTransactionSuccess {
return nil, nil
}
var amt float64
if tx.Amount != nil && tx.Amount.Total != nil {
amt = payment.FenToYuan(*tx.Amount.Total)
}
st := payment.ProviderStatusFailed
if wxSV(tx.TradeState) == wxpayTradeStateSuccess {
st = payment.ProviderStatusSuccess
}
return &payment.PaymentNotification{
TradeNo: wxSV(tx.TransactionId), OrderID: wxSV(tx.OutTradeNo),
Amount: amt, Status: st, RawData: rawBody,
}, nil
}
func (w *Wxpay) Refund(ctx context.Context, req payment.RefundRequest) (*payment.RefundResponse, error) {
c, err := w.ensureClient()
if err != nil {
return nil, err
}
rf, err := payment.YuanToFen(req.Amount)
if err != nil {
return nil, fmt.Errorf("wxpay refund amount: %w", err)
}
tf, err := w.queryOrderTotalFen(ctx, c, req.OrderID)
if err != nil {
return nil, err
}
rs := refunddomestic.RefundsApiService{Client: c}
cur := wxpayCurrency
res, _, err := rs.Create(ctx, refunddomestic.CreateRequest{
OutTradeNo: core.String(req.OrderID),
OutRefundNo: core.String(fmt.Sprintf("%s-refund-%d", req.OrderID, time.Now().UnixNano())),
Reason: core.String(req.Reason),
Amount: &refunddomestic.AmountReq{Refund: core.Int64(rf), Total: core.Int64(tf), Currency: &cur},
})
if err != nil {
return nil, fmt.Errorf("wxpay refund: %w", err)
}
rid := wxSV(res.RefundId)
if rid == "" {
rid = fmt.Sprintf("%s-refund", req.OrderID)
}
st := payment.ProviderStatusPending
if res.Status != nil && *res.Status == refunddomestic.STATUS_SUCCESS {
st = payment.ProviderStatusSuccess
}
return &payment.RefundResponse{RefundID: rid, Status: st}, nil
}
func (w *Wxpay) queryOrderTotalFen(ctx context.Context, c *core.Client, orderID string) (int64, error) {
svc := native.NativeApiService{Client: c}
tx, _, err := svc.QueryOrderByOutTradeNo(ctx, native.QueryOrderByOutTradeNoRequest{
OutTradeNo: core.String(orderID), Mchid: core.String(w.config["mchId"]),
})
if err != nil {
return 0, fmt.Errorf("wxpay refund query order: %w", err)
}
var tf int64
if tx.Amount != nil && tx.Amount.Total != nil {
tf = *tx.Amount.Total
}
return tf, nil
}
func (w *Wxpay) CancelPayment(ctx context.Context, tradeNo string) error {
c, err := w.ensureClient()
if err != nil {
return err
}
svc := native.NativeApiService{Client: c}
_, err = svc.CloseOrder(ctx, native.CloseOrderRequest{
OutTradeNo: core.String(tradeNo), Mchid: core.String(w.config["mchId"]),
})
if err != nil {
return fmt.Errorf("wxpay cancel payment: %w", err)
}
return nil
}
var (
_ payment.Provider = (*Wxpay)(nil)
_ payment.CancelableProvider = (*Wxpay)(nil)
)

View File

@ -0,0 +1,259 @@
//go:build unit
package provider
import (
"strings"
"testing"
"github.com/Wei-Shaw/sub2api/internal/payment"
)
func TestMapWxState(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
want string
}{
{
name: "SUCCESS maps to paid",
input: wxpayTradeStateSuccess,
want: payment.ProviderStatusPaid,
},
{
name: "REFUND maps to refunded",
input: wxpayTradeStateRefund,
want: payment.ProviderStatusRefunded,
},
{
name: "CLOSED maps to failed",
input: wxpayTradeStateClosed,
want: payment.ProviderStatusFailed,
},
{
name: "PAYERROR maps to failed",
input: wxpayTradeStatePayError,
want: payment.ProviderStatusFailed,
},
{
name: "unknown state maps to pending",
input: "NOTPAY",
want: payment.ProviderStatusPending,
},
{
name: "empty string maps to pending",
input: "",
want: payment.ProviderStatusPending,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := mapWxState(tt.input)
if got != tt.want {
t.Errorf("mapWxState(%q) = %q, want %q", tt.input, got, tt.want)
}
})
}
}
func TestWxSV(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input *string
want string
}{
{
name: "nil pointer returns empty string",
input: nil,
want: "",
},
{
name: "non-nil pointer returns value",
input: strPtr("hello"),
want: "hello",
},
{
name: "pointer to empty string returns empty string",
input: strPtr(""),
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := wxSV(tt.input)
if got != tt.want {
t.Errorf("wxSV() = %q, want %q", got, tt.want)
}
})
}
}
func strPtr(s string) *string {
return &s
}
func TestFormatPEM(t *testing.T) {
t.Parallel()
tests := []struct {
name string
key string
keyType string
want string
}{
{
name: "raw key gets wrapped with headers",
key: "MIIBIjANBgkqhki...",
keyType: "PUBLIC KEY",
want: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki...\n-----END PUBLIC KEY-----",
},
{
name: "already formatted key is returned as-is",
key: "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBg...\n-----END PRIVATE KEY-----",
keyType: "PRIVATE KEY",
want: "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBg...\n-----END PRIVATE KEY-----",
},
{
name: "key with leading/trailing whitespace is trimmed before check",
key: " \n MIIBIjANBgkqhki... \n ",
keyType: "PUBLIC KEY",
want: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki...\n-----END PUBLIC KEY-----",
},
{
name: "already formatted key with whitespace is trimmed and returned",
key: " -----BEGIN RSA PRIVATE KEY-----\ndata\n-----END RSA PRIVATE KEY----- ",
keyType: "RSA PRIVATE KEY",
want: "-----BEGIN RSA PRIVATE KEY-----\ndata\n-----END RSA PRIVATE KEY-----",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := formatPEM(tt.key, tt.keyType)
if got != tt.want {
t.Errorf("formatPEM(%q, %q) =\n%s\nwant:\n%s", tt.key, tt.keyType, got, tt.want)
}
})
}
}
func TestNewWxpay(t *testing.T) {
t.Parallel()
validConfig := map[string]string{
"appId": "wx1234567890",
"mchId": "1234567890",
"privateKey": "fake-private-key",
"apiV3Key": "12345678901234567890123456789012", // exactly 32 bytes
"publicKey": "fake-public-key",
"publicKeyId": "key-id-001",
"certSerial": "SERIAL001",
}
// helper to clone and override config fields
withOverride := func(overrides map[string]string) map[string]string {
cfg := make(map[string]string, len(validConfig))
for k, v := range validConfig {
cfg[k] = v
}
for k, v := range overrides {
cfg[k] = v
}
return cfg
}
tests := []struct {
name string
config map[string]string
wantErr bool
errSubstr string
}{
{
name: "valid config succeeds",
config: validConfig,
wantErr: false,
},
{
name: "missing appId",
config: withOverride(map[string]string{"appId": ""}),
wantErr: true,
errSubstr: "appId",
},
{
name: "missing mchId",
config: withOverride(map[string]string{"mchId": ""}),
wantErr: true,
errSubstr: "mchId",
},
{
name: "missing privateKey",
config: withOverride(map[string]string{"privateKey": ""}),
wantErr: true,
errSubstr: "privateKey",
},
{
name: "missing apiV3Key",
config: withOverride(map[string]string{"apiV3Key": ""}),
wantErr: true,
errSubstr: "apiV3Key",
},
{
name: "missing publicKey",
config: withOverride(map[string]string{"publicKey": ""}),
wantErr: true,
errSubstr: "publicKey",
},
{
name: "missing publicKeyId",
config: withOverride(map[string]string{"publicKeyId": ""}),
wantErr: true,
errSubstr: "publicKeyId",
},
{
name: "apiV3Key too short",
config: withOverride(map[string]string{"apiV3Key": "short"}),
wantErr: true,
errSubstr: "exactly 32 bytes",
},
{
name: "apiV3Key too long",
config: withOverride(map[string]string{"apiV3Key": "123456789012345678901234567890123"}), // 33 bytes
wantErr: true,
errSubstr: "exactly 32 bytes",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := NewWxpay("test-instance", tt.config)
if tt.wantErr {
if err == nil {
t.Fatal("expected error, got nil")
}
if tt.errSubstr != "" && !strings.Contains(err.Error(), tt.errSubstr) {
t.Errorf("error %q should contain %q", err.Error(), tt.errSubstr)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got == nil {
t.Fatal("expected non-nil Wxpay instance")
}
if got.instanceID != "test-instance" {
t.Errorf("instanceID = %q, want %q", got.instanceID, "test-instance")
}
})
}
}

View File

@ -0,0 +1,85 @@
package payment
import (
"sync"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
)
// Registry is a thread-safe registry mapping PaymentType to Provider.
type Registry struct {
mu sync.RWMutex
providers map[PaymentType]Provider
}
// ErrProviderNotFound is returned when a requested payment provider is not registered.
var ErrProviderNotFound = infraerrors.NotFound("PROVIDER_NOT_FOUND", "payment provider not registered")
// NewRegistry creates a new empty provider registry.
func NewRegistry() *Registry {
return &Registry{
providers: make(map[PaymentType]Provider),
}
}
// Register adds a provider for each of its supported payment types.
// If a type was previously registered, it is overwritten.
func (r *Registry) Register(p Provider) {
r.mu.Lock()
defer r.mu.Unlock()
for _, t := range p.SupportedTypes() {
r.providers[t] = p
}
}
// GetProvider returns the provider registered for the given payment type.
func (r *Registry) GetProvider(t PaymentType) (Provider, error) {
r.mu.RLock()
defer r.mu.RUnlock()
p, ok := r.providers[t]
if !ok {
return nil, ErrProviderNotFound
}
return p, nil
}
// GetProviderByKey returns the first provider whose ProviderKey matches the given key.
func (r *Registry) GetProviderByKey(key string) (Provider, error) {
r.mu.RLock()
defer r.mu.RUnlock()
for _, p := range r.providers {
if p.ProviderKey() == key {
return p, nil
}
}
return nil, ErrProviderNotFound
}
// GetProviderKey returns the provider key for the given payment type, or empty string if not found.
func (r *Registry) GetProviderKey(t PaymentType) string {
r.mu.RLock()
defer r.mu.RUnlock()
p, ok := r.providers[t]
if !ok {
return ""
}
return p.ProviderKey()
}
// SupportedTypes returns all currently registered payment types.
func (r *Registry) SupportedTypes() []PaymentType {
r.mu.RLock()
defer r.mu.RUnlock()
types := make([]PaymentType, 0, len(r.providers))
for t := range r.providers {
types = append(types, t)
}
return types
}
// Clear removes all registered providers.
func (r *Registry) Clear() {
r.mu.Lock()
defer r.mu.Unlock()
r.providers = make(map[PaymentType]Provider)
}

View File

@ -0,0 +1,234 @@
package payment
import (
"context"
"fmt"
"sync"
"testing"
)
// mockProvider implements the Provider interface for testing.
type mockProvider struct {
name string
key string
supportedTypes []PaymentType
}
func (m *mockProvider) Name() string { return m.name }
func (m *mockProvider) ProviderKey() string { return m.key }
func (m *mockProvider) SupportedTypes() []PaymentType { return m.supportedTypes }
func (m *mockProvider) CreatePayment(_ context.Context, _ CreatePaymentRequest) (*CreatePaymentResponse, error) {
return nil, nil
}
func (m *mockProvider) QueryOrder(_ context.Context, _ string) (*QueryOrderResponse, error) {
return nil, nil
}
func (m *mockProvider) VerifyNotification(_ context.Context, _ string, _ map[string]string) (*PaymentNotification, error) {
return nil, nil
}
func (m *mockProvider) Refund(_ context.Context, _ RefundRequest) (*RefundResponse, error) {
return nil, nil
}
func TestRegistryRegisterAndGetProvider(t *testing.T) {
t.Parallel()
r := NewRegistry()
p := &mockProvider{
name: "TestPay",
key: "testpay",
supportedTypes: []PaymentType{TypeAlipay, TypeWxpay},
}
r.Register(p)
got, err := r.GetProvider(TypeAlipay)
if err != nil {
t.Fatalf("GetProvider(alipay) error: %v", err)
}
if got.ProviderKey() != "testpay" {
t.Fatalf("GetProvider(alipay) key = %q, want %q", got.ProviderKey(), "testpay")
}
got2, err := r.GetProvider(TypeWxpay)
if err != nil {
t.Fatalf("GetProvider(wxpay) error: %v", err)
}
if got2.ProviderKey() != "testpay" {
t.Fatalf("GetProvider(wxpay) key = %q, want %q", got2.ProviderKey(), "testpay")
}
}
func TestRegistryGetProviderNotFound(t *testing.T) {
t.Parallel()
r := NewRegistry()
_, err := r.GetProvider("nonexistent")
if err == nil {
t.Fatal("GetProvider for unregistered type should return error")
}
}
func TestRegistryGetProviderByKey(t *testing.T) {
t.Parallel()
r := NewRegistry()
p := &mockProvider{
name: "EasyPay",
key: "easypay",
supportedTypes: []PaymentType{TypeAlipay},
}
r.Register(p)
got, err := r.GetProviderByKey("easypay")
if err != nil {
t.Fatalf("GetProviderByKey error: %v", err)
}
if got.Name() != "EasyPay" {
t.Fatalf("GetProviderByKey name = %q, want %q", got.Name(), "EasyPay")
}
}
func TestRegistryGetProviderByKeyNotFound(t *testing.T) {
t.Parallel()
r := NewRegistry()
_, err := r.GetProviderByKey("nonexistent")
if err == nil {
t.Fatal("GetProviderByKey for unknown key should return error")
}
}
func TestRegistryGetProviderKeyUnknownType(t *testing.T) {
t.Parallel()
r := NewRegistry()
key := r.GetProviderKey("unknown_type")
if key != "" {
t.Fatalf("GetProviderKey for unknown type should return empty, got %q", key)
}
}
func TestRegistryGetProviderKeyKnownType(t *testing.T) {
t.Parallel()
r := NewRegistry()
p := &mockProvider{
name: "Stripe",
key: "stripe",
supportedTypes: []PaymentType{TypeStripe},
}
r.Register(p)
key := r.GetProviderKey(TypeStripe)
if key != "stripe" {
t.Fatalf("GetProviderKey(stripe) = %q, want %q", key, "stripe")
}
}
func TestRegistrySupportedTypes(t *testing.T) {
t.Parallel()
r := NewRegistry()
p1 := &mockProvider{
name: "EasyPay",
key: "easypay",
supportedTypes: []PaymentType{TypeAlipay, TypeWxpay},
}
p2 := &mockProvider{
name: "Stripe",
key: "stripe",
supportedTypes: []PaymentType{TypeStripe},
}
r.Register(p1)
r.Register(p2)
types := r.SupportedTypes()
if len(types) != 3 {
t.Fatalf("SupportedTypes() len = %d, want 3", len(types))
}
typeSet := make(map[PaymentType]bool)
for _, tp := range types {
typeSet[tp] = true
}
for _, expected := range []PaymentType{TypeAlipay, TypeWxpay, TypeStripe} {
if !typeSet[expected] {
t.Fatalf("SupportedTypes() missing %q", expected)
}
}
}
func TestRegistrySupportedTypesEmpty(t *testing.T) {
t.Parallel()
r := NewRegistry()
types := r.SupportedTypes()
if len(types) != 0 {
t.Fatalf("SupportedTypes() on empty registry should be empty, got %d", len(types))
}
}
func TestRegistryOverwriteExisting(t *testing.T) {
t.Parallel()
r := NewRegistry()
p1 := &mockProvider{
name: "OldPay",
key: "old",
supportedTypes: []PaymentType{TypeAlipay},
}
p2 := &mockProvider{
name: "NewPay",
key: "new",
supportedTypes: []PaymentType{TypeAlipay},
}
r.Register(p1)
r.Register(p2)
got, err := r.GetProvider(TypeAlipay)
if err != nil {
t.Fatalf("GetProvider error: %v", err)
}
if got.Name() != "NewPay" {
t.Fatalf("expected overwritten provider, got %q", got.Name())
}
}
func TestRegistryConcurrentAccess(t *testing.T) {
t.Parallel()
r := NewRegistry()
const goroutines = 50
var wg sync.WaitGroup
wg.Add(goroutines * 2)
// Concurrent writers
for i := 0; i < goroutines; i++ {
go func(idx int) {
defer wg.Done()
p := &mockProvider{
name: fmt.Sprintf("Provider-%d", idx),
key: fmt.Sprintf("key-%d", idx),
supportedTypes: []PaymentType{PaymentType(fmt.Sprintf("type-%d", idx))},
}
r.Register(p)
}(i)
}
// Concurrent readers
for i := 0; i < goroutines; i++ {
go func() {
defer wg.Done()
_ = r.SupportedTypes()
_, _ = r.GetProvider("some-type")
_ = r.GetProviderKey("some-type")
}()
}
wg.Wait()
types := r.SupportedTypes()
if len(types) != goroutines {
t.Fatalf("after concurrent registration, expected %d types, got %d", goroutines, len(types))
}
}

View File

@ -0,0 +1,180 @@
// Package payment provides the core payment provider abstraction,
// registry, load balancing, and shared utilities for the payment subsystem.
package payment
import "context"
// PaymentType represents a supported payment method.
type PaymentType = string
// Supported payment type constants.
const (
TypeAlipay PaymentType = "alipay"
TypeWxpay PaymentType = "wxpay"
TypeAlipayDirect PaymentType = "alipay_direct"
TypeWxpayDirect PaymentType = "wxpay_direct"
TypeStripe PaymentType = "stripe"
TypeCard PaymentType = "card"
TypeLink PaymentType = "link"
TypeEasyPay PaymentType = "easypay"
)
// Order status constants shared across payment and service layers.
const (
OrderStatusPending = "PENDING"
OrderStatusPaid = "PAID"
OrderStatusRecharging = "RECHARGING"
OrderStatusCompleted = "COMPLETED"
OrderStatusExpired = "EXPIRED"
OrderStatusCancelled = "CANCELLED"
OrderStatusFailed = "FAILED"
OrderStatusRefundRequested = "REFUND_REQUESTED"
OrderStatusRefunding = "REFUNDING"
OrderStatusPartiallyRefunded = "PARTIALLY_REFUNDED"
OrderStatusRefunded = "REFUNDED"
OrderStatusRefundFailed = "REFUND_FAILED"
)
// Order types distinguish balance recharges from subscription purchases.
const (
OrderTypeBalance = "balance"
OrderTypeSubscription = "subscription"
)
// Entity statuses shared across users, groups, etc.
const (
EntityStatusActive = "active"
)
// Deduction types for refund flow.
const (
DeductionTypeBalance = "balance"
DeductionTypeSubscription = "subscription"
DeductionTypeNone = "none"
)
// Payment notification status values.
const (
NotificationStatusSuccess = "success"
NotificationStatusPaid = "paid"
)
// Provider-level status constants returned by provider implementations
// to the service layer (lowercase, distinct from OrderStatus uppercase constants).
const (
ProviderStatusPending = "pending"
ProviderStatusPaid = "paid"
ProviderStatusSuccess = "success"
ProviderStatusFailed = "failed"
ProviderStatusRefunded = "refunded"
)
// DefaultLoadBalanceStrategy is the default load-balancing strategy
// used when no strategy is configured.
const DefaultLoadBalanceStrategy = "round-robin"
// ConfigKeyPublishableKey is the config map key for Stripe's publishable key.
const ConfigKeyPublishableKey = "publishableKey"
// GetBasePaymentType extracts the base payment method from a composite key.
// For example, "alipay_direct" -> "alipay".
func GetBasePaymentType(t string) string {
switch {
case t == TypeEasyPay:
return TypeEasyPay
case t == TypeStripe || t == TypeCard || t == TypeLink:
return TypeStripe
case len(t) >= len(TypeAlipay) && t[:len(TypeAlipay)] == TypeAlipay:
return TypeAlipay
case len(t) >= len(TypeWxpay) && t[:len(TypeWxpay)] == TypeWxpay:
return TypeWxpay
default:
return t
}
}
// CreatePaymentRequest holds the parameters for creating a new payment.
type CreatePaymentRequest struct {
OrderID string // Internal order ID
Amount string // Pay amount in CNY (formatted to 2 decimal places)
PaymentType string // e.g. "alipay", "wxpay", "stripe"
Subject string // Product description
NotifyURL string // Webhook callback URL
ReturnURL string // Browser redirect URL after payment
ClientIP string // Payer's IP address
IsMobile bool // Whether the request comes from a mobile device
InstanceSubMethods string // Comma-separated sub-methods from instance supported_types (for Stripe)
}
// CreatePaymentResponse is returned after successfully initiating a payment.
type CreatePaymentResponse struct {
TradeNo string // Third-party transaction ID
PayURL string // H5 payment URL (alipay/wxpay)
QRCode string // QR code content for scanning
ClientSecret string // Stripe PaymentIntent client secret
}
// QueryOrderResponse describes the payment status from the upstream provider.
type QueryOrderResponse struct {
TradeNo string
Status string // "pending", "paid", "failed", "refunded"
Amount float64 // Amount in CNY
PaidAt string // RFC3339 timestamp or empty
}
// PaymentNotification is the parsed result of a webhook/notify callback.
type PaymentNotification struct {
TradeNo string
OrderID string
Amount float64
Status string // "success" or "failed"
RawData string // Raw notification body for audit
}
// RefundRequest contains the parameters for requesting a refund.
type RefundRequest struct {
TradeNo string
OrderID string
Amount string // Refund amount formatted to 2 decimal places
Reason string
}
// RefundResponse is returned after a refund request.
type RefundResponse struct {
RefundID string
Status string // "success", "pending", "failed"
}
// InstanceSelection holds the selected provider instance and its decrypted config.
type InstanceSelection struct {
InstanceID string
Config map[string]string
SupportedTypes string // Comma-separated list of supported payment types from the instance
PaymentMode string // Payment display mode: "qrcode", "redirect", "popup"
}
// Provider defines the interface that all payment providers must implement.
type Provider interface {
// Name returns a human-readable name for this provider.
Name() string
// ProviderKey returns the unique key identifying this provider type (e.g. "easypay").
ProviderKey() string
// SupportedTypes returns the list of payment types this provider handles.
SupportedTypes() []PaymentType
// CreatePayment initiates a payment and returns the upstream response.
CreatePayment(ctx context.Context, req CreatePaymentRequest) (*CreatePaymentResponse, error)
// QueryOrder queries the payment status of the given trade number.
QueryOrder(ctx context.Context, tradeNo string) (*QueryOrderResponse, error)
// VerifyNotification parses and verifies a webhook callback.
// Returns nil for unrecognized or irrelevant events (caller should return 200).
VerifyNotification(ctx context.Context, rawBody string, headers map[string]string) (*PaymentNotification, error)
// Refund requests a refund from the upstream provider.
Refund(ctx context.Context, req RefundRequest) (*RefundResponse, error)
}
// CancelableProvider extends Provider with the ability to cancel pending payments.
type CancelableProvider interface {
Provider
// CancelPayment cancels/expires a pending payment on the upstream platform.
CancelPayment(ctx context.Context, tradeNo string) error
}

View File

@ -0,0 +1,53 @@
package payment
import (
"encoding/hex"
"fmt"
"log/slog"
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/google/wire"
)
// EncryptionKey is a named type for the payment encryption key (AES-256, 32 bytes).
// Using a named type avoids Wire ambiguity with other []byte parameters.
type EncryptionKey []byte
// ProvideEncryptionKey derives the payment encryption key from the TOTP encryption key in config.
// When the key is empty, nil is returned (payment features that need encryption will be disabled).
// When the key is non-empty but invalid (bad hex or wrong length), an error is returned
// to prevent startup with a misconfigured encryption key.
func ProvideEncryptionKey(cfg *config.Config) (EncryptionKey, error) {
if cfg.Totp.EncryptionKey == "" {
slog.Warn("payment encryption key not configured — encrypted payment config will be unavailable")
return nil, nil
}
key, err := hex.DecodeString(cfg.Totp.EncryptionKey)
if err != nil {
return nil, fmt.Errorf("invalid payment encryption key (hex decode): %w", err)
}
if len(key) != 32 {
return nil, fmt.Errorf("payment encryption key must be 32 bytes, got %d", len(key))
}
return EncryptionKey(key), nil
}
// ProvideRegistry creates an empty payment provider registry.
// Providers are registered at runtime after application startup.
func ProvideRegistry() *Registry {
return NewRegistry()
}
// ProvideDefaultLoadBalancer creates a DefaultLoadBalancer backed by the ent client.
func ProvideDefaultLoadBalancer(client *dbent.Client, key EncryptionKey) *DefaultLoadBalancer {
return NewDefaultLoadBalancer(client, []byte(key))
}
// ProviderSet is the Wire provider set for the payment package.
var ProviderSet = wire.NewSet(
ProvideEncryptionKey,
ProvideRegistry,
ProvideDefaultLoadBalancer,
wire.Bind(new(LoadBalancer), new(*DefaultLoadBalancer)),
)

View File

@ -587,6 +587,24 @@ func TestAPIContracts(t *testing.T) {
"enable_cch_signing": false,
"enable_fingerprint_unification": true,
"enable_metadata_passthrough": false,
"payment_enabled": false,
"payment_min_amount": 0,
"payment_max_amount": 0,
"payment_daily_limit": 0,
"payment_order_timeout_minutes": 0,
"payment_max_pending_orders": 0,
"payment_enabled_types": null,
"payment_balance_disabled": false,
"payment_load_balance_strategy": "",
"payment_product_name_prefix": "",
"payment_product_name_suffix": "",
"payment_help_image_url": "",
"payment_help_text": "",
"payment_cancel_rate_limit_enabled": false,
"payment_cancel_rate_limit_max": 0,
"payment_cancel_rate_limit_window": 0,
"payment_cancel_rate_limit_unit": "",
"payment_cancel_rate_limit_window_mode": "",
"custom_menu_items": [],
"custom_endpoints": []
}
@ -700,7 +718,7 @@ func newContractDeps(t *testing.T) *contractDeps {
authHandler := handler.NewAuthHandler(cfg, nil, userService, settingService, nil, redeemService, nil)
apiKeyHandler := handler.NewAPIKeyHandler(apiKeyService)
usageHandler := handler.NewUsageHandler(usageService, apiKeyService)
adminSettingHandler := adminhandler.NewSettingHandler(settingService, nil, nil, nil)
adminSettingHandler := adminhandler.NewSettingHandler(settingService, nil, nil, nil, nil, nil)
adminAccountHandler := adminhandler.NewAccountHandler(adminService, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
jwtAuth := func(c *gin.Context) {

View File

@ -111,4 +111,5 @@ func registerRoutes(
routes.RegisterUserRoutes(v1, h, jwtAuth, settingService)
routes.RegisterAdminRoutes(v1, h, adminAuth)
routes.RegisterGatewayRoutes(r, h, apiKeyAuth, apiKeyService, subscriptionService, opsService, settingService, cfg)
routes.RegisterPaymentRoutes(v1, h.Payment, h.PaymentWebhook, h.Admin.Payment, jwtAuth, adminAuth, settingService)
}

View File

@ -0,0 +1,103 @@
package routes
import (
"github.com/Wei-Shaw/sub2api/internal/handler"
"github.com/Wei-Shaw/sub2api/internal/handler/admin"
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
)
// RegisterPaymentRoutes registers all payment-related routes:
// user-facing endpoints, webhook endpoints, and admin endpoints.
func RegisterPaymentRoutes(
v1 *gin.RouterGroup,
paymentHandler *handler.PaymentHandler,
webhookHandler *handler.PaymentWebhookHandler,
adminPaymentHandler *admin.PaymentHandler,
jwtAuth middleware.JWTAuthMiddleware,
adminAuth middleware.AdminAuthMiddleware,
settingService *service.SettingService,
) {
// --- User-facing payment endpoints (authenticated) ---
authenticated := v1.Group("/payment")
authenticated.Use(gin.HandlerFunc(jwtAuth))
authenticated.Use(middleware.BackendModeUserGuard(settingService))
{
authenticated.GET("/config", paymentHandler.GetPaymentConfig)
authenticated.GET("/checkout-info", paymentHandler.GetCheckoutInfo)
authenticated.GET("/plans", paymentHandler.GetPlans)
authenticated.GET("/channels", paymentHandler.GetChannels)
authenticated.GET("/limits", paymentHandler.GetLimits)
orders := authenticated.Group("/orders")
{
orders.POST("", paymentHandler.CreateOrder)
orders.POST("/verify", paymentHandler.VerifyOrder)
orders.GET("/my", paymentHandler.GetMyOrders)
orders.GET("/:id", paymentHandler.GetOrder)
orders.POST("/:id/cancel", paymentHandler.CancelOrder)
orders.POST("/:id/refund-request", paymentHandler.RequestRefund)
}
}
// --- Public payment endpoints (no auth) ---
// Payment result page needs to verify order status without login
// (user session may have expired during provider redirect).
public := v1.Group("/payment/public")
{
public.POST("/orders/verify", paymentHandler.VerifyOrderPublic)
}
// --- Webhook endpoints (no auth) ---
webhook := v1.Group("/payment/webhook")
{
// EasyPay sends GET callbacks with query params
webhook.GET("/easypay", webhookHandler.EasyPayNotify)
webhook.POST("/easypay", webhookHandler.EasyPayNotify)
webhook.POST("/alipay", webhookHandler.AlipayNotify)
webhook.POST("/wxpay", webhookHandler.WxpayNotify)
webhook.POST("/stripe", webhookHandler.StripeWebhook)
}
// --- Admin payment endpoints (admin auth) ---
adminGroup := v1.Group("/admin/payment")
adminGroup.Use(gin.HandlerFunc(adminAuth))
{
// Dashboard
adminGroup.GET("/dashboard", adminPaymentHandler.GetDashboard)
// Config
adminGroup.GET("/config", adminPaymentHandler.GetConfig)
adminGroup.PUT("/config", adminPaymentHandler.UpdateConfig)
// Orders
adminOrders := adminGroup.Group("/orders")
{
adminOrders.GET("", adminPaymentHandler.ListOrders)
adminOrders.GET("/:id", adminPaymentHandler.GetOrderDetail)
adminOrders.POST("/:id/cancel", adminPaymentHandler.CancelOrder)
adminOrders.POST("/:id/retry", adminPaymentHandler.RetryFulfillment)
adminOrders.POST("/:id/refund", adminPaymentHandler.ProcessRefund)
}
// Subscription Plans
plans := adminGroup.Group("/plans")
{
plans.GET("", adminPaymentHandler.ListPlans)
plans.POST("", adminPaymentHandler.CreatePlan)
plans.PUT("/:id", adminPaymentHandler.UpdatePlan)
plans.DELETE("/:id", adminPaymentHandler.DeletePlan)
}
// Provider Instances
providers := adminGroup.Group("/providers")
{
providers.GET("", adminPaymentHandler.ListProviders)
providers.POST("", adminPaymentHandler.CreateProvider)
providers.PUT("/:id", adminPaymentHandler.UpdateProvider)
providers.DELETE("/:id", adminPaymentHandler.DeleteProvider)
}
}
}

View File

@ -0,0 +1,172 @@
package service
import (
"context"
"encoding/json"
"fmt"
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
"github.com/Wei-Shaw/sub2api/internal/payment"
)
// GetAvailableMethodLimits collects all payment types from enabled provider
// instances and returns limits for each, plus the global widest range.
// Stripe sub-types (card, link) are aggregated under "stripe".
func (s *PaymentConfigService) GetAvailableMethodLimits(ctx context.Context) (*MethodLimitsResponse, error) {
instances, err := s.entClient.PaymentProviderInstance.Query().
Where(paymentproviderinstance.EnabledEQ(true)).All(ctx)
if err != nil {
return nil, fmt.Errorf("query provider instances: %w", err)
}
typeInstances := pcGroupByPaymentType(instances)
resp := &MethodLimitsResponse{
Methods: make(map[string]MethodLimits, len(typeInstances)),
}
for pt, insts := range typeInstances {
ml := pcAggregateMethodLimits(pt, insts)
resp.Methods[ml.PaymentType] = ml
}
resp.GlobalMin, resp.GlobalMax = pcComputeGlobalRange(resp.Methods)
return resp, nil
}
// GetMethodLimits returns per-payment-type limits from enabled provider instances.
func (s *PaymentConfigService) GetMethodLimits(ctx context.Context, types []string) ([]MethodLimits, error) {
instances, err := s.entClient.PaymentProviderInstance.Query().
Where(paymentproviderinstance.EnabledEQ(true)).All(ctx)
if err != nil {
return nil, fmt.Errorf("query provider instances: %w", err)
}
result := make([]MethodLimits, 0, len(types))
for _, pt := range types {
var matching []*dbent.PaymentProviderInstance
for _, inst := range instances {
if payment.InstanceSupportsType(inst.SupportedTypes, pt) {
matching = append(matching, inst)
}
}
result = append(result, pcAggregateMethodLimits(pt, matching))
}
return result, nil
}
// pcGroupByPaymentType groups instances by user-facing payment type.
// For Stripe providers, ALL sub-types (card, link, alipay, wxpay) map to "stripe"
// because the user sees a single "Stripe" button, not individual sub-methods.
// Uses a seen set to avoid counting one instance twice.
func pcGroupByPaymentType(instances []*dbent.PaymentProviderInstance) map[string][]*dbent.PaymentProviderInstance {
typeInstances := make(map[string][]*dbent.PaymentProviderInstance)
seen := make(map[string]map[int64]bool)
add := func(key string, inst *dbent.PaymentProviderInstance) {
if seen[key] == nil {
seen[key] = make(map[int64]bool)
}
if !seen[key][int64(inst.ID)] {
seen[key][int64(inst.ID)] = true
typeInstances[key] = append(typeInstances[key], inst)
}
}
for _, inst := range instances {
// Stripe provider: all sub-types → single "stripe" group
if inst.ProviderKey == payment.TypeStripe {
add(payment.TypeStripe, inst)
continue
}
for _, t := range splitTypes(inst.SupportedTypes) {
add(t, inst)
}
}
return typeInstances
}
// pcInstanceTypeLimits extracts per-type limits from a provider instance.
// Returns (limits, true) if configured; (zero, false) if unlimited.
// For Stripe instances, limits are stored under "stripe" key regardless of sub-types.
func pcInstanceTypeLimits(inst *dbent.PaymentProviderInstance, pt string) (payment.ChannelLimits, bool) {
if inst.Limits == "" {
return payment.ChannelLimits{}, false
}
var limits payment.InstanceLimits
if err := json.Unmarshal([]byte(inst.Limits), &limits); err != nil {
return payment.ChannelLimits{}, false
}
cl, ok := limits[pt]
return cl, ok
}
// unionFloat merges a single limit value into the aggregate using UNION semantics.
// - For "min" fields (wantMin=true): keeps the lowest non-zero value
// - For "max"/"cap" fields (wantMin=false): keeps the highest non-zero value
// - If any value is 0 (unlimited), the result is unlimited.
//
// Returns (aggregated value, still limited).
func unionFloat(agg float64, limited bool, val float64, wantMin bool) (float64, bool) {
if val == 0 {
return agg, false
}
if !limited {
return agg, false
}
if agg == 0 {
return val, true
}
if wantMin && val < agg {
return val, true
}
if !wantMin && val > agg {
return val, true
}
return agg, true
}
// pcAggregateMethodLimits computes the UNION (least restrictive) of limits
// across all provider instances for a given payment type.
//
// Since the load balancer can route an order to any available instance,
// the user should see the widest possible range:
// - SingleMin: lowest floor across instances; 0 if any is unlimited
// - SingleMax: highest ceiling across instances; 0 if any is unlimited
// - DailyLimit: highest cap across instances; 0 if any is unlimited
func pcAggregateMethodLimits(pt string, instances []*dbent.PaymentProviderInstance) MethodLimits {
ml := MethodLimits{PaymentType: pt}
minLimited, maxLimited, dailyLimited := true, true, true
for _, inst := range instances {
cl, hasLimits := pcInstanceTypeLimits(inst, pt)
if !hasLimits {
return MethodLimits{PaymentType: pt} // any unlimited instance → all zeros
}
ml.SingleMin, minLimited = unionFloat(ml.SingleMin, minLimited, cl.SingleMin, true)
ml.SingleMax, maxLimited = unionFloat(ml.SingleMax, maxLimited, cl.SingleMax, false)
ml.DailyLimit, dailyLimited = unionFloat(ml.DailyLimit, dailyLimited, cl.DailyLimit, false)
}
if !minLimited {
ml.SingleMin = 0
}
if !maxLimited {
ml.SingleMax = 0
}
if !dailyLimited {
ml.DailyLimit = 0
}
return ml
}
// pcComputeGlobalRange computes the widest [min, max] across all methods.
// Uses the same union logic: lowest min, highest max, 0 if any is unlimited.
func pcComputeGlobalRange(methods map[string]MethodLimits) (globalMin, globalMax float64) {
minLimited, maxLimited := true, true
for _, ml := range methods {
globalMin, minLimited = unionFloat(globalMin, minLimited, ml.SingleMin, true)
globalMax, maxLimited = unionFloat(globalMax, maxLimited, ml.SingleMax, false)
}
if !minLimited {
globalMin = 0
}
if !maxLimited {
globalMax = 0
}
return globalMin, globalMax
}

View File

@ -0,0 +1,301 @@
package service
import (
"testing"
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/internal/payment"
)
func TestUnionFloat(t *testing.T) {
t.Parallel()
tests := []struct {
name string
agg float64
limited bool
val float64
wantMin bool
wantAgg float64
wantLimited bool
}{
{"first non-zero value", 0, true, 5, true, 5, true},
{"lower min replaces", 10, true, 3, true, 3, true},
{"higher min does not replace", 3, true, 10, true, 3, true},
{"higher max replaces", 10, true, 20, false, 20, true},
{"lower max does not replace", 20, true, 10, false, 20, true},
{"zero value makes unlimited", 5, true, 0, true, 5, false},
{"already unlimited stays unlimited", 5, false, 10, true, 5, false},
{"zero on first call", 0, true, 0, true, 0, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
gotAgg, gotLimited := unionFloat(tt.agg, tt.limited, tt.val, tt.wantMin)
if gotAgg != tt.wantAgg || gotLimited != tt.wantLimited {
t.Fatalf("unionFloat(%v, %v, %v, %v) = (%v, %v), want (%v, %v)",
tt.agg, tt.limited, tt.val, tt.wantMin,
gotAgg, gotLimited, tt.wantAgg, tt.wantLimited)
}
})
}
}
func makeInstance(id int64, providerKey, supportedTypes, limits string) *dbent.PaymentProviderInstance {
return &dbent.PaymentProviderInstance{
ID: id,
ProviderKey: providerKey,
SupportedTypes: supportedTypes,
Limits: limits,
Enabled: true,
}
}
func TestPcAggregateMethodLimits(t *testing.T) {
t.Parallel()
t.Run("single instance with limits", func(t *testing.T) {
t.Parallel()
inst := makeInstance(1, "easypay", "alipay,wxpay",
`{"alipay":{"singleMin":2,"singleMax":14},"wxpay":{"singleMin":1,"singleMax":12}}`)
ml := pcAggregateMethodLimits("alipay", []*dbent.PaymentProviderInstance{inst})
if ml.SingleMin != 2 || ml.SingleMax != 14 {
t.Fatalf("alipay limits = min:%v max:%v, want min:2 max:14", ml.SingleMin, ml.SingleMax)
}
})
t.Run("two instances union takes widest range", func(t *testing.T) {
t.Parallel()
inst1 := makeInstance(1, "easypay", "alipay,wxpay",
`{"alipay":{"singleMin":5,"singleMax":100}}`)
inst2 := makeInstance(2, "easypay", "alipay,wxpay",
`{"alipay":{"singleMin":2,"singleMax":200}}`)
ml := pcAggregateMethodLimits("alipay", []*dbent.PaymentProviderInstance{inst1, inst2})
if ml.SingleMin != 2 {
t.Fatalf("SingleMin = %v, want 2 (lowest floor)", ml.SingleMin)
}
if ml.SingleMax != 200 {
t.Fatalf("SingleMax = %v, want 200 (highest ceiling)", ml.SingleMax)
}
})
t.Run("one instance unlimited makes aggregate unlimited", func(t *testing.T) {
t.Parallel()
inst1 := makeInstance(1, "easypay", "wxpay",
`{"wxpay":{"singleMin":3,"singleMax":10}}`)
inst2 := makeInstance(2, "easypay", "wxpay", "") // no limits = unlimited
ml := pcAggregateMethodLimits("wxpay", []*dbent.PaymentProviderInstance{inst1, inst2})
if ml.SingleMin != 0 || ml.SingleMax != 0 {
t.Fatalf("limits = min:%v max:%v, want min:0 max:0 (unlimited)", ml.SingleMin, ml.SingleMax)
}
})
t.Run("one field unlimited others limited", func(t *testing.T) {
t.Parallel()
inst1 := makeInstance(1, "easypay", "alipay",
`{"alipay":{"singleMin":5,"singleMax":100}}`)
inst2 := makeInstance(2, "easypay", "alipay",
`{"alipay":{"singleMin":3,"singleMax":0}}`) // singleMax=0 = unlimited
ml := pcAggregateMethodLimits("alipay", []*dbent.PaymentProviderInstance{inst1, inst2})
if ml.SingleMin != 3 {
t.Fatalf("SingleMin = %v, want 3 (lowest floor)", ml.SingleMin)
}
if ml.SingleMax != 0 {
t.Fatalf("SingleMax = %v, want 0 (unlimited)", ml.SingleMax)
}
})
t.Run("empty instances returns zeros", func(t *testing.T) {
t.Parallel()
ml := pcAggregateMethodLimits("alipay", nil)
if ml.SingleMin != 0 || ml.SingleMax != 0 || ml.DailyLimit != 0 {
t.Fatalf("empty instances should return all zeros, got %+v", ml)
}
})
t.Run("invalid JSON treated as unlimited", func(t *testing.T) {
t.Parallel()
inst := makeInstance(1, "easypay", "alipay", `{invalid json}`)
ml := pcAggregateMethodLimits("alipay", []*dbent.PaymentProviderInstance{inst})
if ml.SingleMin != 0 || ml.SingleMax != 0 {
t.Fatalf("invalid JSON should be treated as unlimited, got %+v", ml)
}
})
t.Run("type not in limits JSON treated as unlimited", func(t *testing.T) {
t.Parallel()
inst := makeInstance(1, "easypay", "alipay,wxpay",
`{"wxpay":{"singleMin":1,"singleMax":10}}`) // only wxpay, no alipay
ml := pcAggregateMethodLimits("alipay", []*dbent.PaymentProviderInstance{inst})
if ml.SingleMin != 0 || ml.SingleMax != 0 {
t.Fatalf("missing type should be treated as unlimited, got %+v", ml)
}
})
t.Run("daily limit aggregation", func(t *testing.T) {
t.Parallel()
inst1 := makeInstance(1, "easypay", "alipay",
`{"alipay":{"singleMin":1,"singleMax":100,"dailyLimit":500}}`)
inst2 := makeInstance(2, "easypay", "alipay",
`{"alipay":{"singleMin":2,"singleMax":200,"dailyLimit":1000}}`)
ml := pcAggregateMethodLimits("alipay", []*dbent.PaymentProviderInstance{inst1, inst2})
if ml.DailyLimit != 1000 {
t.Fatalf("DailyLimit = %v, want 1000 (highest cap)", ml.DailyLimit)
}
})
}
func TestPcGroupByPaymentType(t *testing.T) {
t.Parallel()
t.Run("stripe instance maps all types to stripe group", func(t *testing.T) {
t.Parallel()
stripe := makeInstance(1, payment.TypeStripe, "card,alipay,link,wxpay", "")
easypay := makeInstance(2, payment.TypeEasyPay, "alipay,wxpay", "")
groups := pcGroupByPaymentType([]*dbent.PaymentProviderInstance{stripe, easypay})
// Stripe instance should only be in "stripe" group
if len(groups[payment.TypeStripe]) != 1 || groups[payment.TypeStripe][0].ID != 1 {
t.Fatalf("stripe group should contain only stripe instance, got %v", groups[payment.TypeStripe])
}
// alipay group should only contain easypay, NOT stripe
if len(groups[payment.TypeAlipay]) != 1 || groups[payment.TypeAlipay][0].ID != 2 {
t.Fatalf("alipay group should contain only easypay instance, got %v", groups[payment.TypeAlipay])
}
// wxpay group should only contain easypay, NOT stripe
if len(groups[payment.TypeWxpay]) != 1 || groups[payment.TypeWxpay][0].ID != 2 {
t.Fatalf("wxpay group should contain only easypay instance, got %v", groups[payment.TypeWxpay])
}
})
t.Run("multiple easypay instances in same groups", func(t *testing.T) {
t.Parallel()
ep1 := makeInstance(1, payment.TypeEasyPay, "alipay,wxpay", "")
ep2 := makeInstance(2, payment.TypeEasyPay, "alipay,wxpay", "")
groups := pcGroupByPaymentType([]*dbent.PaymentProviderInstance{ep1, ep2})
if len(groups[payment.TypeAlipay]) != 2 {
t.Fatalf("alipay group should have 2 instances, got %d", len(groups[payment.TypeAlipay]))
}
if len(groups[payment.TypeWxpay]) != 2 {
t.Fatalf("wxpay group should have 2 instances, got %d", len(groups[payment.TypeWxpay]))
}
})
t.Run("stripe with no supported types still in stripe group", func(t *testing.T) {
t.Parallel()
stripe := makeInstance(1, payment.TypeStripe, "", "")
groups := pcGroupByPaymentType([]*dbent.PaymentProviderInstance{stripe})
if len(groups[payment.TypeStripe]) != 1 {
t.Fatalf("stripe with empty types should still be in stripe group, got %v", groups)
}
})
}
func TestPcComputeGlobalRange(t *testing.T) {
t.Parallel()
t.Run("all methods have limits", func(t *testing.T) {
t.Parallel()
methods := map[string]MethodLimits{
"alipay": {SingleMin: 2, SingleMax: 14},
"wxpay": {SingleMin: 1, SingleMax: 12},
"stripe": {SingleMin: 5, SingleMax: 100},
}
gMin, gMax := pcComputeGlobalRange(methods)
if gMin != 1 {
t.Fatalf("global min = %v, want 1 (lowest floor)", gMin)
}
if gMax != 100 {
t.Fatalf("global max = %v, want 100 (highest ceiling)", gMax)
}
})
t.Run("one method unlimited makes global unlimited", func(t *testing.T) {
t.Parallel()
methods := map[string]MethodLimits{
"alipay": {SingleMin: 2, SingleMax: 14},
"stripe": {SingleMin: 0, SingleMax: 0}, // unlimited
}
gMin, gMax := pcComputeGlobalRange(methods)
if gMin != 0 {
t.Fatalf("global min = %v, want 0 (unlimited)", gMin)
}
if gMax != 0 {
t.Fatalf("global max = %v, want 0 (unlimited)", gMax)
}
})
t.Run("empty methods returns zeros", func(t *testing.T) {
t.Parallel()
gMin, gMax := pcComputeGlobalRange(map[string]MethodLimits{})
if gMin != 0 || gMax != 0 {
t.Fatalf("empty methods should return (0, 0), got (%v, %v)", gMin, gMax)
}
})
t.Run("only min unlimited", func(t *testing.T) {
t.Parallel()
methods := map[string]MethodLimits{
"alipay": {SingleMin: 0, SingleMax: 100},
"wxpay": {SingleMin: 5, SingleMax: 50},
}
gMin, gMax := pcComputeGlobalRange(methods)
if gMin != 0 {
t.Fatalf("global min = %v, want 0 (unlimited)", gMin)
}
if gMax != 100 {
t.Fatalf("global max = %v, want 100", gMax)
}
})
}
func TestPcInstanceTypeLimits(t *testing.T) {
t.Parallel()
t.Run("empty limits string returns false", func(t *testing.T) {
t.Parallel()
inst := makeInstance(1, "easypay", "alipay", "")
_, ok := pcInstanceTypeLimits(inst, "alipay")
if ok {
t.Fatal("expected ok=false for empty limits")
}
})
t.Run("type found returns correct values", func(t *testing.T) {
t.Parallel()
inst := makeInstance(1, "easypay", "alipay",
`{"alipay":{"singleMin":2,"singleMax":14,"dailyLimit":500}}`)
cl, ok := pcInstanceTypeLimits(inst, "alipay")
if !ok {
t.Fatal("expected ok=true")
}
if cl.SingleMin != 2 || cl.SingleMax != 14 || cl.DailyLimit != 500 {
t.Fatalf("limits = %+v, want min:2 max:14 daily:500", cl)
}
})
t.Run("type not found returns false", func(t *testing.T) {
t.Parallel()
inst := makeInstance(1, "easypay", "alipay",
`{"wxpay":{"singleMin":1}}`)
_, ok := pcInstanceTypeLimits(inst, "alipay")
if ok {
t.Fatal("expected ok=false for missing type")
}
})
t.Run("invalid JSON returns false", func(t *testing.T) {
t.Parallel()
inst := makeInstance(1, "easypay", "alipay", `{bad json}`)
_, ok := pcInstanceTypeLimits(inst, "alipay")
if ok {
t.Fatal("expected ok=false for invalid JSON")
}
})
}

View File

@ -0,0 +1,147 @@
package service
import (
"context"
"fmt"
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/subscriptionplan"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
)
// --- Plan CRUD ---
// PlanGroupInfo holds the group details needed for subscription plan display.
type PlanGroupInfo struct {
Platform string `json:"platform"`
Name string `json:"name"`
RateMultiplier float64 `json:"rate_multiplier"`
DailyLimitUSD *float64 `json:"daily_limit_usd"`
WeeklyLimitUSD *float64 `json:"weekly_limit_usd"`
MonthlyLimitUSD *float64 `json:"monthly_limit_usd"`
ModelScopes []string `json:"supported_model_scopes"`
}
// GetGroupPlatformMap returns a map of group_id → platform for the given plans.
func (s *PaymentConfigService) GetGroupPlatformMap(ctx context.Context, plans []*dbent.SubscriptionPlan) map[int64]string {
info := s.GetGroupInfoMap(ctx, plans)
m := make(map[int64]string, len(info))
for id, gi := range info {
m[id] = gi.Platform
}
return m
}
// GetGroupInfoMap returns a map of group_id → PlanGroupInfo for the given plans.
func (s *PaymentConfigService) GetGroupInfoMap(ctx context.Context, plans []*dbent.SubscriptionPlan) map[int64]PlanGroupInfo {
ids := make([]int64, 0, len(plans))
seen := make(map[int64]bool)
for _, p := range plans {
if !seen[p.GroupID] {
seen[p.GroupID] = true
ids = append(ids, p.GroupID)
}
}
if len(ids) == 0 {
return nil
}
groups, err := s.entClient.Group.Query().Where(group.IDIn(ids...)).All(ctx)
if err != nil {
return nil
}
m := make(map[int64]PlanGroupInfo, len(groups))
for _, g := range groups {
m[int64(g.ID)] = PlanGroupInfo{
Platform: g.Platform,
Name: g.Name,
RateMultiplier: g.RateMultiplier,
DailyLimitUSD: g.DailyLimitUsd,
WeeklyLimitUSD: g.WeeklyLimitUsd,
MonthlyLimitUSD: g.MonthlyLimitUsd,
ModelScopes: g.SupportedModelScopes,
}
}
return m
}
func (s *PaymentConfigService) ListPlans(ctx context.Context) ([]*dbent.SubscriptionPlan, error) {
return s.entClient.SubscriptionPlan.Query().Order(subscriptionplan.BySortOrder()).All(ctx)
}
func (s *PaymentConfigService) ListPlansForSale(ctx context.Context) ([]*dbent.SubscriptionPlan, error) {
return s.entClient.SubscriptionPlan.Query().Where(subscriptionplan.ForSaleEQ(true)).Order(subscriptionplan.BySortOrder()).All(ctx)
}
func (s *PaymentConfigService) CreatePlan(ctx context.Context, req CreatePlanRequest) (*dbent.SubscriptionPlan, error) {
b := s.entClient.SubscriptionPlan.Create().
SetGroupID(req.GroupID).SetName(req.Name).SetDescription(req.Description).
SetPrice(req.Price).SetValidityDays(req.ValidityDays).SetValidityUnit(req.ValidityUnit).
SetFeatures(req.Features).SetProductName(req.ProductName).
SetForSale(req.ForSale).SetSortOrder(req.SortOrder)
if req.OriginalPrice != nil {
b.SetOriginalPrice(*req.OriginalPrice)
}
return b.Save(ctx)
}
// UpdatePlan updates a subscription plan by ID (patch semantics).
// NOTE: This function exceeds 30 lines due to per-field nil-check patch update boilerplate.
func (s *PaymentConfigService) UpdatePlan(ctx context.Context, id int64, req UpdatePlanRequest) (*dbent.SubscriptionPlan, error) {
u := s.entClient.SubscriptionPlan.UpdateOneID(id)
if req.GroupID != nil {
u.SetGroupID(*req.GroupID)
}
if req.Name != nil {
u.SetName(*req.Name)
}
if req.Description != nil {
u.SetDescription(*req.Description)
}
if req.Price != nil {
u.SetPrice(*req.Price)
}
if req.OriginalPrice != nil {
u.SetOriginalPrice(*req.OriginalPrice)
}
if req.ValidityDays != nil {
u.SetValidityDays(*req.ValidityDays)
}
if req.ValidityUnit != nil {
u.SetValidityUnit(*req.ValidityUnit)
}
if req.Features != nil {
u.SetFeatures(*req.Features)
}
if req.ProductName != nil {
u.SetProductName(*req.ProductName)
}
if req.ForSale != nil {
u.SetForSale(*req.ForSale)
}
if req.SortOrder != nil {
u.SetSortOrder(*req.SortOrder)
}
return u.Save(ctx)
}
func (s *PaymentConfigService) DeletePlan(ctx context.Context, id int64) error {
count, err := s.countPendingOrdersByPlan(ctx, id)
if err != nil {
return fmt.Errorf("check pending orders: %w", err)
}
if count > 0 {
return infraerrors.Conflict("PENDING_ORDERS",
fmt.Sprintf("this plan has %d in-progress orders and cannot be deleted — wait for orders to complete first", count))
}
return s.entClient.SubscriptionPlan.DeleteOneID(id).Exec(ctx)
}
// GetPlan returns a subscription plan by ID.
func (s *PaymentConfigService) GetPlan(ctx context.Context, id int64) (*dbent.SubscriptionPlan, error) {
plan, err := s.entClient.SubscriptionPlan.Get(ctx, id)
if err != nil {
return nil, infraerrors.NotFound("PLAN_NOT_FOUND", "subscription plan not found")
}
return plan, nil
}

View File

@ -0,0 +1,286 @@
package service
import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
"github.com/Wei-Shaw/sub2api/internal/payment"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
)
// --- Provider Instance CRUD ---
func (s *PaymentConfigService) ListProviderInstances(ctx context.Context) ([]*dbent.PaymentProviderInstance, error) {
return s.entClient.PaymentProviderInstance.Query().Order(paymentproviderinstance.BySortOrder()).All(ctx)
}
// ProviderInstanceResponse is the API response for a provider instance.
type ProviderInstanceResponse struct {
ID int64 `json:"id"`
ProviderKey string `json:"provider_key"`
Name string `json:"name"`
Config map[string]string `json:"config"`
SupportedTypes []string `json:"supported_types"`
Limits string `json:"limits"`
Enabled bool `json:"enabled"`
RefundEnabled bool `json:"refund_enabled"`
SortOrder int `json:"sort_order"`
PaymentMode string `json:"payment_mode"`
}
// ListProviderInstancesWithConfig returns provider instances with decrypted config.
func (s *PaymentConfigService) ListProviderInstancesWithConfig(ctx context.Context) ([]ProviderInstanceResponse, error) {
instances, err := s.entClient.PaymentProviderInstance.Query().
Order(paymentproviderinstance.BySortOrder()).All(ctx)
if err != nil {
return nil, err
}
result := make([]ProviderInstanceResponse, 0, len(instances))
for _, inst := range instances {
resp := ProviderInstanceResponse{
ID: int64(inst.ID), ProviderKey: inst.ProviderKey, Name: inst.Name,
SupportedTypes: splitTypes(inst.SupportedTypes), Limits: inst.Limits,
Enabled: inst.Enabled, RefundEnabled: inst.RefundEnabled, SortOrder: inst.SortOrder,
PaymentMode: inst.PaymentMode,
}
resp.Config, err = s.decryptAndMaskConfig(inst.Config)
if err != nil {
return nil, fmt.Errorf("decrypt config for instance %d: %w", inst.ID, err)
}
result = append(result, resp)
}
return result, nil
}
func (s *PaymentConfigService) decryptAndMaskConfig(encrypted string) (map[string]string, error) {
return s.decryptConfig(encrypted)
}
// pendingOrderStatuses are order statuses considered "in progress".
var pendingOrderStatuses = []string{
payment.OrderStatusPending,
payment.OrderStatusPaid,
payment.OrderStatusRecharging,
}
var sensitiveConfigPatterns = []string{"key", "pkey", "secret", "private", "password"}
func isSensitiveConfigField(fieldName string) bool {
lower := strings.ToLower(fieldName)
for _, p := range sensitiveConfigPatterns {
if strings.Contains(lower, p) {
return true
}
}
return false
}
func (s *PaymentConfigService) countPendingOrders(ctx context.Context, providerInstanceID int64) (int, error) {
return s.entClient.PaymentOrder.Query().
Where(
paymentorder.ProviderInstanceIDEQ(strconv.FormatInt(providerInstanceID, 10)),
paymentorder.StatusIn(pendingOrderStatuses...),
).Count(ctx)
}
func (s *PaymentConfigService) countPendingOrdersByPlan(ctx context.Context, planID int64) (int, error) {
return s.entClient.PaymentOrder.Query().
Where(
paymentorder.PlanIDEQ(planID),
paymentorder.StatusIn(pendingOrderStatuses...),
).Count(ctx)
}
var validProviderKeys = map[string]bool{
payment.TypeEasyPay: true, payment.TypeAlipay: true, payment.TypeWxpay: true, payment.TypeStripe: true,
}
func (s *PaymentConfigService) CreateProviderInstance(ctx context.Context, req CreateProviderInstanceRequest) (*dbent.PaymentProviderInstance, error) {
typesStr := joinTypes(req.SupportedTypes)
if err := validateProviderRequest(req.ProviderKey, req.Name, typesStr); err != nil {
return nil, err
}
enc, err := s.encryptConfig(req.Config)
if err != nil {
return nil, err
}
return s.entClient.PaymentProviderInstance.Create().
SetProviderKey(req.ProviderKey).SetName(req.Name).SetConfig(enc).
SetSupportedTypes(typesStr).SetEnabled(req.Enabled).SetPaymentMode(req.PaymentMode).
SetSortOrder(req.SortOrder).SetLimits(req.Limits).SetRefundEnabled(req.RefundEnabled).
Save(ctx)
}
func validateProviderRequest(providerKey, name, supportedTypes string) error {
if strings.TrimSpace(name) == "" {
return infraerrors.BadRequest("VALIDATION_ERROR", "provider name is required")
}
if !validProviderKeys[providerKey] {
return infraerrors.BadRequest("VALIDATION_ERROR", fmt.Sprintf("invalid provider key: %s", providerKey))
}
// supported_types can be empty (provider accepts no payment types until configured)
return nil
}
// UpdateProviderInstance updates a provider instance by ID (patch semantics).
// NOTE: This function exceeds 30 lines due to per-field nil-check patch update
// boilerplate and pending-order safety checks.
func (s *PaymentConfigService) UpdateProviderInstance(ctx context.Context, id int64, req UpdateProviderInstanceRequest) (*dbent.PaymentProviderInstance, error) {
if req.Config != nil {
hasSensitive := false
for k := range req.Config {
if isSensitiveConfigField(k) && req.Config[k] != "" {
hasSensitive = true
break
}
}
if hasSensitive {
count, err := s.countPendingOrders(ctx, id)
if err != nil {
return nil, fmt.Errorf("check pending orders: %w", err)
}
if count > 0 {
return nil, infraerrors.Conflict("PENDING_ORDERS", "instance has pending orders").
WithMetadata(map[string]string{"count": strconv.Itoa(count)})
}
}
}
if req.Enabled != nil && !*req.Enabled {
count, err := s.countPendingOrders(ctx, id)
if err != nil {
return nil, fmt.Errorf("check pending orders: %w", err)
}
if count > 0 {
return nil, infraerrors.Conflict("PENDING_ORDERS", "instance has pending orders").
WithMetadata(map[string]string{"count": strconv.Itoa(count)})
}
}
u := s.entClient.PaymentProviderInstance.UpdateOneID(id)
if req.Name != nil {
u.SetName(*req.Name)
}
if req.Config != nil {
merged, err := s.mergeConfig(ctx, id, req.Config)
if err != nil {
return nil, err
}
enc, err := s.encryptConfig(merged)
if err != nil {
return nil, err
}
u.SetConfig(enc)
}
if req.SupportedTypes != nil {
// Check pending orders before removing payment types
count, err := s.countPendingOrders(ctx, id)
if err != nil {
return nil, fmt.Errorf("check pending orders: %w", err)
}
if count > 0 {
// Load current instance to compare types
inst, err := s.entClient.PaymentProviderInstance.Get(ctx, id)
if err != nil {
return nil, fmt.Errorf("load provider instance: %w", err)
}
oldTypes := strings.Split(inst.SupportedTypes, ",")
newTypes := req.SupportedTypes
for _, ot := range oldTypes {
ot = strings.TrimSpace(ot)
if ot == "" {
continue
}
found := false
for _, nt := range newTypes {
if strings.TrimSpace(nt) == ot {
found = true
break
}
}
if !found {
return nil, infraerrors.Conflict("PENDING_ORDERS", "cannot remove payment types while instance has pending orders").
WithMetadata(map[string]string{"count": strconv.Itoa(count)})
}
}
}
u.SetSupportedTypes(joinTypes(req.SupportedTypes))
}
if req.Enabled != nil {
u.SetEnabled(*req.Enabled)
}
if req.SortOrder != nil {
u.SetSortOrder(*req.SortOrder)
}
if req.Limits != nil {
u.SetLimits(*req.Limits)
}
if req.RefundEnabled != nil {
u.SetRefundEnabled(*req.RefundEnabled)
}
if req.PaymentMode != nil {
u.SetPaymentMode(*req.PaymentMode)
}
return u.Save(ctx)
}
func (s *PaymentConfigService) mergeConfig(ctx context.Context, id int64, newConfig map[string]string) (map[string]string, error) {
inst, err := s.entClient.PaymentProviderInstance.Get(ctx, id)
if err != nil {
return nil, fmt.Errorf("load existing provider: %w", err)
}
existing, err := s.decryptConfig(inst.Config)
if err != nil {
return nil, fmt.Errorf("decrypt existing config for instance %d: %w", id, err)
}
if existing == nil {
return newConfig, nil
}
for k, v := range newConfig {
existing[k] = v
}
return existing, nil
}
func (s *PaymentConfigService) decryptConfig(encrypted string) (map[string]string, error) {
if encrypted == "" {
return nil, nil
}
decrypted, err := payment.Decrypt(encrypted, s.encryptionKey)
if err != nil {
return nil, fmt.Errorf("decrypt config: %w", err)
}
var raw map[string]string
if err := json.Unmarshal([]byte(decrypted), &raw); err != nil {
return nil, fmt.Errorf("unmarshal decrypted config: %w", err)
}
return raw, nil
}
func (s *PaymentConfigService) DeleteProviderInstance(ctx context.Context, id int64) error {
count, err := s.countPendingOrders(ctx, id)
if err != nil {
return fmt.Errorf("check pending orders: %w", err)
}
if count > 0 {
return infraerrors.Conflict("PENDING_ORDERS",
fmt.Sprintf("this instance has %d in-progress orders and cannot be deleted — wait for orders to complete or disable the instance first", count))
}
return s.entClient.PaymentProviderInstance.DeleteOneID(id).Exec(ctx)
}
func (s *PaymentConfigService) encryptConfig(cfg map[string]string) (string, error) {
data, err := json.Marshal(cfg)
if err != nil {
return "", fmt.Errorf("marshal config: %w", err)
}
enc, err := payment.Encrypt(string(data), s.encryptionKey)
if err != nil {
return "", fmt.Errorf("encrypt config: %w", err)
}
return enc, nil
}

View File

@ -0,0 +1,187 @@
//go:build unit
package service
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestValidateProviderRequest(t *testing.T) {
t.Parallel()
tests := []struct {
name string
providerKey string
providerName string
supportedTypes string
wantErr bool
errContains string
}{
{
name: "valid easypay with types",
providerKey: "easypay",
providerName: "MyProvider",
supportedTypes: "alipay,wxpay",
wantErr: false,
},
{
name: "valid stripe with empty types",
providerKey: "stripe",
providerName: "Stripe Provider",
supportedTypes: "",
wantErr: false,
},
{
name: "valid alipay provider",
providerKey: "alipay",
providerName: "Alipay Direct",
supportedTypes: "alipay",
wantErr: false,
},
{
name: "valid wxpay provider",
providerKey: "wxpay",
providerName: "WeChat Pay",
supportedTypes: "wxpay",
wantErr: false,
},
{
name: "invalid provider key",
providerKey: "invalid",
providerName: "Name",
supportedTypes: "alipay",
wantErr: true,
errContains: "invalid provider key",
},
{
name: "empty name",
providerKey: "easypay",
providerName: "",
supportedTypes: "alipay",
wantErr: true,
errContains: "provider name is required",
},
{
name: "whitespace-only name",
providerKey: "easypay",
providerName: " ",
supportedTypes: "alipay",
wantErr: true,
errContains: "provider name is required",
},
{
name: "tab-only name",
providerKey: "easypay",
providerName: "\t",
supportedTypes: "alipay",
wantErr: true,
errContains: "provider name is required",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
err := validateProviderRequest(tc.providerKey, tc.providerName, tc.supportedTypes)
if tc.wantErr {
require.Error(t, err)
assert.Contains(t, err.Error(), tc.errContains)
} else {
require.NoError(t, err)
}
})
}
}
func TestIsSensitiveConfigField(t *testing.T) {
t.Parallel()
tests := []struct {
field string
wantSen bool
}{
// Sensitive fields (contain key/secret/private/password/pkey patterns)
{"secretKey", true},
{"apiSecret", true},
{"pkey", true},
{"privateKey", true},
{"apiPassword", true},
{"appKey", true},
{"SECRET_TOKEN", true},
{"PrivateData", true},
{"PASSWORD", true},
{"mySecretValue", true},
// Non-sensitive fields
{"appId", false},
{"mchId", false},
{"apiBase", false},
{"endpoint", false},
{"merchantNo", false},
{"paymentMode", false},
{"notifyUrl", false},
}
for _, tc := range tests {
t.Run(tc.field, func(t *testing.T) {
t.Parallel()
got := isSensitiveConfigField(tc.field)
assert.Equal(t, tc.wantSen, got, "isSensitiveConfigField(%q)", tc.field)
})
}
}
func TestJoinTypes(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input []string
want string
}{
{
name: "multiple types",
input: []string{"alipay", "wxpay"},
want: "alipay,wxpay",
},
{
name: "single type",
input: []string{"stripe"},
want: "stripe",
},
{
name: "empty slice",
input: []string{},
want: "",
},
{
name: "nil slice",
input: nil,
want: "",
},
{
name: "three types",
input: []string{"alipay", "wxpay", "stripe"},
want: "alipay,wxpay,stripe",
},
{
name: "types with spaces are not trimmed",
input: []string{" alipay ", " wxpay "},
want: " alipay , wxpay ",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := joinTypes(tc.input)
assert.Equal(t, tc.want, got)
})
}
}

View File

@ -0,0 +1,351 @@
package service
import (
"context"
"fmt"
"strconv"
"strings"
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
"github.com/Wei-Shaw/sub2api/internal/payment"
)
const (
SettingPaymentEnabled = "payment_enabled"
SettingMinRechargeAmount = "MIN_RECHARGE_AMOUNT"
SettingMaxRechargeAmount = "MAX_RECHARGE_AMOUNT"
SettingDailyRechargeLimit = "DAILY_RECHARGE_LIMIT"
SettingOrderTimeoutMinutes = "ORDER_TIMEOUT_MINUTES"
SettingMaxPendingOrders = "MAX_PENDING_ORDERS"
SettingEnabledPaymentTypes = "ENABLED_PAYMENT_TYPES"
SettingLoadBalanceStrategy = "LOAD_BALANCE_STRATEGY"
SettingBalancePayDisabled = "BALANCE_PAYMENT_DISABLED"
SettingProductNamePrefix = "PRODUCT_NAME_PREFIX"
SettingProductNameSuffix = "PRODUCT_NAME_SUFFIX"
SettingHelpImageURL = "PAYMENT_HELP_IMAGE_URL"
SettingHelpText = "PAYMENT_HELP_TEXT"
SettingCancelRateLimitOn = "CANCEL_RATE_LIMIT_ENABLED"
SettingCancelRateLimitMax = "CANCEL_RATE_LIMIT_MAX"
SettingCancelWindowSize = "CANCEL_RATE_LIMIT_WINDOW"
SettingCancelWindowUnit = "CANCEL_RATE_LIMIT_UNIT"
SettingCancelWindowMode = "CANCEL_RATE_LIMIT_WINDOW_MODE"
)
// Default values for payment configuration settings.
const (
defaultOrderTimeoutMin = 30
defaultMaxPendingOrders = 3
)
// PaymentConfig holds the payment system configuration.
type PaymentConfig struct {
Enabled bool `json:"enabled"`
MinAmount float64 `json:"min_amount"`
MaxAmount float64 `json:"max_amount"`
DailyLimit float64 `json:"daily_limit"`
OrderTimeoutMin int `json:"order_timeout_minutes"`
MaxPendingOrders int `json:"max_pending_orders"`
EnabledTypes []string `json:"enabled_payment_types"`
BalanceDisabled bool `json:"balance_disabled"`
LoadBalanceStrategy string `json:"load_balance_strategy"`
ProductNamePrefix string `json:"product_name_prefix"`
ProductNameSuffix string `json:"product_name_suffix"`
HelpImageURL string `json:"help_image_url"`
HelpText string `json:"help_text"`
StripePublishableKey string `json:"stripe_publishable_key,omitempty"`
// Cancel rate limit settings
CancelRateLimitEnabled bool `json:"cancel_rate_limit_enabled"`
CancelRateLimitMax int `json:"cancel_rate_limit_max"`
CancelRateLimitWindow int `json:"cancel_rate_limit_window"`
CancelRateLimitUnit string `json:"cancel_rate_limit_unit"`
CancelRateLimitMode string `json:"cancel_rate_limit_window_mode"`
}
// UpdatePaymentConfigRequest contains fields to update payment configuration.
type UpdatePaymentConfigRequest struct {
Enabled *bool `json:"enabled"`
MinAmount *float64 `json:"min_amount"`
MaxAmount *float64 `json:"max_amount"`
DailyLimit *float64 `json:"daily_limit"`
OrderTimeoutMin *int `json:"order_timeout_minutes"`
MaxPendingOrders *int `json:"max_pending_orders"`
EnabledTypes []string `json:"enabled_payment_types"`
BalanceDisabled *bool `json:"balance_disabled"`
LoadBalanceStrategy *string `json:"load_balance_strategy"`
ProductNamePrefix *string `json:"product_name_prefix"`
ProductNameSuffix *string `json:"product_name_suffix"`
HelpImageURL *string `json:"help_image_url"`
HelpText *string `json:"help_text"`
// Cancel rate limit settings
CancelRateLimitEnabled *bool `json:"cancel_rate_limit_enabled"`
CancelRateLimitMax *int `json:"cancel_rate_limit_max"`
CancelRateLimitWindow *int `json:"cancel_rate_limit_window"`
CancelRateLimitUnit *string `json:"cancel_rate_limit_unit"`
CancelRateLimitMode *string `json:"cancel_rate_limit_window_mode"`
}
// MethodLimits holds per-payment-type limits.
type MethodLimits struct {
PaymentType string `json:"payment_type"`
FeeRate float64 `json:"fee_rate"`
DailyLimit float64 `json:"daily_limit"`
SingleMin float64 `json:"single_min"`
SingleMax float64 `json:"single_max"`
}
// MethodLimitsResponse is the full response for the user-facing /limits API.
// It includes per-method limits and the global widest range (union of all methods).
type MethodLimitsResponse struct {
Methods map[string]MethodLimits `json:"methods"`
GlobalMin float64 `json:"global_min"` // 0 = no minimum
GlobalMax float64 `json:"global_max"` // 0 = no maximum
}
type CreateProviderInstanceRequest struct {
ProviderKey string `json:"provider_key"`
Name string `json:"name"`
Config map[string]string `json:"config"`
SupportedTypes []string `json:"supported_types"`
Enabled bool `json:"enabled"`
PaymentMode string `json:"payment_mode"`
SortOrder int `json:"sort_order"`
Limits string `json:"limits"`
RefundEnabled bool `json:"refund_enabled"`
}
type UpdateProviderInstanceRequest struct {
Name *string `json:"name"`
Config map[string]string `json:"config"`
SupportedTypes []string `json:"supported_types"`
Enabled *bool `json:"enabled"`
PaymentMode *string `json:"payment_mode"`
SortOrder *int `json:"sort_order"`
Limits *string `json:"limits"`
RefundEnabled *bool `json:"refund_enabled"`
}
type CreatePlanRequest struct {
GroupID int64 `json:"group_id"`
Name string `json:"name"`
Description string `json:"description"`
Price float64 `json:"price"`
OriginalPrice *float64 `json:"original_price"`
ValidityDays int `json:"validity_days"`
ValidityUnit string `json:"validity_unit"`
Features string `json:"features"`
ProductName string `json:"product_name"`
ForSale bool `json:"for_sale"`
SortOrder int `json:"sort_order"`
}
type UpdatePlanRequest struct {
GroupID *int64 `json:"group_id"`
Name *string `json:"name"`
Description *string `json:"description"`
Price *float64 `json:"price"`
OriginalPrice *float64 `json:"original_price"`
ValidityDays *int `json:"validity_days"`
ValidityUnit *string `json:"validity_unit"`
Features *string `json:"features"`
ProductName *string `json:"product_name"`
ForSale *bool `json:"for_sale"`
SortOrder *int `json:"sort_order"`
}
// PaymentConfigService manages payment configuration and CRUD for
// provider instances, channels, and subscription plans.
type PaymentConfigService struct {
entClient *dbent.Client
settingRepo SettingRepository
encryptionKey []byte
}
// NewPaymentConfigService creates a new PaymentConfigService.
func NewPaymentConfigService(entClient *dbent.Client, settingRepo SettingRepository, encryptionKey []byte) *PaymentConfigService {
return &PaymentConfigService{entClient: entClient, settingRepo: settingRepo, encryptionKey: encryptionKey}
}
// IsPaymentEnabled returns whether the payment system is enabled.
func (s *PaymentConfigService) IsPaymentEnabled(ctx context.Context) bool {
val, err := s.settingRepo.GetValue(ctx, SettingPaymentEnabled)
if err != nil {
return false
}
return val == "true"
}
// GetPaymentConfig returns the full payment configuration.
func (s *PaymentConfigService) GetPaymentConfig(ctx context.Context) (*PaymentConfig, error) {
keys := []string{
SettingPaymentEnabled, SettingMinRechargeAmount, SettingMaxRechargeAmount,
SettingDailyRechargeLimit, SettingOrderTimeoutMinutes, SettingMaxPendingOrders,
SettingEnabledPaymentTypes, SettingBalancePayDisabled, SettingLoadBalanceStrategy,
SettingProductNamePrefix, SettingProductNameSuffix,
SettingHelpImageURL, SettingHelpText,
SettingCancelRateLimitOn, SettingCancelRateLimitMax,
SettingCancelWindowSize, SettingCancelWindowUnit, SettingCancelWindowMode,
}
vals, err := s.settingRepo.GetMultiple(ctx, keys)
if err != nil {
return nil, fmt.Errorf("get payment config settings: %w", err)
}
cfg := s.parsePaymentConfig(vals)
// Load Stripe publishable key from the first enabled Stripe provider instance
cfg.StripePublishableKey = s.getStripePublishableKey(ctx)
return cfg, nil
}
func (s *PaymentConfigService) parsePaymentConfig(vals map[string]string) *PaymentConfig {
cfg := &PaymentConfig{
Enabled: vals[SettingPaymentEnabled] == "true",
MinAmount: pcParseFloat(vals[SettingMinRechargeAmount], 1),
MaxAmount: pcParseFloat(vals[SettingMaxRechargeAmount], 0),
DailyLimit: pcParseFloat(vals[SettingDailyRechargeLimit], 0),
OrderTimeoutMin: pcParseInt(vals[SettingOrderTimeoutMinutes], defaultOrderTimeoutMin),
MaxPendingOrders: pcParseInt(vals[SettingMaxPendingOrders], defaultMaxPendingOrders),
BalanceDisabled: vals[SettingBalancePayDisabled] == "true",
LoadBalanceStrategy: vals[SettingLoadBalanceStrategy],
ProductNamePrefix: vals[SettingProductNamePrefix],
ProductNameSuffix: vals[SettingProductNameSuffix],
HelpImageURL: vals[SettingHelpImageURL],
HelpText: vals[SettingHelpText],
CancelRateLimitEnabled: vals[SettingCancelRateLimitOn] == "true",
CancelRateLimitMax: pcParseInt(vals[SettingCancelRateLimitMax], 10),
CancelRateLimitWindow: pcParseInt(vals[SettingCancelWindowSize], 1),
CancelRateLimitUnit: vals[SettingCancelWindowUnit],
CancelRateLimitMode: vals[SettingCancelWindowMode],
}
if cfg.LoadBalanceStrategy == "" {
cfg.LoadBalanceStrategy = payment.DefaultLoadBalanceStrategy
}
if raw := vals[SettingEnabledPaymentTypes]; raw != "" {
for _, t := range strings.Split(raw, ",") {
t = strings.TrimSpace(t)
if t != "" {
cfg.EnabledTypes = append(cfg.EnabledTypes, t)
}
}
}
return cfg
}
// getStripePublishableKey finds the publishable key from the first enabled Stripe provider instance.
func (s *PaymentConfigService) getStripePublishableKey(ctx context.Context) string {
instances, err := s.entClient.PaymentProviderInstance.Query().
Where(
paymentproviderinstance.EnabledEQ(true),
paymentproviderinstance.ProviderKeyEQ(payment.TypeStripe),
).Limit(1).All(ctx)
if err != nil || len(instances) == 0 {
return ""
}
cfg, err := s.decryptConfig(instances[0].Config)
if err != nil || cfg == nil {
return ""
}
return cfg[payment.ConfigKeyPublishableKey]
}
// UpdatePaymentConfig updates the payment configuration settings.
// NOTE: This function exceeds 30 lines because each field requires an independent
// nil-check before serialisation — this is inherent to patch-style update patterns
// and cannot be meaningfully decomposed without introducing unnecessary abstraction.
func (s *PaymentConfigService) UpdatePaymentConfig(ctx context.Context, req UpdatePaymentConfigRequest) error {
m := map[string]string{
SettingPaymentEnabled: formatBoolOrEmpty(req.Enabled),
SettingMinRechargeAmount: formatPositiveFloat(req.MinAmount),
SettingMaxRechargeAmount: formatPositiveFloat(req.MaxAmount),
SettingDailyRechargeLimit: formatPositiveFloat(req.DailyLimit),
SettingOrderTimeoutMinutes: formatPositiveInt(req.OrderTimeoutMin),
SettingMaxPendingOrders: formatPositiveInt(req.MaxPendingOrders),
SettingBalancePayDisabled: formatBoolOrEmpty(req.BalanceDisabled),
SettingLoadBalanceStrategy: derefStr(req.LoadBalanceStrategy),
SettingProductNamePrefix: derefStr(req.ProductNamePrefix),
SettingProductNameSuffix: derefStr(req.ProductNameSuffix),
SettingHelpImageURL: derefStr(req.HelpImageURL),
SettingHelpText: derefStr(req.HelpText),
SettingCancelRateLimitOn: formatBoolOrEmpty(req.CancelRateLimitEnabled),
SettingCancelRateLimitMax: formatPositiveInt(req.CancelRateLimitMax),
SettingCancelWindowSize: formatPositiveInt(req.CancelRateLimitWindow),
SettingCancelWindowUnit: derefStr(req.CancelRateLimitUnit),
SettingCancelWindowMode: derefStr(req.CancelRateLimitMode),
}
if req.EnabledTypes != nil {
m[SettingEnabledPaymentTypes] = strings.Join(req.EnabledTypes, ",")
} else {
m[SettingEnabledPaymentTypes] = ""
}
return s.settingRepo.SetMultiple(ctx, m)
}
func formatBoolOrEmpty(v *bool) string {
if v == nil {
return ""
}
return strconv.FormatBool(*v)
}
func formatPositiveFloat(v *float64) string {
if v == nil || *v <= 0 {
return "" // empty → parsePaymentConfig uses default
}
return strconv.FormatFloat(*v, 'f', 2, 64)
}
func formatPositiveInt(v *int) string {
if v == nil || *v <= 0 {
return ""
}
return strconv.Itoa(*v)
}
func derefStr(v *string) string {
if v == nil {
return ""
}
return *v
}
func splitTypes(s string) []string {
if s == "" {
return nil
}
parts := strings.Split(s, ",")
result := make([]string, 0, len(parts))
for _, p := range parts {
p = strings.TrimSpace(p)
if p != "" {
result = append(result, p)
}
}
return result
}
func joinTypes(types []string) string {
return strings.Join(types, ",")
}
func pcParseFloat(s string, defaultVal float64) float64 {
if s == "" {
return defaultVal
}
v, err := strconv.ParseFloat(s, 64)
if err != nil {
return defaultVal
}
return v
}
func pcParseInt(s string, defaultVal int) int {
if s == "" {
return defaultVal
}
v, err := strconv.Atoi(s)
if err != nil {
return defaultVal
}
return v
}

View File

@ -0,0 +1,206 @@
package service
import (
"testing"
"github.com/Wei-Shaw/sub2api/internal/payment"
)
func TestPcParseFloat(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
defaultVal float64
expected float64
}{
{"empty string returns default", "", 1.0, 1.0},
{"valid float", "3.14", 0, 3.14},
{"valid integer as float", "42", 0, 42.0},
{"invalid string returns default", "notanumber", 9.99, 9.99},
{"zero value", "0", 5.0, 0},
{"negative value", "-10.5", 0, -10.5},
{"very large value", "99999999.99", 0, 99999999.99},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := pcParseFloat(tt.input, tt.defaultVal)
if got != tt.expected {
t.Fatalf("pcParseFloat(%q, %v) = %v, want %v", tt.input, tt.defaultVal, got, tt.expected)
}
})
}
}
func TestPcParseInt(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
defaultVal int
expected int
}{
{"empty string returns default", "", 30, 30},
{"valid int", "10", 0, 10},
{"invalid string returns default", "abc", 5, 5},
{"float string returns default", "3.14", 0, 0},
{"zero value", "0", 99, 0},
{"negative value", "-1", 0, -1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := pcParseInt(tt.input, tt.defaultVal)
if got != tt.expected {
t.Fatalf("pcParseInt(%q, %v) = %v, want %v", tt.input, tt.defaultVal, got, tt.expected)
}
})
}
}
func TestParsePaymentConfig(t *testing.T) {
t.Parallel()
svc := &PaymentConfigService{}
t.Run("empty vals uses defaults", func(t *testing.T) {
t.Parallel()
cfg := svc.parsePaymentConfig(map[string]string{})
if cfg.Enabled {
t.Fatal("expected Enabled=false by default")
}
if cfg.MinAmount != 1 {
t.Fatalf("expected MinAmount=1, got %v", cfg.MinAmount)
}
if cfg.MaxAmount != 0 {
t.Fatalf("expected MaxAmount=0 (no limit), got %v", cfg.MaxAmount)
}
if cfg.OrderTimeoutMin != 30 {
t.Fatalf("expected OrderTimeoutMin=30, got %v", cfg.OrderTimeoutMin)
}
if cfg.MaxPendingOrders != 3 {
t.Fatalf("expected MaxPendingOrders=3, got %v", cfg.MaxPendingOrders)
}
if cfg.LoadBalanceStrategy != payment.DefaultLoadBalanceStrategy {
t.Fatalf("expected LoadBalanceStrategy=%s, got %q", payment.DefaultLoadBalanceStrategy, cfg.LoadBalanceStrategy)
}
if len(cfg.EnabledTypes) != 0 {
t.Fatalf("expected empty EnabledTypes, got %v", cfg.EnabledTypes)
}
})
t.Run("all values populated", func(t *testing.T) {
t.Parallel()
vals := map[string]string{
SettingPaymentEnabled: "true",
SettingMinRechargeAmount: "5.00",
SettingMaxRechargeAmount: "1000.00",
SettingDailyRechargeLimit: "5000.00",
SettingOrderTimeoutMinutes: "15",
SettingMaxPendingOrders: "5",
SettingEnabledPaymentTypes: "alipay,wxpay,stripe",
SettingBalancePayDisabled: "true",
SettingLoadBalanceStrategy: "least_amount",
SettingProductNamePrefix: "PRE",
SettingProductNameSuffix: "SUF",
}
cfg := svc.parsePaymentConfig(vals)
if !cfg.Enabled {
t.Fatal("expected Enabled=true")
}
if cfg.MinAmount != 5 {
t.Fatalf("MinAmount = %v, want 5", cfg.MinAmount)
}
if cfg.MaxAmount != 1000 {
t.Fatalf("MaxAmount = %v, want 1000", cfg.MaxAmount)
}
if cfg.DailyLimit != 5000 {
t.Fatalf("DailyLimit = %v, want 5000", cfg.DailyLimit)
}
if cfg.OrderTimeoutMin != 15 {
t.Fatalf("OrderTimeoutMin = %v, want 15", cfg.OrderTimeoutMin)
}
if cfg.MaxPendingOrders != 5 {
t.Fatalf("MaxPendingOrders = %v, want 5", cfg.MaxPendingOrders)
}
if len(cfg.EnabledTypes) != 3 {
t.Fatalf("EnabledTypes len = %d, want 3", len(cfg.EnabledTypes))
}
if cfg.EnabledTypes[0] != "alipay" || cfg.EnabledTypes[1] != "wxpay" || cfg.EnabledTypes[2] != "stripe" {
t.Fatalf("EnabledTypes = %v, want [alipay wxpay stripe]", cfg.EnabledTypes)
}
if !cfg.BalanceDisabled {
t.Fatal("expected BalanceDisabled=true")
}
if cfg.LoadBalanceStrategy != "least_amount" {
t.Fatalf("LoadBalanceStrategy = %q, want %q", cfg.LoadBalanceStrategy, "least_amount")
}
if cfg.ProductNamePrefix != "PRE" {
t.Fatalf("ProductNamePrefix = %q, want %q", cfg.ProductNamePrefix, "PRE")
}
if cfg.ProductNameSuffix != "SUF" {
t.Fatalf("ProductNameSuffix = %q, want %q", cfg.ProductNameSuffix, "SUF")
}
})
t.Run("enabled types with spaces are trimmed", func(t *testing.T) {
t.Parallel()
vals := map[string]string{
SettingEnabledPaymentTypes: " alipay , wxpay ",
}
cfg := svc.parsePaymentConfig(vals)
if len(cfg.EnabledTypes) != 2 {
t.Fatalf("EnabledTypes len = %d, want 2", len(cfg.EnabledTypes))
}
if cfg.EnabledTypes[0] != "alipay" || cfg.EnabledTypes[1] != "wxpay" {
t.Fatalf("EnabledTypes = %v, want [alipay wxpay]", cfg.EnabledTypes)
}
})
t.Run("empty enabled types string", func(t *testing.T) {
t.Parallel()
vals := map[string]string{
SettingEnabledPaymentTypes: "",
}
cfg := svc.parsePaymentConfig(vals)
if len(cfg.EnabledTypes) != 0 {
t.Fatalf("expected empty EnabledTypes for empty string, got %v", cfg.EnabledTypes)
}
})
}
func TestGetBasePaymentType(t *testing.T) {
t.Parallel()
tests := []struct {
input string
expected string
}{
{payment.TypeEasyPay, payment.TypeEasyPay},
{payment.TypeStripe, payment.TypeStripe},
{payment.TypeCard, payment.TypeStripe},
{payment.TypeLink, payment.TypeStripe},
{payment.TypeAlipay, payment.TypeAlipay},
{payment.TypeAlipayDirect, payment.TypeAlipay},
{payment.TypeWxpay, payment.TypeWxpay},
{payment.TypeWxpayDirect, payment.TypeWxpay},
{"unknown", "unknown"},
{"", ""},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
t.Parallel()
got := payment.GetBasePaymentType(tt.input)
if got != tt.expected {
t.Fatalf("GetBasePaymentType(%q) = %q, want %q", tt.input, got, tt.expected)
}
})
}
}

View File

@ -0,0 +1,325 @@
package service
import (
"context"
"fmt"
"log/slog"
"math"
"strconv"
"strings"
"time"
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/paymentauditlog"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/internal/payment"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
)
// --- Payment Notification & Fulfillment ---
func (s *PaymentService) HandlePaymentNotification(ctx context.Context, n *payment.PaymentNotification, pk string) error {
if n.Status != payment.NotificationStatusSuccess {
return nil
}
// Look up order by out_trade_no (the external order ID we sent to the provider)
order, err := s.entClient.PaymentOrder.Query().Where(paymentorder.OutTradeNo(n.OrderID)).Only(ctx)
if err != nil {
// Fallback: try legacy format (sub2_N where N is DB ID)
trimmed := strings.TrimPrefix(n.OrderID, orderIDPrefix)
if oid, parseErr := strconv.ParseInt(trimmed, 10, 64); parseErr == nil {
return s.confirmPayment(ctx, oid, n.TradeNo, n.Amount, pk)
}
return fmt.Errorf("order not found for out_trade_no: %s", n.OrderID)
}
return s.confirmPayment(ctx, order.ID, n.TradeNo, n.Amount, pk)
}
func (s *PaymentService) confirmPayment(ctx context.Context, oid int64, tradeNo string, paid float64, pk string) error {
o, err := s.entClient.PaymentOrder.Get(ctx, oid)
if err != nil {
slog.Error("order not found", "orderID", oid)
return nil
}
// Skip amount check when paid=0 (e.g. QueryOrder doesn't return amount).
// Also skip if paid is NaN/Inf (malformed provider data).
if paid > 0 && !math.IsNaN(paid) && !math.IsInf(paid, 0) {
if math.Abs(paid-o.PayAmount) > amountToleranceCNY {
s.writeAuditLog(ctx, o.ID, "PAYMENT_AMOUNT_MISMATCH", pk, map[string]any{"expected": o.PayAmount, "paid": paid, "tradeNo": tradeNo})
return fmt.Errorf("amount mismatch: expected %.2f, got %.2f", o.PayAmount, paid)
}
}
// Use order's expected amount when provider didn't report one
if paid <= 0 || math.IsNaN(paid) || math.IsInf(paid, 0) {
paid = o.PayAmount
}
return s.toPaid(ctx, o, tradeNo, paid, pk)
}
func (s *PaymentService) toPaid(ctx context.Context, o *dbent.PaymentOrder, tradeNo string, paid float64, pk string) error {
previousStatus := o.Status
now := time.Now()
grace := now.Add(-paymentGraceMinutes * time.Minute)
c, err := s.entClient.PaymentOrder.Update().Where(
paymentorder.IDEQ(o.ID),
paymentorder.Or(
paymentorder.StatusEQ(OrderStatusPending),
paymentorder.StatusEQ(OrderStatusCancelled),
paymentorder.And(
paymentorder.StatusEQ(OrderStatusExpired),
paymentorder.UpdatedAtGTE(grace),
),
),
).SetStatus(OrderStatusPaid).SetPayAmount(paid).SetPaymentTradeNo(tradeNo).SetPaidAt(now).ClearFailedAt().ClearFailedReason().Save(ctx)
if err != nil {
return fmt.Errorf("update to PAID: %w", err)
}
if c == 0 {
return s.alreadyProcessed(ctx, o)
}
if previousStatus == OrderStatusCancelled || previousStatus == OrderStatusExpired {
slog.Info("order recovered from webhook payment success",
"orderID", o.ID,
"previousStatus", previousStatus,
"tradeNo", tradeNo,
"provider", pk,
)
s.writeAuditLog(ctx, o.ID, "ORDER_RECOVERED", pk, map[string]any{
"previous_status": previousStatus,
"tradeNo": tradeNo,
"paidAmount": paid,
"reason": "webhook payment success received after order " + previousStatus,
})
}
s.writeAuditLog(ctx, o.ID, "ORDER_PAID", pk, map[string]any{"tradeNo": tradeNo, "paidAmount": paid})
return s.executeFulfillment(ctx, o.ID)
}
func (s *PaymentService) alreadyProcessed(ctx context.Context, o *dbent.PaymentOrder) error {
cur, err := s.entClient.PaymentOrder.Get(ctx, o.ID)
if err != nil {
return nil
}
switch cur.Status {
case OrderStatusCompleted, OrderStatusRefunded:
return nil
case OrderStatusFailed:
return s.executeFulfillment(ctx, o.ID)
case OrderStatusPaid, OrderStatusRecharging:
return fmt.Errorf("order %d is being processed", o.ID)
case OrderStatusExpired:
slog.Warn("webhook payment success for expired order beyond grace period",
"orderID", o.ID,
"status", cur.Status,
"updatedAt", cur.UpdatedAt,
)
s.writeAuditLog(ctx, o.ID, "PAYMENT_AFTER_EXPIRY", "system", map[string]any{
"status": cur.Status,
"updatedAt": cur.UpdatedAt,
"reason": "payment arrived after expiry grace period",
})
return nil
default:
return nil
}
}
func (s *PaymentService) executeFulfillment(ctx context.Context, oid int64) error {
o, err := s.entClient.PaymentOrder.Get(ctx, oid)
if err != nil {
return fmt.Errorf("get order: %w", err)
}
if o.OrderType == payment.OrderTypeSubscription {
return s.ExecuteSubscriptionFulfillment(ctx, oid)
}
return s.ExecuteBalanceFulfillment(ctx, oid)
}
func (s *PaymentService) ExecuteBalanceFulfillment(ctx context.Context, oid int64) error {
o, err := s.entClient.PaymentOrder.Get(ctx, oid)
if err != nil {
return infraerrors.NotFound("NOT_FOUND", "order not found")
}
if o.Status == OrderStatusCompleted {
return nil
}
if psIsRefundStatus(o.Status) {
return infraerrors.BadRequest("INVALID_STATUS", "refund-related order cannot fulfill")
}
if o.Status != OrderStatusPaid && o.Status != OrderStatusFailed {
return infraerrors.BadRequest("INVALID_STATUS", "order cannot fulfill in status "+o.Status)
}
c, err := s.entClient.PaymentOrder.Update().Where(paymentorder.IDEQ(oid), paymentorder.StatusIn(OrderStatusPaid, OrderStatusFailed)).SetStatus(OrderStatusRecharging).Save(ctx)
if err != nil {
return fmt.Errorf("lock: %w", err)
}
if c == 0 {
return nil
}
if err := s.doBalance(ctx, o); err != nil {
s.markFailed(ctx, oid, err)
return err
}
return nil
}
// redeemAction represents the idempotency decision for balance fulfillment.
type redeemAction int
const (
// redeemActionCreate: code does not exist — create it, then redeem.
redeemActionCreate redeemAction = iota
// redeemActionRedeem: code exists but is unused — skip creation, redeem only.
redeemActionRedeem
// redeemActionSkipCompleted: code exists and is already used — skip to mark completed.
redeemActionSkipCompleted
)
// resolveRedeemAction decides the idempotency action based on an existing redeem code lookup.
// existing is the result of GetByCode; lookupErr is the error from that call.
func resolveRedeemAction(existing *RedeemCode, lookupErr error) redeemAction {
if existing == nil || lookupErr != nil {
return redeemActionCreate
}
if existing.IsUsed() {
return redeemActionSkipCompleted
}
return redeemActionRedeem
}
func (s *PaymentService) doBalance(ctx context.Context, o *dbent.PaymentOrder) error {
// Idempotency: check if redeem code already exists (from a previous partial run)
existing, lookupErr := s.redeemService.GetByCode(ctx, o.RechargeCode)
action := resolveRedeemAction(existing, lookupErr)
switch action {
case redeemActionSkipCompleted:
// Code already created and redeemed — just mark completed
return s.markCompleted(ctx, o, "RECHARGE_SUCCESS")
case redeemActionCreate:
rc := &RedeemCode{Code: o.RechargeCode, Type: RedeemTypeBalance, Value: o.Amount, Status: StatusUnused}
if err := s.redeemService.CreateCode(ctx, rc); err != nil {
return fmt.Errorf("create redeem code: %w", err)
}
case redeemActionRedeem:
// Code exists but unused — skip creation, proceed to redeem
}
if _, err := s.redeemService.Redeem(ctx, o.UserID, o.RechargeCode); err != nil {
return fmt.Errorf("redeem balance: %w", err)
}
return s.markCompleted(ctx, o, "RECHARGE_SUCCESS")
}
func (s *PaymentService) markCompleted(ctx context.Context, o *dbent.PaymentOrder, auditAction string) error {
now := time.Now()
_, err := s.entClient.PaymentOrder.Update().Where(paymentorder.IDEQ(o.ID), paymentorder.StatusEQ(OrderStatusRecharging)).SetStatus(OrderStatusCompleted).SetCompletedAt(now).Save(ctx)
if err != nil {
return fmt.Errorf("mark completed: %w", err)
}
s.writeAuditLog(ctx, o.ID, auditAction, "system", map[string]any{"rechargeCode": o.RechargeCode, "amount": o.Amount})
return nil
}
func (s *PaymentService) ExecuteSubscriptionFulfillment(ctx context.Context, oid int64) error {
o, err := s.entClient.PaymentOrder.Get(ctx, oid)
if err != nil {
return infraerrors.NotFound("NOT_FOUND", "order not found")
}
if o.Status == OrderStatusCompleted {
return nil
}
if psIsRefundStatus(o.Status) {
return infraerrors.BadRequest("INVALID_STATUS", "refund-related order cannot fulfill")
}
if o.Status != OrderStatusPaid && o.Status != OrderStatusFailed {
return infraerrors.BadRequest("INVALID_STATUS", "order cannot fulfill in status "+o.Status)
}
if o.SubscriptionGroupID == nil || o.SubscriptionDays == nil {
return infraerrors.BadRequest("INVALID_STATUS", "missing subscription info")
}
c, err := s.entClient.PaymentOrder.Update().Where(paymentorder.IDEQ(oid), paymentorder.StatusIn(OrderStatusPaid, OrderStatusFailed)).SetStatus(OrderStatusRecharging).Save(ctx)
if err != nil {
return fmt.Errorf("lock: %w", err)
}
if c == 0 {
return nil
}
if err := s.doSub(ctx, o); err != nil {
s.markFailed(ctx, oid, err)
return err
}
return nil
}
func (s *PaymentService) doSub(ctx context.Context, o *dbent.PaymentOrder) error {
gid := *o.SubscriptionGroupID
days := *o.SubscriptionDays
g, err := s.groupRepo.GetByID(ctx, gid)
if err != nil || g.Status != payment.EntityStatusActive {
return fmt.Errorf("group %d no longer exists or inactive", gid)
}
// Idempotency: check audit log to see if subscription was already assigned.
// Prevents double-extension on retry after markCompleted fails.
if s.hasAuditLog(ctx, o.ID, "SUBSCRIPTION_SUCCESS") {
slog.Info("subscription already assigned for order, skipping", "orderID", o.ID, "groupID", gid)
return s.markCompleted(ctx, o, "SUBSCRIPTION_SUCCESS")
}
orderNote := fmt.Sprintf("payment order %d", o.ID)
_, _, err = s.subscriptionSvc.AssignOrExtendSubscription(ctx, &AssignSubscriptionInput{UserID: o.UserID, GroupID: gid, ValidityDays: days, AssignedBy: 0, Notes: orderNote})
if err != nil {
return fmt.Errorf("assign subscription: %w", err)
}
return s.markCompleted(ctx, o, "SUBSCRIPTION_SUCCESS")
}
func (s *PaymentService) hasAuditLog(ctx context.Context, orderID int64, action string) bool {
oid := strconv.FormatInt(orderID, 10)
c, _ := s.entClient.PaymentAuditLog.Query().
Where(paymentauditlog.OrderIDEQ(oid), paymentauditlog.ActionEQ(action)).
Limit(1).Count(ctx)
return c > 0
}
func (s *PaymentService) markFailed(ctx context.Context, oid int64, cause error) {
now := time.Now()
r := psErrMsg(cause)
// Only mark FAILED if still in RECHARGING state — prevents overwriting
// a COMPLETED order when markCompleted failed but fulfillment succeeded.
c, e := s.entClient.PaymentOrder.Update().
Where(paymentorder.IDEQ(oid), paymentorder.StatusEQ(OrderStatusRecharging)).
SetStatus(OrderStatusFailed).SetFailedAt(now).SetFailedReason(r).Save(ctx)
if e != nil {
slog.Error("mark FAILED", "orderID", oid, "error", e)
}
if c > 0 {
s.writeAuditLog(ctx, oid, "FULFILLMENT_FAILED", "system", map[string]any{"reason": r})
}
}
func (s *PaymentService) RetryFulfillment(ctx context.Context, oid int64) error {
o, err := s.entClient.PaymentOrder.Get(ctx, oid)
if err != nil {
return infraerrors.NotFound("NOT_FOUND", "order not found")
}
if o.PaidAt == nil {
return infraerrors.BadRequest("INVALID_STATUS", "order is not paid")
}
if psIsRefundStatus(o.Status) {
return infraerrors.BadRequest("INVALID_STATUS", "refund-related order cannot retry")
}
if o.Status == OrderStatusRecharging {
return infraerrors.Conflict("CONFLICT", "order is being processed")
}
if o.Status == OrderStatusCompleted {
return infraerrors.BadRequest("INVALID_STATUS", "order already completed")
}
if o.Status != OrderStatusFailed && o.Status != OrderStatusPaid {
return infraerrors.BadRequest("INVALID_STATUS", "only paid and failed orders can retry")
}
_, err = s.entClient.PaymentOrder.Update().Where(paymentorder.IDEQ(oid), paymentorder.StatusIn(OrderStatusFailed, OrderStatusPaid)).SetStatus(OrderStatusPaid).ClearFailedAt().ClearFailedReason().Save(ctx)
if err != nil {
return fmt.Errorf("reset for retry: %w", err)
}
s.writeAuditLog(ctx, oid, "RECHARGE_RETRY", "admin", map[string]any{"detail": "admin manual retry"})
return s.executeFulfillment(ctx, oid)
}

View File

@ -0,0 +1,163 @@
//go:build unit
package service
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
// ---------------------------------------------------------------------------
// resolveRedeemAction — pure idempotency decision logic
// ---------------------------------------------------------------------------
func TestResolveRedeemAction_CodeNotFound(t *testing.T) {
t.Parallel()
action := resolveRedeemAction(nil, nil)
assert.Equal(t, redeemActionCreate, action, "nil code with nil error should create")
}
func TestResolveRedeemAction_LookupError(t *testing.T) {
t.Parallel()
action := resolveRedeemAction(nil, errors.New("db connection lost"))
assert.Equal(t, redeemActionCreate, action, "lookup error should fall back to create")
}
func TestResolveRedeemAction_LookupErrorWithNonNilCode(t *testing.T) {
t.Parallel()
// Edge case: both code and error are non-nil (shouldn't happen in practice,
// but the function should still treat error as authoritative)
code := &RedeemCode{Status: StatusUnused}
action := resolveRedeemAction(code, errors.New("partial error"))
assert.Equal(t, redeemActionCreate, action, "non-nil error should always result in create regardless of code")
}
func TestResolveRedeemAction_CodeExistsAndUsed(t *testing.T) {
t.Parallel()
code := &RedeemCode{
Code: "test-code-123",
Status: StatusUsed,
Type: RedeemTypeBalance,
Value: 10.0,
}
action := resolveRedeemAction(code, nil)
assert.Equal(t, redeemActionSkipCompleted, action, "used code should skip to completed")
}
func TestResolveRedeemAction_CodeExistsAndUnused(t *testing.T) {
t.Parallel()
code := &RedeemCode{
Code: "test-code-456",
Status: StatusUnused,
Type: RedeemTypeBalance,
Value: 25.0,
}
action := resolveRedeemAction(code, nil)
assert.Equal(t, redeemActionRedeem, action, "unused code should skip creation and proceed to redeem")
}
func TestResolveRedeemAction_CodeExistsWithExpiredStatus(t *testing.T) {
t.Parallel()
// A code with a non-standard status (neither "unused" nor "used")
// should NOT be treated as used, so it falls through to redeemActionRedeem.
code := &RedeemCode{
Code: "expired-code",
Status: StatusExpired,
}
action := resolveRedeemAction(code, nil)
assert.Equal(t, redeemActionRedeem, action, "expired-status code is not IsUsed(), should redeem")
}
// ---------------------------------------------------------------------------
// Table-driven comprehensive test
// ---------------------------------------------------------------------------
func TestResolveRedeemAction_Table(t *testing.T) {
t.Parallel()
tests := []struct {
name string
code *RedeemCode
err error
expected redeemAction
}{
{
name: "nil code, nil error — first run",
code: nil,
err: nil,
expected: redeemActionCreate,
},
{
name: "nil code, lookup error — treat as not found",
code: nil,
err: ErrRedeemCodeNotFound,
expected: redeemActionCreate,
},
{
name: "nil code, generic DB error — treat as not found",
code: nil,
err: errors.New("connection refused"),
expected: redeemActionCreate,
},
{
name: "code exists, used — previous run completed redeem",
code: &RedeemCode{Status: StatusUsed},
err: nil,
expected: redeemActionSkipCompleted,
},
{
name: "code exists, unused — previous run created code but crashed before redeem",
code: &RedeemCode{Status: StatusUnused},
err: nil,
expected: redeemActionRedeem,
},
{
name: "code exists but error also set — error takes precedence",
code: &RedeemCode{Status: StatusUsed},
err: errors.New("unexpected"),
expected: redeemActionCreate,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := resolveRedeemAction(tt.code, tt.err)
assert.Equal(t, tt.expected, got)
})
}
}
// ---------------------------------------------------------------------------
// redeemAction enum value sanity
// ---------------------------------------------------------------------------
func TestRedeemAction_DistinctValues(t *testing.T) {
t.Parallel()
// Ensure the three actions have distinct values (iota correctness)
assert.NotEqual(t, redeemActionCreate, redeemActionRedeem)
assert.NotEqual(t, redeemActionCreate, redeemActionSkipCompleted)
assert.NotEqual(t, redeemActionRedeem, redeemActionSkipCompleted)
}
// ---------------------------------------------------------------------------
// RedeemCode.IsUsed / CanUse interaction with resolveRedeemAction
// ---------------------------------------------------------------------------
func TestResolveRedeemAction_IsUsedCanUseConsistency(t *testing.T) {
t.Parallel()
usedCode := &RedeemCode{Status: StatusUsed}
unusedCode := &RedeemCode{Status: StatusUnused}
// Verify our decision function is consistent with the domain model methods
assert.True(t, usedCode.IsUsed())
assert.False(t, usedCode.CanUse())
assert.Equal(t, redeemActionSkipCompleted, resolveRedeemAction(usedCode, nil))
assert.False(t, unusedCode.IsUsed())
assert.True(t, unusedCode.CanUse())
assert.Equal(t, redeemActionRedeem, resolveRedeemAction(unusedCode, nil))
}

View File

@ -0,0 +1,314 @@
package service
import (
"context"
"fmt"
"log/slog"
"math"
"strconv"
"strings"
"time"
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/internal/payment"
"github.com/Wei-Shaw/sub2api/internal/payment/provider"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
)
// --- Order Creation ---
func (s *PaymentService) CreateOrder(ctx context.Context, req CreateOrderRequest) (*CreateOrderResponse, error) {
if req.OrderType == "" {
req.OrderType = payment.OrderTypeBalance
}
cfg, err := s.configService.GetPaymentConfig(ctx)
if err != nil {
return nil, fmt.Errorf("get payment config: %w", err)
}
if !cfg.Enabled {
return nil, infraerrors.Forbidden("PAYMENT_DISABLED", "payment system is disabled")
}
plan, err := s.validateOrderInput(ctx, req, cfg)
if err != nil {
return nil, err
}
if err := s.checkCancelRateLimit(ctx, req.UserID, cfg); err != nil {
return nil, err
}
user, err := s.userRepo.GetByID(ctx, req.UserID)
if err != nil {
return nil, fmt.Errorf("get user: %w", err)
}
if user.Status != payment.EntityStatusActive {
return nil, infraerrors.Forbidden("USER_INACTIVE", "user account is disabled")
}
amount := req.Amount
if plan != nil {
amount = plan.Price
}
feeRate := s.getFeeRate(req.PaymentType)
payAmountStr := payment.CalculatePayAmount(amount, feeRate)
payAmount, _ := strconv.ParseFloat(payAmountStr, 64)
order, err := s.createOrderInTx(ctx, req, user, plan, cfg, amount, feeRate, payAmount)
if err != nil {
return nil, err
}
resp, err := s.invokeProvider(ctx, order, req, cfg, payAmountStr, payAmount, plan)
if err != nil {
_, _ = s.entClient.PaymentOrder.UpdateOneID(order.ID).
SetStatus(OrderStatusFailed).
Save(ctx)
return nil, err
}
return resp, nil
}
func (s *PaymentService) validateOrderInput(ctx context.Context, req CreateOrderRequest, cfg *PaymentConfig) (*dbent.SubscriptionPlan, error) {
if req.OrderType == payment.OrderTypeBalance && cfg.BalanceDisabled {
return nil, infraerrors.Forbidden("BALANCE_PAYMENT_DISABLED", "balance recharge has been disabled")
}
if req.OrderType == payment.OrderTypeSubscription {
return s.validateSubOrder(ctx, req)
}
if math.IsNaN(req.Amount) || math.IsInf(req.Amount, 0) || req.Amount <= 0 {
return nil, infraerrors.BadRequest("INVALID_AMOUNT", "amount must be a positive number")
}
if (cfg.MinAmount > 0 && req.Amount < cfg.MinAmount) || (cfg.MaxAmount > 0 && req.Amount > cfg.MaxAmount) {
return nil, infraerrors.BadRequest("INVALID_AMOUNT", "amount out of range").
WithMetadata(map[string]string{"min": fmt.Sprintf("%.2f", cfg.MinAmount), "max": fmt.Sprintf("%.2f", cfg.MaxAmount)})
}
return nil, nil
}
func (s *PaymentService) validateSubOrder(ctx context.Context, req CreateOrderRequest) (*dbent.SubscriptionPlan, error) {
if req.PlanID == 0 {
return nil, infraerrors.BadRequest("INVALID_INPUT", "subscription order requires a plan")
}
plan, err := s.configService.GetPlan(ctx, req.PlanID)
if err != nil || !plan.ForSale {
return nil, infraerrors.NotFound("PLAN_NOT_AVAILABLE", "plan not found or not for sale")
}
group, err := s.groupRepo.GetByID(ctx, plan.GroupID)
if err != nil || group.Status != payment.EntityStatusActive {
return nil, infraerrors.NotFound("GROUP_NOT_FOUND", "subscription group is no longer available")
}
if !group.IsSubscriptionType() {
return nil, infraerrors.BadRequest("GROUP_TYPE_MISMATCH", "group is not a subscription type")
}
return plan, nil
}
func (s *PaymentService) createOrderInTx(ctx context.Context, req CreateOrderRequest, user *User, plan *dbent.SubscriptionPlan, cfg *PaymentConfig, amount, feeRate, payAmount float64) (*dbent.PaymentOrder, error) {
tx, err := s.entClient.Tx(ctx)
if err != nil {
return nil, fmt.Errorf("begin transaction: %w", err)
}
defer func() { _ = tx.Rollback() }()
if err := s.checkPendingLimit(ctx, tx, req.UserID, cfg.MaxPendingOrders); err != nil {
return nil, err
}
if err := s.checkDailyLimit(ctx, tx, req.UserID, amount, cfg.DailyLimit); err != nil {
return nil, err
}
tm := cfg.OrderTimeoutMin
if tm <= 0 {
tm = defaultOrderTimeoutMin
}
exp := time.Now().Add(time.Duration(tm) * time.Minute)
b := tx.PaymentOrder.Create().
SetUserID(req.UserID).
SetUserEmail(user.Email).
SetUserName(user.Username).
SetNillableUserNotes(psNilIfEmpty(user.Notes)).
SetAmount(amount).
SetPayAmount(payAmount).
SetFeeRate(feeRate).
SetRechargeCode("").
SetOutTradeNo(generateOutTradeNo()).
SetPaymentType(req.PaymentType).
SetPaymentTradeNo("").
SetOrderType(req.OrderType).
SetStatus(OrderStatusPending).
SetExpiresAt(exp).
SetClientIP(req.ClientIP).
SetSrcHost(req.SrcHost)
if req.SrcURL != "" {
b.SetSrcURL(req.SrcURL)
}
if plan != nil {
b.SetPlanID(plan.ID).SetSubscriptionGroupID(plan.GroupID).SetSubscriptionDays(psComputeValidityDays(plan.ValidityDays, plan.ValidityUnit))
}
order, err := b.Save(ctx)
if err != nil {
return nil, fmt.Errorf("create order: %w", err)
}
code := fmt.Sprintf("PAY-%d-%d", order.ID, time.Now().UnixNano()%100000)
order, err = tx.PaymentOrder.UpdateOneID(order.ID).SetRechargeCode(code).Save(ctx)
if err != nil {
return nil, fmt.Errorf("set recharge code: %w", err)
}
if err := tx.Commit(); err != nil {
return nil, fmt.Errorf("commit order transaction: %w", err)
}
return order, nil
}
func (s *PaymentService) checkPendingLimit(ctx context.Context, tx *dbent.Tx, userID int64, max int) error {
if max <= 0 {
max = defaultMaxPendingOrders
}
c, err := tx.PaymentOrder.Query().Where(paymentorder.UserIDEQ(userID), paymentorder.StatusEQ(OrderStatusPending)).Count(ctx)
if err != nil {
return fmt.Errorf("count pending orders: %w", err)
}
if c >= max {
return infraerrors.TooManyRequests("TOO_MANY_PENDING", fmt.Sprintf("too many pending orders (max %d)", max)).
WithMetadata(map[string]string{"max": strconv.Itoa(max)})
}
return nil
}
func (s *PaymentService) checkDailyLimit(ctx context.Context, tx *dbent.Tx, userID int64, amount, limit float64) error {
if limit <= 0 {
return nil
}
ts := psStartOfDayUTC(time.Now())
orders, err := tx.PaymentOrder.Query().Where(paymentorder.UserIDEQ(userID), paymentorder.StatusIn(OrderStatusPaid, OrderStatusRecharging, OrderStatusCompleted), paymentorder.PaidAtGTE(ts)).All(ctx)
if err != nil {
return fmt.Errorf("query daily usage: %w", err)
}
var used float64
for _, o := range orders {
used += o.Amount
}
if used+amount > limit {
return infraerrors.TooManyRequests("DAILY_LIMIT_EXCEEDED", fmt.Sprintf("daily recharge limit reached, remaining: %.2f", math.Max(0, limit-used)))
}
return nil
}
func (s *PaymentService) invokeProvider(ctx context.Context, order *dbent.PaymentOrder, req CreateOrderRequest, cfg *PaymentConfig, payAmountStr string, payAmount float64, plan *dbent.SubscriptionPlan) (*CreateOrderResponse, error) {
s.EnsureProviders(ctx)
providerKey := s.registry.GetProviderKey(req.PaymentType)
if providerKey == "" {
return nil, infraerrors.ServiceUnavailable("PAYMENT_GATEWAY_ERROR", fmt.Sprintf("payment method (%s) is not configured", req.PaymentType))
}
sel, err := s.loadBalancer.SelectInstance(ctx, providerKey, req.PaymentType, payment.Strategy(cfg.LoadBalanceStrategy), payAmount)
if err != nil {
return nil, fmt.Errorf("select provider instance: %w", err)
}
if sel == nil {
return nil, infraerrors.TooManyRequests("NO_AVAILABLE_INSTANCE", "no available payment instance")
}
prov, err := provider.CreateProvider(providerKey, sel.InstanceID, sel.Config)
if err != nil {
return nil, infraerrors.ServiceUnavailable("PAYMENT_GATEWAY_ERROR", "payment method is temporarily unavailable")
}
subject := s.buildPaymentSubject(plan, payAmountStr, cfg)
outTradeNo := order.OutTradeNo
pr, err := prov.CreatePayment(ctx, payment.CreatePaymentRequest{OrderID: outTradeNo, Amount: payAmountStr, PaymentType: req.PaymentType, Subject: subject, ClientIP: req.ClientIP, IsMobile: req.IsMobile, InstanceSubMethods: sel.SupportedTypes})
if err != nil {
slog.Error("[PaymentService] CreatePayment failed", "provider", providerKey, "instance", sel.InstanceID, "error", err)
return nil, infraerrors.ServiceUnavailable("PAYMENT_GATEWAY_ERROR", fmt.Sprintf("payment gateway error: %s", err.Error()))
}
_, err = s.entClient.PaymentOrder.UpdateOneID(order.ID).SetNillablePaymentTradeNo(psNilIfEmpty(pr.TradeNo)).SetNillablePayURL(psNilIfEmpty(pr.PayURL)).SetNillableQrCode(psNilIfEmpty(pr.QRCode)).SetNillableProviderInstanceID(psNilIfEmpty(sel.InstanceID)).Save(ctx)
if err != nil {
return nil, fmt.Errorf("update order with payment details: %w", err)
}
s.writeAuditLog(ctx, order.ID, "ORDER_CREATED", fmt.Sprintf("user:%d", req.UserID), map[string]any{"amount": req.Amount, "paymentType": req.PaymentType, "orderType": req.OrderType})
return &CreateOrderResponse{OrderID: order.ID, Amount: order.Amount, PayAmount: payAmount, FeeRate: order.FeeRate, Status: OrderStatusPending, PaymentType: req.PaymentType, PayURL: pr.PayURL, QRCode: pr.QRCode, ClientSecret: pr.ClientSecret, ExpiresAt: order.ExpiresAt, PaymentMode: sel.PaymentMode}, nil
}
func (s *PaymentService) buildPaymentSubject(plan *dbent.SubscriptionPlan, payAmountStr string, cfg *PaymentConfig) string {
if plan != nil {
if plan.ProductName != "" {
return plan.ProductName
}
return "Sub2API Subscription " + plan.Name
}
pf := strings.TrimSpace(cfg.ProductNamePrefix)
sf := strings.TrimSpace(cfg.ProductNameSuffix)
if pf != "" || sf != "" {
return strings.TrimSpace(pf + " " + payAmountStr + " " + sf)
}
return "Sub2API " + payAmountStr + " CNY"
}
// --- Order Queries ---
func (s *PaymentService) GetOrder(ctx context.Context, orderID, userID int64) (*dbent.PaymentOrder, error) {
o, err := s.entClient.PaymentOrder.Get(ctx, orderID)
if err != nil {
return nil, infraerrors.NotFound("NOT_FOUND", "order not found")
}
if o.UserID != userID {
return nil, infraerrors.Forbidden("FORBIDDEN", "no permission for this order")
}
return o, nil
}
func (s *PaymentService) GetOrderByID(ctx context.Context, orderID int64) (*dbent.PaymentOrder, error) {
o, err := s.entClient.PaymentOrder.Get(ctx, orderID)
if err != nil {
return nil, infraerrors.NotFound("NOT_FOUND", "order not found")
}
return o, nil
}
func (s *PaymentService) GetUserOrders(ctx context.Context, userID int64, p OrderListParams) ([]*dbent.PaymentOrder, int, error) {
q := s.entClient.PaymentOrder.Query().Where(paymentorder.UserIDEQ(userID))
if p.Status != "" {
q = q.Where(paymentorder.StatusEQ(p.Status))
}
if p.OrderType != "" {
q = q.Where(paymentorder.OrderTypeEQ(p.OrderType))
}
if p.PaymentType != "" {
q = q.Where(paymentorder.PaymentTypeEQ(p.PaymentType))
}
total, err := q.Clone().Count(ctx)
if err != nil {
return nil, 0, fmt.Errorf("count user orders: %w", err)
}
ps, pg := applyPagination(p.PageSize, p.Page)
orders, err := q.Order(dbent.Desc(paymentorder.FieldCreatedAt)).Limit(ps).Offset((pg - 1) * ps).All(ctx)
if err != nil {
return nil, 0, fmt.Errorf("query user orders: %w", err)
}
return orders, total, nil
}
// AdminListOrders returns a paginated list of orders. If userID > 0, filters by user.
func (s *PaymentService) AdminListOrders(ctx context.Context, userID int64, p OrderListParams) ([]*dbent.PaymentOrder, int, error) {
q := s.entClient.PaymentOrder.Query()
if userID > 0 {
q = q.Where(paymentorder.UserIDEQ(userID))
}
if p.Status != "" {
q = q.Where(paymentorder.StatusEQ(p.Status))
}
if p.OrderType != "" {
q = q.Where(paymentorder.OrderTypeEQ(p.OrderType))
}
if p.PaymentType != "" {
q = q.Where(paymentorder.PaymentTypeEQ(p.PaymentType))
}
if p.Keyword != "" {
q = q.Where(paymentorder.Or(
paymentorder.OutTradeNoContainsFold(p.Keyword),
paymentorder.UserEmailContainsFold(p.Keyword),
paymentorder.UserNameContainsFold(p.Keyword),
))
}
total, err := q.Clone().Count(ctx)
if err != nil {
return nil, 0, fmt.Errorf("count admin orders: %w", err)
}
ps, pg := applyPagination(p.PageSize, p.Page)
orders, err := q.Order(dbent.Desc(paymentorder.FieldCreatedAt)).Limit(ps).Offset((pg - 1) * ps).All(ctx)
if err != nil {
return nil, 0, fmt.Errorf("query admin orders: %w", err)
}
return orders, total, nil
}

View File

@ -0,0 +1,73 @@
package service
import (
"context"
"log/slog"
"sync"
"time"
)
const expiryCheckTimeout = 30 * time.Second
// PaymentOrderExpiryService periodically expires timed-out payment orders.
type PaymentOrderExpiryService struct {
paymentSvc *PaymentService
interval time.Duration
stopCh chan struct{}
stopOnce sync.Once
wg sync.WaitGroup
}
func NewPaymentOrderExpiryService(paymentSvc *PaymentService, interval time.Duration) *PaymentOrderExpiryService {
return &PaymentOrderExpiryService{
paymentSvc: paymentSvc,
interval: interval,
stopCh: make(chan struct{}),
}
}
func (s *PaymentOrderExpiryService) Start() {
if s == nil || s.paymentSvc == nil || s.interval <= 0 {
return
}
s.wg.Add(1)
go func() {
defer s.wg.Done()
ticker := time.NewTicker(s.interval)
defer ticker.Stop()
s.runOnce()
for {
select {
case <-ticker.C:
s.runOnce()
case <-s.stopCh:
return
}
}
}()
}
func (s *PaymentOrderExpiryService) Stop() {
if s == nil {
return
}
s.stopOnce.Do(func() {
close(s.stopCh)
})
s.wg.Wait()
}
func (s *PaymentOrderExpiryService) runOnce() {
ctx, cancel := context.WithTimeout(context.Background(), expiryCheckTimeout)
defer cancel()
expired, err := s.paymentSvc.ExpireTimedOutOrders(ctx)
if err != nil {
slog.Error("[PaymentOrderExpiry] failed to expire orders", "error", err)
return
}
if expired > 0 {
slog.Info("[PaymentOrderExpiry] expired timed-out orders", "count", expired)
}
}

View File

@ -0,0 +1,257 @@
package service
import (
"context"
"fmt"
"log/slog"
"strconv"
"time"
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/paymentauditlog"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/internal/payment"
"github.com/Wei-Shaw/sub2api/internal/payment/provider"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
)
// --- Cancel & Expire ---
// Cancel rate limit configuration constants.
const (
rateLimitUnitDay = "day"
rateLimitUnitMinute = "minute"
rateLimitUnitHour = "hour"
rateLimitModeFixed = "fixed"
checkPaidResultAlreadyPaid = "already_paid"
checkPaidResultCancelled = "cancelled"
)
func (s *PaymentService) checkCancelRateLimit(ctx context.Context, userID int64, cfg *PaymentConfig) error {
if !cfg.CancelRateLimitEnabled || cfg.CancelRateLimitMax <= 0 {
return nil
}
windowStart := cancelRateLimitWindowStart(cfg)
operator := fmt.Sprintf("user:%d", userID)
count, err := s.entClient.PaymentAuditLog.Query().
Where(
paymentauditlog.ActionEQ("ORDER_CANCELLED"),
paymentauditlog.OperatorEQ(operator),
paymentauditlog.CreatedAtGTE(windowStart),
).Count(ctx)
if err != nil {
slog.Error("check cancel rate limit failed", "userID", userID, "error", err)
return nil // fail open
}
if count >= cfg.CancelRateLimitMax {
return infraerrors.TooManyRequests("CANCEL_RATE_LIMITED", "cancel rate limited").
WithMetadata(map[string]string{
"max": strconv.Itoa(cfg.CancelRateLimitMax),
"window": strconv.Itoa(cfg.CancelRateLimitWindow),
"unit": cfg.CancelRateLimitUnit,
})
}
return nil
}
func cancelRateLimitWindowStart(cfg *PaymentConfig) time.Time {
now := time.Now()
w := cfg.CancelRateLimitWindow
if w <= 0 {
w = 1
}
unit := cfg.CancelRateLimitUnit
if unit == "" {
unit = rateLimitUnitDay
}
if cfg.CancelRateLimitMode == rateLimitModeFixed {
switch unit {
case rateLimitUnitMinute:
t := now.Truncate(time.Minute)
return t.Add(-time.Duration(w-1) * time.Minute)
case rateLimitUnitDay:
y, m, d := now.Date()
t := time.Date(y, m, d, 0, 0, 0, 0, now.Location())
return t.AddDate(0, 0, -(w - 1))
default: // hour
t := now.Truncate(time.Hour)
return t.Add(-time.Duration(w-1) * time.Hour)
}
}
// rolling window
switch unit {
case rateLimitUnitMinute:
return now.Add(-time.Duration(w) * time.Minute)
case rateLimitUnitDay:
return now.AddDate(0, 0, -w)
default: // hour
return now.Add(-time.Duration(w) * time.Hour)
}
}
func (s *PaymentService) CancelOrder(ctx context.Context, orderID, userID int64) (string, error) {
o, err := s.entClient.PaymentOrder.Get(ctx, orderID)
if err != nil {
return "", infraerrors.NotFound("NOT_FOUND", "order not found")
}
if o.UserID != userID {
return "", infraerrors.Forbidden("FORBIDDEN", "no permission for this order")
}
if o.Status != OrderStatusPending {
return "", infraerrors.BadRequest("INVALID_STATUS", "order cannot be cancelled in current status")
}
return s.cancelCore(ctx, o, OrderStatusCancelled, fmt.Sprintf("user:%d", userID), "user cancelled order")
}
func (s *PaymentService) AdminCancelOrder(ctx context.Context, orderID int64) (string, error) {
o, err := s.entClient.PaymentOrder.Get(ctx, orderID)
if err != nil {
return "", infraerrors.NotFound("NOT_FOUND", "order not found")
}
if o.Status != OrderStatusPending {
return "", infraerrors.BadRequest("INVALID_STATUS", "order cannot be cancelled in current status")
}
return s.cancelCore(ctx, o, OrderStatusCancelled, "admin", "admin cancelled order")
}
func (s *PaymentService) cancelCore(ctx context.Context, o *dbent.PaymentOrder, fs, op, ad string) (string, error) {
if o.PaymentTradeNo != "" || o.PaymentType != "" {
if s.checkPaid(ctx, o) == checkPaidResultAlreadyPaid {
return checkPaidResultAlreadyPaid, nil
}
}
c, err := s.entClient.PaymentOrder.Update().Where(paymentorder.IDEQ(o.ID), paymentorder.StatusEQ(OrderStatusPending)).SetStatus(fs).Save(ctx)
if err != nil {
return "", fmt.Errorf("update order status: %w", err)
}
if c > 0 {
auditAction := "ORDER_CANCELLED"
if fs == OrderStatusExpired {
auditAction = "ORDER_EXPIRED"
}
s.writeAuditLog(ctx, o.ID, auditAction, op, map[string]any{"detail": ad})
}
return checkPaidResultCancelled, nil
}
func (s *PaymentService) checkPaid(ctx context.Context, o *dbent.PaymentOrder) string {
prov, err := s.getOrderProvider(ctx, o)
if err != nil {
return ""
}
// Use OutTradeNo as fallback when PaymentTradeNo is empty
// (e.g. EasyPay popup mode where trade_no arrives only via notify callback)
tradeNo := o.PaymentTradeNo
if tradeNo == "" {
tradeNo = o.OutTradeNo
}
resp, err := prov.QueryOrder(ctx, tradeNo)
if err != nil {
slog.Warn("query upstream failed", "orderID", o.ID, "error", err)
return ""
}
if resp.Status == payment.ProviderStatusPaid {
if err := s.HandlePaymentNotification(ctx, &payment.PaymentNotification{TradeNo: o.PaymentTradeNo, OrderID: o.OutTradeNo, Amount: resp.Amount, Status: payment.ProviderStatusSuccess}, prov.ProviderKey()); err != nil {
slog.Error("fulfillment failed during checkPaid", "orderID", o.ID, "error", err)
// Still return already_paid — order was paid, fulfillment can be retried
}
return checkPaidResultAlreadyPaid
}
if cp, ok := prov.(payment.CancelableProvider); ok {
_ = cp.CancelPayment(ctx, tradeNo)
}
return ""
}
// VerifyOrderByOutTradeNo actively queries the upstream provider to check
// if a payment was made, and processes it if so. This handles the case where
// the provider's notify callback was missed (e.g. EasyPay popup mode).
func (s *PaymentService) VerifyOrderByOutTradeNo(ctx context.Context, outTradeNo string, userID int64) (*dbent.PaymentOrder, error) {
o, err := s.entClient.PaymentOrder.Query().
Where(paymentorder.OutTradeNo(outTradeNo)).
Only(ctx)
if err != nil {
return nil, infraerrors.NotFound("NOT_FOUND", "order not found")
}
if o.UserID != userID {
return nil, infraerrors.Forbidden("FORBIDDEN", "no permission for this order")
}
// Only verify orders that are still pending or recently expired
if o.Status == OrderStatusPending || o.Status == OrderStatusExpired {
result := s.checkPaid(ctx, o)
if result == checkPaidResultAlreadyPaid {
// Reload order to get updated status
o, err = s.entClient.PaymentOrder.Get(ctx, o.ID)
if err != nil {
return nil, fmt.Errorf("reload order: %w", err)
}
}
}
return o, nil
}
// VerifyOrderPublic verifies payment status without user authentication.
// Used by the payment result page when the user's session has expired.
func (s *PaymentService) VerifyOrderPublic(ctx context.Context, outTradeNo string) (*dbent.PaymentOrder, error) {
o, err := s.entClient.PaymentOrder.Query().
Where(paymentorder.OutTradeNo(outTradeNo)).
Only(ctx)
if err != nil {
return nil, infraerrors.NotFound("NOT_FOUND", "order not found")
}
if o.Status == OrderStatusPending || o.Status == OrderStatusExpired {
result := s.checkPaid(ctx, o)
if result == checkPaidResultAlreadyPaid {
o, err = s.entClient.PaymentOrder.Get(ctx, o.ID)
if err != nil {
return nil, fmt.Errorf("reload order: %w", err)
}
}
}
return o, nil
}
func (s *PaymentService) ExpireTimedOutOrders(ctx context.Context) (int, error) {
now := time.Now()
orders, err := s.entClient.PaymentOrder.Query().Where(paymentorder.StatusEQ(OrderStatusPending), paymentorder.ExpiresAtLTE(now)).All(ctx)
if err != nil {
return 0, fmt.Errorf("query expired: %w", err)
}
n := 0
for _, o := range orders {
// Check upstream payment status before expiring — the user may have
// paid just before timeout and the webhook hasn't arrived yet.
outcome, _ := s.cancelCore(ctx, o, OrderStatusExpired, "system", "order expired")
if outcome == checkPaidResultAlreadyPaid {
slog.Info("order was paid during expiry", "orderID", o.ID)
continue
}
if outcome != "" {
n++
}
}
return n, nil
}
// getOrderProvider creates a provider using the order's original instance config.
// Falls back to registry lookup if instance ID is missing (legacy orders).
func (s *PaymentService) getOrderProvider(ctx context.Context, o *dbent.PaymentOrder) (payment.Provider, error) {
if o.ProviderInstanceID != nil && *o.ProviderInstanceID != "" {
instID, err := strconv.ParseInt(*o.ProviderInstanceID, 10, 64)
if err == nil {
cfg, err := s.loadBalancer.GetInstanceConfig(ctx, instID)
if err == nil {
providerKey := s.registry.GetProviderKey(o.PaymentType)
if providerKey == "" {
providerKey = o.PaymentType
}
p, err := provider.CreateProvider(providerKey, *o.ProviderInstanceID, cfg)
if err == nil {
return p, nil
}
}
}
}
s.EnsureProviders(ctx)
return s.registry.GetProvider(o.PaymentType)
}

View File

@ -0,0 +1,248 @@
package service
import (
"context"
"fmt"
"log/slog"
"math"
"strconv"
"strings"
"time"
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/internal/payment"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
)
// --- Refund Flow ---
func (s *PaymentService) RequestRefund(ctx context.Context, oid, uid int64, reason string) error {
o, err := s.validateRefundRequest(ctx, oid, uid)
if err != nil {
return err
}
u, err := s.userRepo.GetByID(ctx, o.UserID)
if err != nil {
return fmt.Errorf("get user: %w", err)
}
if u.Balance < o.Amount {
return infraerrors.BadRequest("BALANCE_NOT_ENOUGH", "refund amount exceeds balance")
}
nr := strings.TrimSpace(reason)
now := time.Now()
by := fmt.Sprintf("%d", uid)
c, err := s.entClient.PaymentOrder.Update().Where(paymentorder.IDEQ(oid), paymentorder.UserIDEQ(uid), paymentorder.StatusEQ(OrderStatusCompleted), paymentorder.OrderTypeEQ(payment.OrderTypeBalance)).SetStatus(OrderStatusRefundRequested).SetRefundRequestedAt(now).SetRefundRequestReason(nr).SetRefundRequestedBy(by).SetRefundAmount(o.Amount).Save(ctx)
if err != nil {
return fmt.Errorf("update: %w", err)
}
if c == 0 {
return infraerrors.Conflict("CONFLICT", "order status changed")
}
s.writeAuditLog(ctx, oid, "REFUND_REQUESTED", fmt.Sprintf("user:%d", uid), map[string]any{"amount": o.Amount, "reason": nr})
return nil
}
func (s *PaymentService) validateRefundRequest(ctx context.Context, oid, uid int64) (*dbent.PaymentOrder, error) {
o, err := s.entClient.PaymentOrder.Get(ctx, oid)
if err != nil {
return nil, infraerrors.NotFound("NOT_FOUND", "order not found")
}
if o.UserID != uid {
return nil, infraerrors.Forbidden("FORBIDDEN", "no permission")
}
if o.OrderType != payment.OrderTypeBalance {
return nil, infraerrors.BadRequest("INVALID_ORDER_TYPE", "only balance orders can request refund")
}
if o.Status != OrderStatusCompleted {
return nil, infraerrors.BadRequest("INVALID_STATUS", "only completed orders can request refund")
}
return o, nil
}
func (s *PaymentService) PrepareRefund(ctx context.Context, oid int64, amt float64, reason string, force, deduct bool) (*RefundPlan, *RefundResult, error) {
o, err := s.entClient.PaymentOrder.Get(ctx, oid)
if err != nil {
return nil, nil, infraerrors.NotFound("NOT_FOUND", "order not found")
}
ok := []string{OrderStatusCompleted, OrderStatusRefundRequested, OrderStatusRefundFailed}
if !psSliceContains(ok, o.Status) {
return nil, nil, infraerrors.BadRequest("INVALID_STATUS", "order status does not allow refund")
}
if math.IsNaN(amt) || math.IsInf(amt, 0) {
return nil, nil, infraerrors.BadRequest("INVALID_AMOUNT", "invalid refund amount")
}
if amt <= 0 {
amt = o.Amount
}
if amt-o.Amount > amountToleranceCNY {
return nil, nil, infraerrors.BadRequest("REFUND_AMOUNT_EXCEEDED", "refund amount exceeds recharge")
}
// Full refund: use actual pay_amount for gateway (includes fees)
ga := amt
if math.Abs(amt-o.Amount) <= amountToleranceCNY {
ga = o.PayAmount
}
rr := strings.TrimSpace(reason)
if rr == "" && o.RefundRequestReason != nil {
rr = *o.RefundRequestReason
}
if rr == "" {
rr = fmt.Sprintf("refund order:%d", o.ID)
}
p := &RefundPlan{OrderID: oid, Order: o, RefundAmount: amt, GatewayAmount: ga, Reason: rr, Force: force, DeductBalance: deduct, DeductionType: payment.DeductionTypeNone}
if deduct {
if er := s.prepDeduct(ctx, o, p, force); er != nil {
return nil, er, nil
}
}
return p, nil, nil
}
func (s *PaymentService) prepDeduct(ctx context.Context, o *dbent.PaymentOrder, p *RefundPlan, force bool) *RefundResult {
if o.OrderType == payment.OrderTypeSubscription {
p.DeductionType = payment.DeductionTypeSubscription
if o.SubscriptionGroupID != nil && o.SubscriptionDays != nil {
p.SubDaysToDeduct = *o.SubscriptionDays
sub, err := s.subscriptionSvc.GetActiveSubscription(ctx, o.UserID, *o.SubscriptionGroupID)
if err == nil && sub != nil {
p.SubscriptionID = sub.ID
} else if !force {
return &RefundResult{Success: false, Warning: "cannot find active subscription for deduction, use force", RequireForce: true}
}
}
return nil
}
u, err := s.userRepo.GetByID(ctx, o.UserID)
if err != nil {
if !force {
return &RefundResult{Success: false, Warning: "cannot fetch user balance, use force", RequireForce: true}
}
return nil
}
p.DeductionType = payment.DeductionTypeBalance
p.BalanceToDeduct = math.Min(p.RefundAmount, u.Balance)
return nil
}
func (s *PaymentService) ExecuteRefund(ctx context.Context, p *RefundPlan) (*RefundResult, error) {
c, err := s.entClient.PaymentOrder.Update().Where(paymentorder.IDEQ(p.OrderID), paymentorder.StatusIn(OrderStatusCompleted, OrderStatusRefundRequested, OrderStatusRefundFailed)).SetStatus(OrderStatusRefunding).Save(ctx)
if err != nil {
return nil, fmt.Errorf("lock: %w", err)
}
if c == 0 {
return nil, infraerrors.Conflict("CONFLICT", "order status changed")
}
if p.DeductionType == payment.DeductionTypeBalance && p.BalanceToDeduct > 0 {
// Skip balance deduction on retry if previous attempt already deducted
// but failed to roll back (REFUND_ROLLBACK_FAILED in audit log).
if !s.hasAuditLog(ctx, p.OrderID, "REFUND_ROLLBACK_FAILED") {
if err := s.userRepo.DeductBalance(ctx, p.Order.UserID, p.BalanceToDeduct); err != nil {
s.restoreStatus(ctx, p)
return nil, fmt.Errorf("deduction: %w", err)
}
} else {
slog.Warn("skipping balance deduction on retry (previous rollback failed)", "orderID", p.OrderID)
p.BalanceToDeduct = 0
}
}
if p.DeductionType == payment.DeductionTypeSubscription && p.SubDaysToDeduct > 0 && p.SubscriptionID > 0 {
if !s.hasAuditLog(ctx, p.OrderID, "REFUND_ROLLBACK_FAILED") {
_, err := s.subscriptionSvc.ExtendSubscription(ctx, p.SubscriptionID, -p.SubDaysToDeduct)
if err != nil {
// If deducting would expire the subscription, revoke it entirely
slog.Info("subscription deduction would expire, revoking", "orderID", p.OrderID, "subID", p.SubscriptionID, "days", p.SubDaysToDeduct)
if revokeErr := s.subscriptionSvc.RevokeSubscription(ctx, p.SubscriptionID); revokeErr != nil {
s.restoreStatus(ctx, p)
return nil, fmt.Errorf("revoke subscription: %w", revokeErr)
}
}
} else {
slog.Warn("skipping subscription deduction on retry (previous rollback failed)", "orderID", p.OrderID)
p.SubDaysToDeduct = 0
}
}
if err := s.gwRefund(ctx, p); err != nil {
return s.handleGwFail(ctx, p, err)
}
return s.markRefundOk(ctx, p)
}
func (s *PaymentService) gwRefund(ctx context.Context, p *RefundPlan) error {
if p.Order.PaymentTradeNo == "" {
s.writeAuditLog(ctx, p.Order.ID, "REFUND_NO_TRADE_NO", "admin", map[string]any{"detail": "skipped"})
return nil
}
// Use the exact provider instance that created this order, not a random one
// from the registry. Each instance has its own merchant credentials.
prov, err := s.getRefundProvider(ctx, p.Order)
if err != nil {
return fmt.Errorf("get refund provider: %w", err)
}
_, err = prov.Refund(ctx, payment.RefundRequest{
TradeNo: p.Order.PaymentTradeNo,
OrderID: p.Order.OutTradeNo,
Amount: strconv.FormatFloat(p.GatewayAmount, 'f', 2, 64),
Reason: p.Reason,
})
return err
}
// getRefundProvider creates a provider using the order's original instance config.
// Delegates to getOrderProvider which handles instance lookup and fallback.
func (s *PaymentService) getRefundProvider(ctx context.Context, o *dbent.PaymentOrder) (payment.Provider, error) {
return s.getOrderProvider(ctx, o)
}
func (s *PaymentService) handleGwFail(ctx context.Context, p *RefundPlan, gErr error) (*RefundResult, error) {
if s.RollbackRefund(ctx, p, gErr) {
s.restoreStatus(ctx, p)
s.writeAuditLog(ctx, p.OrderID, "REFUND_GATEWAY_FAILED", "admin", map[string]any{"detail": psErrMsg(gErr)})
return &RefundResult{Success: false, Warning: "gateway failed: " + psErrMsg(gErr) + ", rolled back"}, nil
}
now := time.Now()
_, _ = s.entClient.PaymentOrder.UpdateOneID(p.OrderID).SetStatus(OrderStatusRefundFailed).SetFailedAt(now).SetFailedReason(psErrMsg(gErr)).Save(ctx)
s.writeAuditLog(ctx, p.OrderID, "REFUND_FAILED", "admin", map[string]any{"detail": psErrMsg(gErr)})
return nil, infraerrors.InternalServer("REFUND_FAILED", psErrMsg(gErr))
}
func (s *PaymentService) markRefundOk(ctx context.Context, p *RefundPlan) (*RefundResult, error) {
fs := OrderStatusRefunded
if p.RefundAmount < p.Order.Amount {
fs = OrderStatusPartiallyRefunded
}
now := time.Now()
_, err := s.entClient.PaymentOrder.UpdateOneID(p.OrderID).SetStatus(fs).SetRefundAmount(p.RefundAmount).SetRefundReason(p.Reason).SetRefundAt(now).SetForceRefund(p.Force).Save(ctx)
if err != nil {
return nil, fmt.Errorf("mark refund: %w", err)
}
s.writeAuditLog(ctx, p.OrderID, "REFUND_SUCCESS", "admin", map[string]any{"refundAmount": p.RefundAmount, "reason": p.Reason, "balanceDeducted": p.BalanceToDeduct, "force": p.Force})
return &RefundResult{Success: true, BalanceDeducted: p.BalanceToDeduct, SubDaysDeducted: p.SubDaysToDeduct}, nil
}
func (s *PaymentService) RollbackRefund(ctx context.Context, p *RefundPlan, gErr error) bool {
if p.DeductionType == payment.DeductionTypeBalance && p.BalanceToDeduct > 0 {
if err := s.userRepo.UpdateBalance(ctx, p.Order.UserID, p.BalanceToDeduct); err != nil {
slog.Error("[CRITICAL] rollback failed", "orderID", p.OrderID, "amount", p.BalanceToDeduct, "error", err)
s.writeAuditLog(ctx, p.OrderID, "REFUND_ROLLBACK_FAILED", "admin", map[string]any{"gatewayError": psErrMsg(gErr), "rollbackError": psErrMsg(err), "balanceDeducted": p.BalanceToDeduct})
return false
}
}
if p.DeductionType == payment.DeductionTypeSubscription && p.SubDaysToDeduct > 0 && p.SubscriptionID > 0 {
if _, err := s.subscriptionSvc.ExtendSubscription(ctx, p.SubscriptionID, p.SubDaysToDeduct); err != nil {
slog.Error("[CRITICAL] subscription rollback failed", "orderID", p.OrderID, "subID", p.SubscriptionID, "days", p.SubDaysToDeduct, "error", err)
s.writeAuditLog(ctx, p.OrderID, "REFUND_ROLLBACK_FAILED", "admin", map[string]any{"gatewayError": psErrMsg(gErr), "rollbackError": psErrMsg(err), "subDaysDeducted": p.SubDaysToDeduct})
return false
}
}
return true
}
func (s *PaymentService) restoreStatus(ctx context.Context, p *RefundPlan) {
rs := OrderStatusCompleted
if p.Order.Status == OrderStatusRefundRequested {
rs = OrderStatusRefundRequested
}
_, _ = s.entClient.PaymentOrder.UpdateOneID(p.OrderID).SetStatus(rs).Save(ctx)
}

View File

@ -0,0 +1,311 @@
package service
import (
"context"
"fmt"
"log/slog"
"math/rand/v2"
"sync"
"time"
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/paymentorder"
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
"github.com/Wei-Shaw/sub2api/internal/payment"
"github.com/Wei-Shaw/sub2api/internal/payment/provider"
)
// --- Order Status Constants ---
const (
OrderStatusPending = payment.OrderStatusPending
OrderStatusPaid = payment.OrderStatusPaid
OrderStatusRecharging = payment.OrderStatusRecharging
OrderStatusCompleted = payment.OrderStatusCompleted
OrderStatusExpired = payment.OrderStatusExpired
OrderStatusCancelled = payment.OrderStatusCancelled
OrderStatusFailed = payment.OrderStatusFailed
OrderStatusRefundRequested = payment.OrderStatusRefundRequested
OrderStatusRefunding = payment.OrderStatusRefunding
OrderStatusPartiallyRefunded = payment.OrderStatusPartiallyRefunded
OrderStatusRefunded = payment.OrderStatusRefunded
OrderStatusRefundFailed = payment.OrderStatusRefundFailed
)
const (
// defaultMaxPendingOrders and defaultOrderTimeoutMin are defined in
// payment_config_service.go alongside other payment configuration defaults.
paymentGraceMinutes = 5
defaultPageSize = 20
maxPageSize = 100
topUsersLimit = 10
amountToleranceCNY = 0.01
orderIDPrefix = "sub2_"
)
// --- Types ---
// generateOutTradeNo creates a unique external order ID for payment providers.
// Format: sub2_20250409aB3kX9mQ (prefix + date + 8-char random)
func generateOutTradeNo() string {
date := time.Now().Format("20060102")
rnd := generateRandomString(8)
return orderIDPrefix + date + rnd
}
func generateRandomString(n int) string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, n)
for i := range b {
b[i] = charset[rand.IntN(len(charset))]
}
return string(b)
}
type CreateOrderRequest struct {
UserID int64
Amount float64
PaymentType string
ClientIP string
IsMobile bool
SrcHost string
SrcURL string
OrderType string
PlanID int64
}
type CreateOrderResponse struct {
OrderID int64 `json:"order_id"`
Amount float64 `json:"amount"`
PayAmount float64 `json:"pay_amount"`
FeeRate float64 `json:"fee_rate"`
Status string `json:"status"`
PaymentType string `json:"payment_type"`
PayURL string `json:"pay_url,omitempty"`
QRCode string `json:"qr_code,omitempty"`
ClientSecret string `json:"client_secret,omitempty"`
ExpiresAt time.Time `json:"expires_at"`
PaymentMode string `json:"payment_mode,omitempty"`
}
type OrderListParams struct {
Page int
PageSize int
Status string
OrderType string
PaymentType string
Keyword string
}
type RefundPlan struct {
OrderID int64
Order *dbent.PaymentOrder
RefundAmount float64
GatewayAmount float64
Reason string
Force bool
DeductBalance bool
DeductionType string
BalanceToDeduct float64
SubDaysToDeduct int
SubscriptionID int64
}
type RefundResult struct {
Success bool `json:"success"`
Warning string `json:"warning,omitempty"`
RequireForce bool `json:"require_force,omitempty"`
BalanceDeducted float64 `json:"balance_deducted,omitempty"`
SubDaysDeducted int `json:"subscription_days_deducted,omitempty"`
}
type DashboardStats struct {
TodayAmount float64 `json:"today_amount"`
TotalAmount float64 `json:"total_amount"`
TodayCount int `json:"today_count"`
TotalCount int `json:"total_count"`
AvgAmount float64 `json:"avg_amount"`
PendingOrders int `json:"pending_orders"`
DailySeries []DailyStats `json:"daily_series"`
PaymentMethods []PaymentMethodStat `json:"payment_methods"`
TopUsers []TopUserStat `json:"top_users"`
}
type DailyStats struct {
Date string `json:"date"`
Amount float64 `json:"amount"`
Count int `json:"count"`
}
type PaymentMethodStat struct {
Type string `json:"type"`
Amount float64 `json:"amount"`
Count int `json:"count"`
}
type TopUserStat struct {
UserID int64 `json:"user_id"`
Email string `json:"email"`
Amount float64 `json:"amount"`
}
// --- Service ---
type PaymentService struct {
providerMu sync.Mutex
providersLoaded bool
entClient *dbent.Client
registry *payment.Registry
loadBalancer payment.LoadBalancer
redeemService *RedeemService
subscriptionSvc *SubscriptionService
configService *PaymentConfigService
userRepo UserRepository
groupRepo GroupRepository
}
func NewPaymentService(entClient *dbent.Client, registry *payment.Registry, loadBalancer payment.LoadBalancer, redeemService *RedeemService, subscriptionSvc *SubscriptionService, configService *PaymentConfigService, userRepo UserRepository, groupRepo GroupRepository) *PaymentService {
return &PaymentService{entClient: entClient, registry: registry, loadBalancer: loadBalancer, redeemService: redeemService, subscriptionSvc: subscriptionSvc, configService: configService, userRepo: userRepo, groupRepo: groupRepo}
}
// --- Provider Registry ---
// EnsureProviders lazily initializes the provider registry on first call.
func (s *PaymentService) EnsureProviders(ctx context.Context) {
s.providerMu.Lock()
defer s.providerMu.Unlock()
if !s.providersLoaded {
s.loadProviders(ctx)
s.providersLoaded = true
}
}
// RefreshProviders clears and re-registers all providers from the database.
func (s *PaymentService) RefreshProviders(ctx context.Context) {
s.providerMu.Lock()
defer s.providerMu.Unlock()
s.registry.Clear()
s.loadProviders(ctx)
s.providersLoaded = true
}
func (s *PaymentService) loadProviders(ctx context.Context) {
instances, err := s.entClient.PaymentProviderInstance.Query().
Where(paymentproviderinstance.EnabledEQ(true)).
All(ctx)
if err != nil {
slog.Error("[PaymentService] failed to query provider instances", "error", err)
return
}
for _, inst := range instances {
cfg, err := s.loadBalancer.GetInstanceConfig(ctx, int64(inst.ID))
if err != nil {
slog.Warn("[PaymentService] failed to decrypt config for instance", "instanceID", inst.ID, "error", err)
continue
}
if inst.PaymentMode != "" {
cfg["paymentMode"] = inst.PaymentMode
}
instID := fmt.Sprintf("%d", inst.ID)
p, err := provider.CreateProvider(inst.ProviderKey, instID, cfg)
if err != nil {
slog.Warn("[PaymentService] failed to create provider for instance", "instanceID", inst.ID, "key", inst.ProviderKey, "error", err)
continue
}
s.registry.Register(p)
}
}
// GetWebhookProvider returns the provider instance that should verify a webhook.
// It extracts out_trade_no from the raw body, looks up the order to find the
// original provider instance, and creates a provider with that instance's credentials.
// Falls back to the registry provider when the order cannot be found.
func (s *PaymentService) GetWebhookProvider(ctx context.Context, providerKey, outTradeNo string) (payment.Provider, error) {
if outTradeNo != "" {
order, err := s.entClient.PaymentOrder.Query().Where(paymentorder.OutTradeNo(outTradeNo)).Only(ctx)
if err == nil {
p, pErr := s.getOrderProvider(ctx, order)
if pErr == nil {
return p, nil
}
slog.Warn("[Webhook] order provider creation failed, falling back to registry", "outTradeNo", outTradeNo, "error", pErr)
}
}
s.EnsureProviders(ctx)
return s.registry.GetProviderByKey(providerKey)
}
// --- Helpers ---
func psIsRefundStatus(s string) bool {
switch s {
case OrderStatusRefundRequested, OrderStatusRefunding, OrderStatusPartiallyRefunded, OrderStatusRefunded, OrderStatusRefundFailed:
return true
}
return false
}
func psErrMsg(err error) string {
if err == nil {
return ""
}
return err.Error()
}
func psNilIfEmpty(s string) *string {
if s == "" {
return nil
}
return &s
}
func psSliceContains(sl []string, s string) bool {
for _, v := range sl {
if v == s {
return true
}
}
return false
}
// Subscription validity period unit constants.
const (
validityUnitWeek = "week"
validityUnitMonth = "month"
)
func psComputeValidityDays(days int, unit string) int {
switch unit {
case validityUnitWeek:
return days * 7
case validityUnitMonth:
return days * 30
default:
return days
}
}
func (s *PaymentService) getFeeRate(_ string) float64 { return 0 }
func psStartOfDayUTC(t time.Time) time.Time {
y, m, d := t.UTC().Date()
return time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
}
func applyPagination(pageSize, page int) (size, pg int) {
size = pageSize
if size <= 0 {
size = defaultPageSize
}
if size > maxPageSize {
size = maxPageSize
}
pg = page
if pg < 1 {
pg = 1
}
return size, pg
}

Some files were not shown because too many files have changed in this diff Show More